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