1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3 
4     SPDX-FileCopyrightText: 2002 Vincent Decorges <vincent.decorges@eivd.ch>
5     SPDX-FileCopyrightText: 2003-2021 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
6 */
7 
8 #include "pythonwriter.h"
9 
10 #include "association.h"
11 #include "attribute.h"
12 #include "classifier.h"
13 #include "debug_utils.h"
14 #include "operation.h"
15 #include "umldoc.h"
16 #include "umlattributelist.h"
17 
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 
21 #include <QFile>
22 #include <QRegExp>
23 #include <QTextCodec>
24 #include <QTextStream>
25 
26 static const char *reserved_words[] = {
27     "abs",
28     "and",
29     "apply",
30     "ArithmeticError",
31     "assert",
32     "AssertionError",
33     "AttributeError",
34     "break",
35     "buffer",
36     "callable",
37     "chr",
38     "class",
39     "classmethod",
40     "cmp",
41     "coerce",
42     "compile",
43     "complex",
44     "continue",
45     "def",
46     "del",
47     "delattr",
48     "DeprecationWarning",
49     "dict",
50     "dir",
51     "divmod",
52     "elif",
53     "Ellipsis",
54     "else",
55     "EnvironmentError",
56     "EOFError",
57     "eval",
58     "except",
59     "Exception",
60     "exec",
61     "execfile",
62     "file",
63     "filter",
64     "finally",
65     "float",
66     "FloatingPointError",
67     "for",
68     "from",
69     "getattr",
70     "global",
71     "globals",
72     "hasattr",
73     "hash",
74     "hex",
75     "id",
76     "if",
77     "import",
78     "__import__",
79     "ImportError",
80     "in",
81     "IndentationError",
82     "IndexError",
83     "input",
84     "int",
85     "intern",
86     "IOError",
87     "is",
88     "isinstance",
89     "issubclass",
90     "iter",
91     "KeyboardInterrupt",
92     "KeyError",
93     "lambda",
94     "len",
95     "list",
96     "locals",
97     "long",
98     "LookupError",
99     "map",
100     "max",
101     "MemoryError",
102     "min",
103     "NameError",
104     "None",
105     "not",
106     "NotImplemented",
107     "NotImplementedError",
108     "object",
109     "oct",
110     "open",
111     "or",
112     "ord",
113     "OSError",
114     "OverflowError",
115     "OverflowWarning",
116     "pass",
117     "pow",
118     "print",
119     "property",
120     "raise",
121     "range",
122     "raw_input",
123     "reduce",
124     "ReferenceError",
125     "reload",
126     "repr",
127     "return",
128     "round",
129     "RuntimeError",
130     "RuntimeWarning",
131     "setattr",
132     "slice",
133     "StandardError",
134     "staticmethod",
135     "StopIteration",
136     "str",
137     "super",
138     "SyntaxError",
139     "SyntaxWarning",
140     "SystemError",
141     "SystemExit",
142     "TabError",
143     "try",
144     "tuple",
145     "type",
146     "TypeError",
147     "UnboundLocalError",
148     "unichr",
149     "unicode",
150     "UnicodeError",
151     "UserWarning",
152     "ValueError",
153     "vars",
154     "Warning",
155     "while",
156     "WindowsError",
157     "xrange",
158     "yield",
159     "ZeroDivisionError",
160     "zip",
161     0
162 };
163 
PythonWriter()164 PythonWriter::PythonWriter() : m_bNeedPass(true)
165 {
166 }
167 
~PythonWriter()168 PythonWriter::~PythonWriter()
169 {
170 }
171 
172 /**
173  * Call this method to generate C++ code for a UMLClassifier.
174  * @param c   the class you want to generate code for
175  */
writeClass(UMLClassifier * c)176 void PythonWriter::writeClass(UMLClassifier *c)
177 {
178     if (!c) {
179         uDebug() << "Cannot write class of NULL concept!";
180         return;
181     }
182 
183     QString classname = cleanName(c->name());
184 
185     UMLClassifierList superclasses = c->getSuperClasses();
186     UMLAssociationList aggregations = c->getAggregations();
187     UMLAssociationList compositions = c->getCompositions();
188 
189     m_bNeedPass = true;
190 
191     //find an appropriate name for our file
192     QString fileName = findFileName(c, QLatin1String(".py"));
193 
194     // Do not generate files for classes that has a container
195     if (hasContainer(fileName)) {
196         emit codeGenerated(c, CodeGenerator::Skipped);
197         return;
198     }
199 
200     if (fileName.isEmpty()) {
201         emit codeGenerated(c, false);
202         return;
203     }
204 
205     QFile fileh;
206     if (!openFile(fileh, fileName)) {
207         emit codeGenerated(c, false);
208         return;
209     }
210     QTextStream h(&fileh);
211     h.setCodec("UTF-8");
212 
213     //////////////////////////////
214     //Start generating the code!!
215     /////////////////////////////
216 
217     //try to find a heading file (license, comments, etc)
218     QString str;
219 
220     str = getHeadingFile(QLatin1String(".py"));
221     if (!str.isEmpty()) {
222         str.replace(QRegExp(QLatin1String("%filename%")), fileName);
223         str.replace(QRegExp(QLatin1String("%filepath%")), fileh.fileName());
224         h<<str<<m_endl;
225     }
226 
227     // generate import statement for superclasses and take packages into account
228     str = cleanName(c->name());
229     QString pkg = cleanName(c->package());
230     if (!pkg.isEmpty())
231         str.prepend(pkg + QLatin1Char('.'));
232     QStringList includesList  = QStringList(str); //save imported classes
233     int i = superclasses.count();
234     foreach (UMLClassifier *classifier,  superclasses) {
235         str = cleanName(classifier->name());
236         pkg = cleanName(classifier->package());
237         if (!pkg.isEmpty())
238             str.prepend(pkg + QLatin1Char('.'));
239         includesList.append(str);
240         h << "from " << str << " import *" << m_endl;
241         i--;
242     }
243 
244     //write includes and take namespaces into account
245     UMLPackageList includes;
246     findObjectsRelated(c, includes);
247 
248     foreach(UMLPackage* conc, includes) {
249         QString headerName = findFileName(conc, QLatin1String(".py"));
250         if (!headerName.isEmpty()) {
251             headerName.remove(QRegExp(QLatin1String(".py$")));
252             str = headerName.replace(QLatin1Char('/'), QLatin1Char('.'));
253             if (includesList.indexOf(str) < 0)  // not yet imported
254                 h << "from " << str << " import *" << m_endl;
255         }
256     }
257     h << m_endl;
258 
259     h << "class " << classname << (superclasses.count() > 0 ? QLatin1String(" (") : QLatin1String("(object)"));
260     i = superclasses.count();
261 
262     foreach (UMLClassifier *obj, superclasses) {
263 
264         h << cleanName(obj->name()) << (i>1 ? QLatin1String(", ") : QString());
265         i--;
266     }
267 
268     h << (superclasses.count() > 0 ? QLatin1String(")") : QString()) << ":" << m_endl << m_endl;
269 
270     if (forceDoc() || !c->doc().isEmpty()) {
271         h << m_indentation << "\"\"\"" << m_endl;
272         if (!c->doc().isEmpty()) {
273             h << formatDoc(c->doc(), m_indentation + QLatin1Char(' ')) << m_endl;
274             h << m_endl;
275         }
276         h << m_indentation << ":version:" << m_endl;
277         h << m_indentation << ":author:" << m_endl;
278         h << m_indentation << "\"\"\"" << m_endl << m_endl;
279         m_bNeedPass = false;
280     }
281 
282     // attributes
283     writeAttributes(c->getAttributeList(), h);
284 
285     //operations
286     writeOperations(c, h);
287 
288     if (m_bNeedPass)
289         h << m_indentation << "pass" << m_endl;
290 
291     //finish files
292     h << m_endl << m_endl;
293 
294     //close files and notfiy we are done
295     fileh.close();
296     emit codeGenerated(c, true);
297     emit showGeneratedFile(fileh.fileName());
298 }
299 
300 ////////////////////////////////////////////////////////////////////////////////////
301 //  Helper Methods
302 
303 /**
304  * Write all attributes for a given class.
305  * @param c    the concept we are generating code for
306  * @param py   output stream for the header file
307  */
writeAttributes(UMLAttributeList atList,QTextStream & py)308 void PythonWriter::writeAttributes(UMLAttributeList atList, QTextStream &py)
309 {
310     if (!forceDoc() || atList.count() == 0)
311         return;
312     py << m_indentation << "\"\"\" ATTRIBUTES" << m_endl << m_endl;
313     foreach (UMLAttribute *at, atList) {
314         if (!at->doc().isEmpty()) {
315             py << formatDoc(at->doc(), m_indentation + QLatin1Char(' ')) << m_endl;
316             py << m_endl;
317         }
318         Uml::Visibility::Enum vis = at->visibility();
319         py << m_indentation << cleanName(at->name()) << "  ("
320             << Uml::Visibility::toString(vis) << ")" << m_endl << m_endl ;
321     } // end for
322     py << m_indentation << "\"\"\"" << m_endl << m_endl;
323 }
324 
325 /**
326  * Write all operations for a given class.
327  * @param c   the concept we are generating code for
328  * @param h   output stream for the header file
329  */
writeOperations(UMLClassifier * c,QTextStream & h)330 void PythonWriter::writeOperations(UMLClassifier *c, QTextStream &h)
331 {
332     //Lists to store operations  sorted by scope
333     UMLOperationList oppub, opprot, oppriv;
334 
335     //sort operations by scope first and see if there are abstract methods
336     UMLOperationList opl(c->getOpList());
337     foreach(UMLOperation *op, opl) {
338         switch(op->visibility()) {
339           case Uml::Visibility::Public:
340             oppub.append(op);
341             break;
342           case Uml::Visibility::Protected:
343             opprot.append(op);
344             break;
345           case Uml::Visibility::Private:
346             oppriv.append(op);
347             break;
348           default:
349             break;
350         }
351     }
352 
353     QString classname(cleanName(c->name()));
354 
355     //write operations to file
356     if(forceSections() || !oppub.isEmpty()) {
357         writeOperations(classname, oppub, h, Uml::Visibility::Public);
358     }
359 
360     if(forceSections() || !opprot.isEmpty()) {
361         writeOperations(classname, opprot, h, Uml::Visibility::Protected);
362     }
363 
364     if(forceSections() || !oppriv.isEmpty()) {
365         writeOperations(classname, oppriv, h, Uml::Visibility::Private);
366     }
367 
368 }
369 
370 /**
371  * Write a list of class operations.
372  * @param classname   the name of the class
373  * @param opList      the list of operations
374  * @param h           output stream for the header file
375  * @param access      visibility identifier
376  */
writeOperations(const QString & classname,UMLOperationList & opList,QTextStream & h,Uml::Visibility::Enum access)377 void PythonWriter::writeOperations(const QString& classname, UMLOperationList &opList,
378                                    QTextStream &h, Uml::Visibility::Enum access)
379 {
380     Q_UNUSED(classname);
381     QString sAccess;
382 
383     switch (access)
384     {
385     case Uml::Visibility::Public:
386         sAccess = QString();
387         break;
388     case Uml::Visibility::Private:
389         sAccess = QLatin1String("__");
390         break;
391     case Uml::Visibility::Protected:
392         sAccess = QLatin1String("_");
393         break;
394     default:
395         break;
396     }
397 
398     foreach (UMLOperation* op,  opList) {
399         UMLAttributeList atl = op->getParmList();
400         //write method doc if we have doc || if at least one of the params has doc
401         bool writeDoc = forceDoc() || !op->doc().isEmpty();
402         foreach (UMLAttribute* at, atl)
403             writeDoc |= !at->doc().isEmpty();
404 
405         h << m_indentation << "def "<< sAccess + cleanName(op->name()) << "(self";
406 
407         int j=0;
408         foreach (UMLAttribute* at, atl) {
409             h << ", " << cleanName(at->name())
410               << (!(at->getInitialValue().isEmpty()) ?
411                   (QLatin1String(" = ") + at->getInitialValue()) : QString());
412             j++;
413         }
414 
415         h << "):" << m_endl;
416 
417         if (writeDoc)  //write method documentation
418         {
419             h << m_indentation << m_indentation << "\"\"\"" << m_endl;
420             h << formatDoc(op->doc(), m_indentation + m_indentation + QLatin1Char(' ')) << m_endl;
421 
422             foreach (UMLAttribute* at, atl)  //write parameter documentation
423             {
424                 if(forceDoc() || !at->doc().isEmpty()) {
425                     h<<m_indentation<<m_indentation<<"@param "<<at->getTypeName()<<
426                     " " << cleanName(at->name());
427                     h<<" : "<<at->doc()<<m_endl;
428                 }
429             }//end for : write parameter documentation
430             h << m_indentation << m_indentation << "@return " << op->getTypeName() << " :" << m_endl;
431             h << m_indentation << m_indentation << "@author" << m_endl;
432             h << m_indentation << m_indentation << "\"\"\"" << m_endl;
433         }
434         QString sourceCode = op->getSourceCode();
435         if (sourceCode.isEmpty()) {
436             h << m_indentation << m_indentation << "pass" << m_endl << m_endl;
437         }
438         else {
439             h << formatSourceCode(sourceCode, m_indentation + m_indentation) << m_endl << m_endl;
440         }
441         m_bNeedPass = false;
442     }//end for
443 }
444 
445 /**
446  * Check if type is a container
447  * @param string      type that will be used
448  * @return            true if is a container
449  */
hasContainer(const QString & string)450 bool PythonWriter::hasContainer(const QString &string)
451 {
452     return string.contains(QLatin1String("<")) && string.contains(QLatin1String(">"));
453 }
454 
455 /**
456  * Return the programming language identifier.
457  * @return   programming language id
458  */
language() const459 Uml::ProgrammingLanguage::Enum PythonWriter::language() const
460 {
461     return Uml::ProgrammingLanguage::Python;
462 }
463 
464 /**
465  * Reimplementation of method from class CodeGenerator
466  */
defaultDatatypes() const467 QStringList PythonWriter::defaultDatatypes() const
468 {
469     QStringList l;
470     l.append(QLatin1String("array"));
471     l.append(QLatin1String("bool"));
472     l.append(QLatin1String("tuple"));
473     l.append(QLatin1String("float"));
474     l.append(QLatin1String("int"));
475     l.append(QLatin1String("list"));
476     l.append(QLatin1String("long"));
477     l.append(QLatin1String("dict"));
478     l.append(QLatin1String("object"));
479     l.append(QLatin1String("set"));
480     l.append(QLatin1String("string"));
481     return l;
482 }
483 
484 /**
485  * Get list of reserved keywords.
486  * @return   the list of reserved keywords
487  */
reservedKeywords() const488 QStringList PythonWriter::reservedKeywords() const
489 {
490     static QStringList keywords;
491 
492     if (keywords.isEmpty()) {
493         for (int i = 0; reserved_words[i]; ++i) {
494             keywords.append(QLatin1String(reserved_words[i]));
495         }
496     }
497 
498     return keywords;
499 }
500 
501