|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the builtin documentation generator. |
|
8 |
|
9 The different parts of the module document are assembled from the parsed |
|
10 Python file. The appearance is determined by several templates defined within |
|
11 this module. |
|
12 """ |
|
13 |
|
14 import sys |
|
15 import re |
|
16 import contextlib |
|
17 |
|
18 from Utilities import html_uencode |
|
19 from Utilities.ModuleParser import RB_SOURCE, Function |
|
20 |
|
21 from . import TemplatesListsStyleCSS |
|
22 |
|
23 _signal = re.compile( |
|
24 r""" |
|
25 ^@signal [ \t]+ |
|
26 (?P<SignalName1> |
|
27 [a-zA-Z_] \w* [ \t]* \( [^)]* \) |
|
28 ) |
|
29 [ \t]* (?P<SignalDescription1> .*) |
|
30 | |
|
31 ^@signal [ \t]+ |
|
32 (?P<SignalName2> |
|
33 [a-zA-Z_] \w* |
|
34 ) |
|
35 [ \t]+ (?P<SignalDescription2> .*) |
|
36 """, re.VERBOSE | re.DOTALL | re.MULTILINE).search |
|
37 |
|
38 _event = re.compile( |
|
39 r""" |
|
40 ^@event [ \t]+ |
|
41 (?P<EventName1> |
|
42 [a-zA-Z_] \w* [ \t]* \( [^)]* \) |
|
43 ) |
|
44 [ \t]* (?P<EventDescription1> .*) |
|
45 | |
|
46 ^@event [ \t]+ |
|
47 (?P<EventName2> |
|
48 [a-zA-Z_] \w* |
|
49 ) |
|
50 [ \t]+ (?P<EventDescription2> .*) |
|
51 """, re.VERBOSE | re.DOTALL | re.MULTILINE).search |
|
52 |
|
53 |
|
54 class TagError(Exception): |
|
55 """ |
|
56 Exception class raised, if an invalid documentation tag was found. |
|
57 """ |
|
58 pass |
|
59 |
|
60 |
|
61 class ModuleDocument: |
|
62 """ |
|
63 Class implementing the builtin documentation generator. |
|
64 """ |
|
65 def __init__(self, module): |
|
66 """ |
|
67 Constructor |
|
68 |
|
69 @param module information of the parsed Python file |
|
70 @type str |
|
71 """ |
|
72 self.module = module |
|
73 self.empty = True |
|
74 |
|
75 self.keywords = [] |
|
76 # list of tuples containing the name (string) and |
|
77 # the ref (string). The ref is without the filename part. |
|
78 self.generated = False |
|
79 |
|
80 def isEmpty(self): |
|
81 """ |
|
82 Public method to determine, if the module contains any classes or |
|
83 functions. |
|
84 |
|
85 @return Flag indicating an empty module (i.e. __init__.py without |
|
86 any contents) |
|
87 """ |
|
88 return self.empty |
|
89 |
|
90 def name(self): |
|
91 """ |
|
92 Public method used to get the module name. |
|
93 |
|
94 @return The name of the module. (string) |
|
95 """ |
|
96 return self.module.name |
|
97 |
|
98 def description(self): |
|
99 """ |
|
100 Public method used to get the description of the module. |
|
101 |
|
102 @return The description of the module. (string) |
|
103 """ |
|
104 return self.__formatDescription(self.module.description) |
|
105 |
|
106 def shortDescription(self): |
|
107 """ |
|
108 Public method used to get the short description of the module. |
|
109 |
|
110 The short description is just the first line of the modules |
|
111 description. |
|
112 |
|
113 @return The short description of the module. (string) |
|
114 """ |
|
115 return self.__getShortDescription(self.module.description) |
|
116 |
|
117 def genDocument(self): |
|
118 """ |
|
119 Public method to generate the source code documentation. |
|
120 |
|
121 @return The source code documentation. (string) |
|
122 """ |
|
123 doc = ( |
|
124 TemplatesListsStyleCSS.headerTemplate.format( |
|
125 **{'Title': self.module.name} |
|
126 ) + |
|
127 self.__genModuleSection() + |
|
128 TemplatesListsStyleCSS.footerTemplate |
|
129 ) |
|
130 self.generated = True |
|
131 return doc |
|
132 |
|
133 def __genModuleSection(self): |
|
134 """ |
|
135 Private method to generate the body of the document. |
|
136 |
|
137 @return The body of the document. (string) |
|
138 """ |
|
139 globalsList = self.__genGlobalsListSection() |
|
140 classList = self.__genClassListSection() |
|
141 functionList = self.__genFunctionListSection() |
|
142 try: |
|
143 if self.module.type == RB_SOURCE: |
|
144 rbModulesList = self.__genRbModulesListSection() |
|
145 modBody = TemplatesListsStyleCSS.rbFileTemplate.format( |
|
146 **{'Module': self.module.name, |
|
147 'ModuleDescription': |
|
148 self.__formatDescription(self.module.description), |
|
149 'GlobalsList': globalsList, |
|
150 'ClassList': classList, |
|
151 'RbModulesList': rbModulesList, |
|
152 'FunctionList': functionList, |
|
153 }) |
|
154 else: |
|
155 modBody = TemplatesListsStyleCSS.moduleTemplate.format( |
|
156 **{'Module': self.module.name, |
|
157 'ModuleDescription': |
|
158 self.__formatDescription(self.module.description), |
|
159 'GlobalsList': globalsList, |
|
160 'ClassList': classList, |
|
161 'FunctionList': functionList, |
|
162 }) |
|
163 except TagError as e: |
|
164 sys.stderr.write( |
|
165 "Error processing {0}.\n".format(self.module.file)) |
|
166 sys.stderr.write( |
|
167 "Error in tags of description of module {0}.\n".format( |
|
168 self.module.name)) |
|
169 sys.stderr.write("{0}\n".format(e)) |
|
170 return "" |
|
171 |
|
172 classesSection = self.__genClassesSection() |
|
173 functionsSection = self.__genFunctionsSection() |
|
174 rbModulesSection = ( |
|
175 self.__genRbModulesSection() |
|
176 if self.module.type == RB_SOURCE else |
|
177 "" |
|
178 ) |
|
179 return "{0}{1}{2}{3}".format( |
|
180 modBody, classesSection, rbModulesSection, functionsSection) |
|
181 |
|
182 def __genListSection(self, names, sectionDict, kwSuffix=""): |
|
183 """ |
|
184 Private method to generate a list section of the document. |
|
185 |
|
186 @param names The names to appear in the list. (list of strings) |
|
187 @param sectionDict dictionary containing all relevant information |
|
188 (dict) |
|
189 @param kwSuffix suffix to be used for the QtHelp keywords (string) |
|
190 @return list section (string) |
|
191 """ |
|
192 lst = [] |
|
193 for name in names: |
|
194 lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( |
|
195 **{'Link': "{0}".format(name), |
|
196 'Name': sectionDict[name].name, |
|
197 'Description': |
|
198 self.__getShortDescription(sectionDict[name].description), |
|
199 'Deprecated': |
|
200 self.__checkDeprecated(sectionDict[name].description) and |
|
201 TemplatesListsStyleCSS.listEntryDeprecatedTemplate or "", |
|
202 })) |
|
203 n = ("{0} ({1})".format(name, kwSuffix) if kwSuffix |
|
204 else "{0}".format(name)) |
|
205 self.keywords.append((n, "#{0}".format(name))) |
|
206 return ''.join(lst) |
|
207 |
|
208 def __genGlobalsListSection(self, class_=None): |
|
209 """ |
|
210 Private method to generate the section listing all global attributes of |
|
211 the module. |
|
212 |
|
213 @param class_ reference to a class object (Class) |
|
214 @return The globals list section. (string) |
|
215 """ |
|
216 attrNames = [] |
|
217 scope = class_ if class_ is not None else self.module |
|
218 attrNames = sorted(attr for attr in scope.globals.keys() |
|
219 if not scope.globals[attr].isSignal) |
|
220 s = ( |
|
221 ''.join( |
|
222 [TemplatesListsStyleCSS.listEntrySimpleTemplate |
|
223 .format(**{'Name': name}) for name in attrNames]) |
|
224 if attrNames else |
|
225 TemplatesListsStyleCSS.listEntryNoneTemplate |
|
226 ) |
|
227 return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) |
|
228 |
|
229 def __genClassListSection(self): |
|
230 """ |
|
231 Private method to generate the section listing all classes of the |
|
232 module. |
|
233 |
|
234 @return The classes list section. (string) |
|
235 """ |
|
236 names = sorted(self.module.classes.keys()) |
|
237 if names: |
|
238 self.empty = False |
|
239 s = self.__genListSection(names, self.module.classes) |
|
240 else: |
|
241 s = TemplatesListsStyleCSS.listEntryNoneTemplate |
|
242 return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) |
|
243 |
|
244 def __genRbModulesListSection(self): |
|
245 """ |
|
246 Private method to generate the section listing all modules of the file |
|
247 (Ruby only). |
|
248 |
|
249 @return The modules list section. (string) |
|
250 """ |
|
251 names = sorted(self.module.modules.keys()) |
|
252 if names: |
|
253 self.empty = False |
|
254 s = self.__genListSection(names, self.module.modules) |
|
255 else: |
|
256 s = TemplatesListsStyleCSS.listEntryNoneTemplate |
|
257 return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) |
|
258 |
|
259 def __genFunctionListSection(self): |
|
260 """ |
|
261 Private method to generate the section listing all functions of the |
|
262 module. |
|
263 |
|
264 @return The functions list section. (string) |
|
265 """ |
|
266 names = sorted(self.module.functions.keys()) |
|
267 if names: |
|
268 self.empty = False |
|
269 s = self.__genListSection(names, self.module.functions) |
|
270 else: |
|
271 s = TemplatesListsStyleCSS.listEntryNoneTemplate |
|
272 return TemplatesListsStyleCSS.listTemplate.format(**{'Entries': s}) |
|
273 |
|
274 def __genClassesSection(self): |
|
275 """ |
|
276 Private method to generate the document section with details about |
|
277 classes. |
|
278 |
|
279 @return The classes details section. (string) |
|
280 """ |
|
281 classNames = sorted(self.module.classes.keys()) |
|
282 classes = [] |
|
283 for className in classNames: |
|
284 _class = self.module.classes[className] |
|
285 supers = _class.super |
|
286 supers = ', '.join(supers) if len(supers) > 0 else "None" |
|
287 |
|
288 globalsList = self.__genGlobalsListSection(_class) |
|
289 classMethList, classMethBodies = self.__genMethodSection( |
|
290 _class, className, Function.Class) |
|
291 methList, methBodies = self.__genMethodSection( |
|
292 _class, className, Function.General) |
|
293 staticMethList, staticMethBodies = self.__genMethodSection( |
|
294 _class, className, Function.Static) |
|
295 |
|
296 try: |
|
297 clsBody = TemplatesListsStyleCSS.classTemplate.format( |
|
298 **{'Anchor': className, |
|
299 'Class': _class.name, |
|
300 'ClassSuper': supers, |
|
301 'ClassDescription': |
|
302 self.__formatDescription(_class.description), |
|
303 'GlobalsList': globalsList, |
|
304 'ClassMethodList': classMethList, |
|
305 'MethodList': methList, |
|
306 'StaticMethodList': staticMethList, |
|
307 'MethodDetails': |
|
308 classMethBodies + methBodies + staticMethBodies, |
|
309 }) |
|
310 except TagError as e: |
|
311 sys.stderr.write( |
|
312 "Error processing {0}.\n".format(self.module.file)) |
|
313 sys.stderr.write( |
|
314 "Error in tags of description of class {0}.\n".format( |
|
315 className)) |
|
316 sys.stderr.write("{0}\n".format(e)) |
|
317 clsBody = "" |
|
318 |
|
319 classes.append(clsBody) |
|
320 |
|
321 return ''.join(classes) |
|
322 |
|
323 def __genMethodsListSection(self, names, sectionDict, className, clsName, |
|
324 includeInit=True): |
|
325 """ |
|
326 Private method to generate the methods list section of a class. |
|
327 |
|
328 @param names names to appear in the list (list of strings) |
|
329 @param sectionDict dictionary containing all relevant information |
|
330 (dict) |
|
331 @param className class name containing the names |
|
332 @param clsName visible class name containing the names |
|
333 @param includeInit flag indicating to include the __init__ method |
|
334 (boolean) |
|
335 @return methods list section (string) |
|
336 """ |
|
337 lst = [] |
|
338 if includeInit: |
|
339 with contextlib.suppress(KeyError): |
|
340 lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( |
|
341 **{'Link': "{0}.{1}".format(className, '__init__'), |
|
342 'Name': clsName, |
|
343 'Description': self.__getShortDescription( |
|
344 sectionDict['__init__'].description), |
|
345 'Deprecated': self.__checkDeprecated( |
|
346 sectionDict['__init__'].description) and |
|
347 TemplatesListsStyleCSS.listEntryDeprecatedTemplate or |
|
348 "", |
|
349 })) |
|
350 self.keywords.append( |
|
351 ("{0} (Constructor)".format(className), |
|
352 "#{0}.{1}".format(className, '__init__'))) |
|
353 |
|
354 for name in names: |
|
355 lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( |
|
356 **{'Link': "{0}.{1}".format(className, name), |
|
357 'Name': sectionDict[name].name, |
|
358 'Description': |
|
359 self.__getShortDescription(sectionDict[name].description), |
|
360 'Deprecated': |
|
361 self.__checkDeprecated(sectionDict[name].description) and |
|
362 TemplatesListsStyleCSS.listEntryDeprecatedTemplate or "", |
|
363 })) |
|
364 self.keywords.append(("{0}.{1}".format(className, name), |
|
365 "#{0}.{1}".format(className, name))) |
|
366 return ''.join(lst) |
|
367 |
|
368 def __genMethodSection(self, obj, className, modifierFilter): |
|
369 """ |
|
370 Private method to generate the method details section. |
|
371 |
|
372 @param obj reference to the object being formatted |
|
373 @param className name of the class containing the method (string) |
|
374 @param modifierFilter filter value designating the method types |
|
375 @return method list and method details section (tuple of two string) |
|
376 """ |
|
377 methList = [] |
|
378 methBodies = [] |
|
379 methods = sorted(k for k in obj.methods.keys() |
|
380 if obj.methods[k].modifier == modifierFilter) |
|
381 if '__init__' in methods: |
|
382 methods.remove('__init__') |
|
383 try: |
|
384 methBody = TemplatesListsStyleCSS.constructorTemplate.format( |
|
385 **{'Anchor': className, |
|
386 'Class': obj.name, |
|
387 'Method': '__init__', |
|
388 'MethodDescription': |
|
389 self.__formatDescription( |
|
390 obj.methods['__init__'].description), |
|
391 'Params': |
|
392 ', '.join(obj.methods['__init__'].parameters[1:]), |
|
393 }) |
|
394 except TagError as e: |
|
395 sys.stderr.write( |
|
396 "Error processing {0}.\n".format(self.module.file)) |
|
397 sys.stderr.write( |
|
398 "Error in tags of description of method {0}.{1}.\n".format( |
|
399 className, '__init__')) |
|
400 sys.stderr.write("{0}\n".format(e)) |
|
401 methBody = "" |
|
402 methBodies.append(methBody) |
|
403 |
|
404 if modifierFilter == Function.Class: |
|
405 methodClassifier = " (class method)" |
|
406 elif modifierFilter == Function.Static: |
|
407 methodClassifier = " (static)" |
|
408 else: |
|
409 methodClassifier = "" |
|
410 for method in methods: |
|
411 try: |
|
412 methBody = TemplatesListsStyleCSS.methodTemplate.format( |
|
413 **{'Anchor': className, |
|
414 'Class': obj.name, |
|
415 'Method': obj.methods[method].name, |
|
416 'MethodClassifier': methodClassifier, |
|
417 'MethodDescription': |
|
418 self.__formatDescription( |
|
419 obj.methods[method].description), |
|
420 'Params': ', '.join(obj.methods[method].parameters[1:]), |
|
421 }) |
|
422 except TagError as e: |
|
423 sys.stderr.write( |
|
424 "Error processing {0}.\n".format(self.module.file)) |
|
425 sys.stderr.write( |
|
426 "Error in tags of description of method {0}.{1}.\n".format( |
|
427 className, method)) |
|
428 sys.stderr.write("{0}\n".format(e)) |
|
429 methBody = "" |
|
430 methBodies.append(methBody) |
|
431 |
|
432 methList = self.__genMethodsListSection( |
|
433 methods, obj.methods, className, obj.name, |
|
434 includeInit=modifierFilter == Function.General) |
|
435 |
|
436 if not methList: |
|
437 methList = TemplatesListsStyleCSS.listEntryNoneTemplate |
|
438 return (TemplatesListsStyleCSS.listTemplate |
|
439 .format(**{'Entries': methList}), ''.join(methBodies)) |
|
440 |
|
441 def __genRbModulesSection(self): |
|
442 """ |
|
443 Private method to generate the document section with details about |
|
444 Ruby modules. |
|
445 |
|
446 @return The Ruby modules details section. (string) |
|
447 """ |
|
448 rbModulesNames = sorted(self.module.modules.keys()) |
|
449 rbModules = [] |
|
450 for rbModuleName in rbModulesNames: |
|
451 rbModule = self.module.modules[rbModuleName] |
|
452 globalsList = self.__genGlobalsListSection(rbModule) |
|
453 methList, methBodies = self.__genMethodSection( |
|
454 rbModule, rbModuleName, Function.General) |
|
455 classList, classBodies = self.__genRbModulesClassesSection( |
|
456 rbModule, rbModuleName) |
|
457 |
|
458 try: |
|
459 rbmBody = TemplatesListsStyleCSS.rbModuleTemplate.format( |
|
460 **{'Anchor': rbModuleName, |
|
461 'Module': rbModule.name, |
|
462 'ModuleDescription': |
|
463 self.__formatDescription(rbModule.description), |
|
464 'GlobalsList': globalsList, |
|
465 'ClassesList': classList, |
|
466 'ClassesDetails': classBodies, |
|
467 'FunctionsList': methList, |
|
468 'FunctionsDetails': methBodies, |
|
469 }) |
|
470 except TagError as e: |
|
471 sys.stderr.write( |
|
472 "Error processing {0}.\n".format(self.module.file)) |
|
473 sys.stderr.write( |
|
474 "Error in tags of description of Ruby module {0}.\n" |
|
475 .format(rbModuleName)) |
|
476 sys.stderr.write("{0}\n".format(e)) |
|
477 rbmBody = "" |
|
478 |
|
479 rbModules.append(rbmBody) |
|
480 |
|
481 return ''.join(rbModules) |
|
482 |
|
483 def __genRbModulesClassesSection(self, obj, modName): |
|
484 """ |
|
485 Private method to generate the Ruby module classes details section. |
|
486 |
|
487 @param obj Reference to the object being formatted. |
|
488 @param modName Name of the Ruby module containing the classes. (string) |
|
489 @return The classes list and classes details section. |
|
490 (tuple of two string) |
|
491 """ |
|
492 classNames = sorted(obj.classes.keys()) |
|
493 classes = [] |
|
494 for className in classNames: |
|
495 _class = obj.classes[className] |
|
496 supers = _class.super |
|
497 supers = ', '.join(supers) if len(supers) > 0 else "None" |
|
498 |
|
499 methList, methBodies = self.__genMethodSection( |
|
500 _class, className, Function.General) |
|
501 |
|
502 try: |
|
503 clsBody = TemplatesListsStyleCSS.rbModulesClassTemplate.format( |
|
504 **{'Anchor': className, |
|
505 'Class': _class.name, |
|
506 'ClassSuper': supers, |
|
507 'ClassDescription': |
|
508 self.__formatDescription(_class.description), |
|
509 'MethodList': methList, |
|
510 'MethodDetails': methBodies, |
|
511 }) |
|
512 except TagError as e: |
|
513 sys.stderr.write( |
|
514 "Error processing {0}.\n".format(self.module.file)) |
|
515 sys.stderr.write( |
|
516 "Error in tags of description of class {0}.\n".format( |
|
517 className)) |
|
518 sys.stderr.write("{0}\n".format(e)) |
|
519 clsBody = "" |
|
520 |
|
521 classes.append(clsBody) |
|
522 |
|
523 classesList = self.__genRbModulesClassesListSection( |
|
524 classNames, obj.classes, modName) |
|
525 |
|
526 if not classesList: |
|
527 classesList = TemplatesListsStyleCSS.listEntryNoneTemplate |
|
528 return (TemplatesListsStyleCSS.listTemplate |
|
529 .format(**{'Entries': classesList}), ''.join(classes)) |
|
530 |
|
531 def __genRbModulesClassesListSection(self, names, sectionDict, moduleName): |
|
532 """ |
|
533 Private method to generate the classes list section of a Ruby module. |
|
534 |
|
535 @param names The names to appear in the list. (list of strings) |
|
536 @param sectionDict dictionary containing all relevant information |
|
537 (dict) |
|
538 @param moduleName name of the Ruby module containing the classes |
|
539 (string) |
|
540 @return The list section. (string) |
|
541 """ |
|
542 lst = [] |
|
543 for name in names: |
|
544 lst.append(TemplatesListsStyleCSS.listEntryTemplate.format( |
|
545 **{'Link': "{0}.{1}".format(moduleName, name), |
|
546 'Name': sectionDict[name].name, |
|
547 'Description': |
|
548 self.__getShortDescription(sectionDict[name].description), |
|
549 'Deprecated': |
|
550 self.__checkDeprecated(sectionDict[name].description) and |
|
551 TemplatesListsStyleCSS.listEntryDeprecatedTemplate or "", |
|
552 })) |
|
553 self.keywords.append(("{0}.{1}".format(moduleName, name), |
|
554 "#{0}.{1}".format(moduleName, name))) |
|
555 return ''.join(lst) |
|
556 |
|
557 def __genFunctionsSection(self): |
|
558 """ |
|
559 Private method to generate the document section with details about |
|
560 functions. |
|
561 |
|
562 @return The functions details section. (string) |
|
563 """ |
|
564 funcBodies = [] |
|
565 funcNames = sorted(self.module.functions.keys()) |
|
566 for funcName in funcNames: |
|
567 try: |
|
568 funcBody = TemplatesListsStyleCSS.functionTemplate.format( |
|
569 **{'Anchor': funcName, |
|
570 'Function': self.module.functions[funcName].name, |
|
571 'FunctionDescription': self.__formatDescription( |
|
572 self.module.functions[funcName].description), |
|
573 'Params': |
|
574 ', '.join(self.module.functions[funcName].parameters), |
|
575 }) |
|
576 except TagError as e: |
|
577 sys.stderr.write( |
|
578 "Error processing {0}.\n".format(self.module.file)) |
|
579 sys.stderr.write( |
|
580 "Error in tags of description of function {0}.\n".format( |
|
581 funcName)) |
|
582 sys.stderr.write("{0}\n".format(e)) |
|
583 funcBody = "" |
|
584 |
|
585 funcBodies.append(funcBody) |
|
586 |
|
587 return ''.join(funcBodies) |
|
588 |
|
589 def __getShortDescription(self, desc): |
|
590 """ |
|
591 Private method to determine the short description of an object. |
|
592 |
|
593 The short description is just the first non empty line of the |
|
594 documentation string. |
|
595 |
|
596 @param desc The documentation string. (string) |
|
597 @return The short description. (string) |
|
598 """ |
|
599 dlist = desc.splitlines() |
|
600 sdlist = [] |
|
601 descfound = 0 |
|
602 for desc in dlist: |
|
603 desc = desc.strip() |
|
604 if desc: |
|
605 descfound = 1 |
|
606 dotpos = desc.find('.') |
|
607 if dotpos == -1: |
|
608 sdlist.append(desc.strip()) |
|
609 else: |
|
610 while ( |
|
611 dotpos + 1 < len(desc) and |
|
612 not desc[dotpos + 1].isspace() |
|
613 ): |
|
614 # don't recognize '.' inside a number or word as |
|
615 # stop condition |
|
616 dotpos = desc.find('.', dotpos + 1) |
|
617 if dotpos == -1: |
|
618 break |
|
619 if dotpos == -1: |
|
620 sdlist.append(desc.strip()) |
|
621 else: |
|
622 sdlist.append(desc[:dotpos + 1].strip()) |
|
623 break # break if a '.' is found |
|
624 else: |
|
625 if descfound: |
|
626 break # break if an empty line is found |
|
627 if sdlist: |
|
628 return html_uencode(' '.join(sdlist)) |
|
629 else: |
|
630 return '' |
|
631 |
|
632 def __checkDeprecated(self, descr): |
|
633 """ |
|
634 Private method to check, if the object to be documented contains a |
|
635 deprecated flag. |
|
636 |
|
637 @param descr documentation string (string) |
|
638 @return flag indicating the deprecation status (boolean) |
|
639 """ |
|
640 dlist = descr.splitlines() |
|
641 for desc in dlist: |
|
642 desc = desc.strip() |
|
643 if desc.startswith("@deprecated"): |
|
644 return True |
|
645 return False |
|
646 |
|
647 def __genParagraphs(self, lines): |
|
648 """ |
|
649 Private method to assemble the descriptive paragraphs of a docstring. |
|
650 |
|
651 A paragraph is made up of a number of consecutive lines without |
|
652 an intermediate empty line. Empty lines are treated as a paragraph |
|
653 delimiter. |
|
654 |
|
655 @param lines A list of individual lines. (list of strings) |
|
656 @return Ready formatted paragraphs. (string) |
|
657 """ |
|
658 lst = [] |
|
659 linelist = [] |
|
660 for line in lines: |
|
661 if line.strip(): |
|
662 if line == '.': |
|
663 linelist.append("") |
|
664 else: |
|
665 linelist.append(html_uencode(line)) |
|
666 else: |
|
667 lst.append(TemplatesListsStyleCSS.paragraphTemplate.format( |
|
668 **{'Lines': '\n'.join(linelist)})) |
|
669 linelist = [] |
|
670 if linelist: |
|
671 lst.append(TemplatesListsStyleCSS.paragraphTemplate.format( |
|
672 **{'Lines': '\n'.join(linelist)})) |
|
673 return ''.join(lst) |
|
674 |
|
675 def __genDescriptionListSection(self, dictionary, template): |
|
676 """ |
|
677 Private method to generate the list section of a description. |
|
678 |
|
679 @param dictionary Dictionary containing the info for the |
|
680 list section. |
|
681 @param template The template to be used for the list. (string) |
|
682 @return The list section. (string) |
|
683 """ |
|
684 lst = [] |
|
685 keys = sorted(dictionary.keys()) |
|
686 for key in keys: |
|
687 lst.append(template.format( |
|
688 **{'Name': key, |
|
689 'Description': html_uencode('\n'.join(dictionary[key])), |
|
690 })) |
|
691 return ''.join(lst) |
|
692 |
|
693 def __genParamDescriptionListSection(self, _list): |
|
694 """ |
|
695 Private method to generate the list section of a description. |
|
696 |
|
697 @param _list list containing the info for the parameter description |
|
698 list section (list of lists with three elements) |
|
699 @return formatted list section (string) |
|
700 """ |
|
701 lst = [] |
|
702 for name, type_, lines in _list: |
|
703 if type_: |
|
704 lst.append( |
|
705 TemplatesListsStyleCSS.parameterTypesListEntryTemplate |
|
706 .format( |
|
707 **{'Name': name, |
|
708 'Type': type_, |
|
709 'Description': html_uencode('\n'.join(lines)), |
|
710 } |
|
711 ) |
|
712 ) |
|
713 else: |
|
714 lst.append( |
|
715 TemplatesListsStyleCSS.parametersListEntryTemplate.format( |
|
716 **{'Name': name, |
|
717 'Description': html_uencode('\n'.join(lines)), |
|
718 } |
|
719 ) |
|
720 ) |
|
721 return ''.join(lst) |
|
722 |
|
723 def __formatCrossReferenceEntry(self, entry): |
|
724 """ |
|
725 Private method to format a cross reference entry. |
|
726 |
|
727 This cross reference entry looks like "package.module#member label". |
|
728 |
|
729 @param entry the entry to be formatted (string) |
|
730 @return formatted entry (string) |
|
731 """ |
|
732 if entry.startswith('"'): |
|
733 return entry |
|
734 elif entry.startswith('<'): |
|
735 entry = entry[3:] |
|
736 else: |
|
737 try: |
|
738 reference, label = entry.split(None, 1) |
|
739 except ValueError: |
|
740 reference = entry |
|
741 label = entry |
|
742 try: |
|
743 path, anchor = reference.split('#', 1) |
|
744 except ValueError: |
|
745 path = reference |
|
746 anchor = '' |
|
747 reference = path and "{0}.html".format(path) or '' |
|
748 if anchor: |
|
749 reference = "{0}#{1}".format(reference, anchor) |
|
750 entry = 'href="{0}">{1}</a>'.format(reference, label) |
|
751 |
|
752 return TemplatesListsStyleCSS.seeLinkTemplate.format(**{'Link': entry}) |
|
753 |
|
754 def __genSeeListSection(self, _list, template): |
|
755 """ |
|
756 Private method to generate the "see also" list section of a |
|
757 description. |
|
758 |
|
759 @param _list List containing the info for the section. |
|
760 @param template The template to be used for the list. (string) |
|
761 @return The list section. (string) |
|
762 """ |
|
763 lst = [] |
|
764 for seeEntry in _list: |
|
765 seeEntryString = ''.join(seeEntry) |
|
766 lst.append(template.format( |
|
767 **{'Link': html_uencode(self.__formatCrossReferenceEntry( |
|
768 seeEntryString)), |
|
769 })) |
|
770 return '\n'.join(lst) |
|
771 |
|
772 def __processInlineTags(self, desc): |
|
773 """ |
|
774 Private method to process inline tags. |
|
775 |
|
776 @param desc One line of the description (string) |
|
777 @return processed line with inline tags expanded (string) |
|
778 @exception TagError raised to indicate an invalid tag |
|
779 """ |
|
780 start = desc.find('{@') |
|
781 while start != -1: |
|
782 stop = desc.find('}', start + 2) |
|
783 if stop == -1: |
|
784 raise TagError("Unterminated inline tag.\n{0}".format(desc)) |
|
785 |
|
786 tagText = desc[start + 1:stop] |
|
787 if tagText.startswith('@link'): |
|
788 parts = tagText.split(None, 1) |
|
789 if len(parts) < 2: |
|
790 raise TagError( |
|
791 "Wrong format in inline tag {0}.\n{1}".format( |
|
792 parts[0], desc)) |
|
793 |
|
794 formattedTag = self.__formatCrossReferenceEntry(parts[1]) |
|
795 desc = desc.replace("{{{0}}}".format(tagText), formattedTag) |
|
796 else: |
|
797 tag = tagText.split(None, 1)[0] |
|
798 raise TagError( |
|
799 "Unknown inline tag encountered, {0}.\n{1}".format( |
|
800 tag, desc)) |
|
801 |
|
802 start = desc.find('{@') |
|
803 |
|
804 return desc |
|
805 |
|
806 def __formatDescription(self, descr): |
|
807 """ |
|
808 Private method to format the contents of the documentation string. |
|
809 |
|
810 @param descr The contents of the documentation string. (string) |
|
811 @exception TagError A tag doesn't have the correct number |
|
812 of arguments. |
|
813 @return The formatted contents of the documentation string. (string) |
|
814 """ |
|
815 if not descr: |
|
816 return "" |
|
817 |
|
818 paragraphs = [] |
|
819 paramList = [] |
|
820 returns = [] |
|
821 returnTypes = [] |
|
822 yields = [] |
|
823 yieldTypes = [] |
|
824 exceptionDict = {} |
|
825 signalDict = {} |
|
826 eventDict = {} |
|
827 deprecated = [] |
|
828 authorInfo = [] |
|
829 sinceInfo = [] |
|
830 seeList = [] |
|
831 lastItem = paragraphs |
|
832 inTagSection = False |
|
833 |
|
834 dlist = descr.splitlines() |
|
835 while dlist and not dlist[0]: |
|
836 del dlist[0] |
|
837 lastTag = "" |
|
838 buffer = "" |
|
839 for ditem in dlist: |
|
840 ditem = self.__processInlineTags(ditem) |
|
841 desc = ditem.strip() |
|
842 if buffer: |
|
843 if desc.startswith("@"): |
|
844 buffer = "" |
|
845 raise TagError( |
|
846 "Wrong format in {0} line.\n".format(lastTag)) |
|
847 else: |
|
848 desc = buffer + desc |
|
849 if desc: |
|
850 if desc.startswith(("@param", "@keyparam")): |
|
851 inTagSection = True |
|
852 parts = desc.split(None, 2) |
|
853 lastTag = parts[0] |
|
854 if len(parts) < 2: |
|
855 raise TagError( |
|
856 "Wrong format in {0} line.\n".format(parts[0])) |
|
857 paramName = parts[1] |
|
858 if parts[0] == "@keyparam": |
|
859 paramName += '=' |
|
860 try: |
|
861 paramList.append([paramName, "", [parts[2]]]) |
|
862 except IndexError: |
|
863 paramList.append([paramName, "", []]) |
|
864 lastItem = paramList[-1][2] |
|
865 elif desc.startswith("@type"): |
|
866 parts = desc.split(None, 1) |
|
867 if lastTag not in ["@param", "@keyparam"]: |
|
868 raise TagError( |
|
869 "{0} line must be preceded by a parameter line\n" |
|
870 .format(parts[0])) |
|
871 inTagSection = True |
|
872 lastTag = parts[0] |
|
873 if len(parts) < 2: |
|
874 raise TagError( |
|
875 "Wrong format in {0} line.\n".format(parts[0])) |
|
876 paramList[-1][1] = parts[1] |
|
877 elif desc.startswith("@ptype"): |
|
878 inTagSection = True |
|
879 parts = desc.split(None, 2) |
|
880 lastTag = parts[0] |
|
881 if len(parts) < 3: |
|
882 raise TagError( |
|
883 "Wrong format in {0} line.\n".format(parts[0])) |
|
884 param, type_ = parts[1:] |
|
885 for index in range(len(paramList)): |
|
886 if paramList[index][0] == param: |
|
887 paramList[index][1] = type_ |
|
888 break |
|
889 else: |
|
890 raise TagError( |
|
891 "Unknow parameter name '{0}' in {1} line.\n" |
|
892 .format(param, parts[0])) |
|
893 elif desc.startswith(("@return", "@ireturn")): |
|
894 inTagSection = True |
|
895 parts = desc.split(None, 1) |
|
896 lastTag = parts[0] |
|
897 if len(parts) < 2: |
|
898 raise TagError( |
|
899 "Wrong format in {0} line.\n".format(parts[0])) |
|
900 returns = [parts[1]] |
|
901 lastItem = returns |
|
902 elif desc.startswith("@rtype"): |
|
903 parts = desc.split(None, 1) |
|
904 if lastTag not in ["@return", "@ireturn"]: |
|
905 raise TagError( |
|
906 "{0} line must be preceded by a @return line\n" |
|
907 .format(parts[0])) |
|
908 inTagSection = True |
|
909 lastTag = parts[0] |
|
910 if len(parts) < 2: |
|
911 raise TagError( |
|
912 "Wrong format in {0} line.\n".format(parts[0])) |
|
913 returnTypes = [parts[1]] |
|
914 lastItem = returnTypes |
|
915 elif desc.startswith("@yield"): |
|
916 inTagSection = True |
|
917 parts = desc.split(None, 1) |
|
918 lastTag = parts[0] |
|
919 if len(parts) < 2: |
|
920 raise TagError( |
|
921 "Wrong format in {0} line.\n".format(parts[0])) |
|
922 yields = [parts[1]] |
|
923 lastItem = yields |
|
924 elif desc.startswith("@ytype"): |
|
925 parts = desc.split(None, 1) |
|
926 if lastTag != "@yield": |
|
927 raise TagError( |
|
928 "{0} line must be preceded by a @yield line\n" |
|
929 .format(parts[0])) |
|
930 inTagSection = True |
|
931 lastTag = parts[0] |
|
932 if len(parts) < 2: |
|
933 raise TagError( |
|
934 "Wrong format in {0} line.\n".format(parts[0])) |
|
935 yieldTypes = [parts[1]] |
|
936 lastItem = yieldTypes |
|
937 elif desc.startswith(("@exception", "@throws", "@raise")): |
|
938 inTagSection = True |
|
939 parts = desc.split(None, 2) |
|
940 lastTag = parts[0] |
|
941 if len(parts) < 2: |
|
942 raise TagError( |
|
943 "Wrong format in {0} line.\n".format(parts[0])) |
|
944 excName = parts[1] |
|
945 try: |
|
946 exceptionDict[excName] = [parts[2]] |
|
947 except IndexError: |
|
948 exceptionDict[excName] = [] |
|
949 lastItem = exceptionDict[excName] |
|
950 elif desc.startswith("@signal"): |
|
951 inTagSection = True |
|
952 lastTag = desc.split(None, 1)[0] |
|
953 m = _signal(desc, 0) |
|
954 if m is None: |
|
955 buffer = desc |
|
956 else: |
|
957 buffer = "" |
|
958 signalName = ( |
|
959 m.group("SignalName1") or m.group("SignalName2") |
|
960 ) |
|
961 signalDesc = ( |
|
962 m.group("SignalDescription1") or |
|
963 m.group("SignalDescription2") |
|
964 ) |
|
965 signalDict[signalName] = [] |
|
966 if signalDesc is not None: |
|
967 signalDict[signalName].append(signalDesc) |
|
968 lastItem = signalDict[signalName] |
|
969 elif desc.startswith("@event"): |
|
970 inTagSection = True |
|
971 lastTag = desc.split(None, 1)[0] |
|
972 m = _event(desc, 0) |
|
973 if m is None: |
|
974 buffer = desc |
|
975 else: |
|
976 buffer = "" |
|
977 eventName = ( |
|
978 m.group("EventName1") or m.group("EventName2") |
|
979 ) |
|
980 eventDesc = ( |
|
981 m.group("EventDescription1") or |
|
982 m.group("EventDescription2") |
|
983 ) |
|
984 eventDict[eventName] = [] |
|
985 if eventDesc is not None: |
|
986 eventDict[eventName].append(eventDesc) |
|
987 lastItem = eventDict[eventName] |
|
988 elif desc.startswith("@deprecated"): |
|
989 inTagSection = True |
|
990 parts = desc.split(None, 1) |
|
991 lastTag = parts[0] |
|
992 if len(parts) < 2: |
|
993 raise TagError( |
|
994 "Wrong format in {0} line.\n".format(parts[0])) |
|
995 deprecated = [parts[1]] |
|
996 lastItem = deprecated |
|
997 elif desc.startswith("@author"): |
|
998 inTagSection = True |
|
999 parts = desc.split(None, 1) |
|
1000 lastTag = parts[0] |
|
1001 if len(parts) < 2: |
|
1002 raise TagError( |
|
1003 "Wrong format in {0} line.\n".format(parts[0])) |
|
1004 authorInfo = [parts[1]] |
|
1005 lastItem = authorInfo |
|
1006 elif desc.startswith("@since"): |
|
1007 inTagSection = True |
|
1008 parts = desc.split(None, 1) |
|
1009 lastTag = parts[0] |
|
1010 if len(parts) < 2: |
|
1011 raise TagError( |
|
1012 "Wrong format in {0} line.\n".format(parts[0])) |
|
1013 sinceInfo = [parts[1]] |
|
1014 lastItem = sinceInfo |
|
1015 elif desc.startswith("@see"): |
|
1016 inTagSection = True |
|
1017 parts = desc.split(None, 1) |
|
1018 lastTag = parts[0] |
|
1019 if len(parts) < 2: |
|
1020 raise TagError( |
|
1021 "Wrong format in {0} line.\n".format(parts[0])) |
|
1022 seeList.append([parts[1]]) |
|
1023 lastItem = seeList[-1] |
|
1024 elif desc.startswith("@@"): |
|
1025 lastItem.append(desc[1:]) |
|
1026 elif desc.startswith("@"): |
|
1027 tag = desc.split(None, 1)[0] |
|
1028 raise TagError( |
|
1029 "Unknown tag encountered, {0}.\n".format(tag)) |
|
1030 else: |
|
1031 lastItem.append(ditem) |
|
1032 elif not inTagSection: |
|
1033 lastItem.append(ditem) |
|
1034 |
|
1035 description = self.__genParagraphs(paragraphs) if paragraphs else "" |
|
1036 |
|
1037 parameterSect = ( |
|
1038 TemplatesListsStyleCSS.parametersListTemplate.format( |
|
1039 **{'Parameters': self.__genParamDescriptionListSection( |
|
1040 paramList)}) |
|
1041 if paramList else |
|
1042 "" |
|
1043 ) |
|
1044 |
|
1045 returnSect = ( |
|
1046 TemplatesListsStyleCSS.returnsTemplate.format( |
|
1047 html_uencode('\n'.join(returns))) |
|
1048 if returns else |
|
1049 "" |
|
1050 ) |
|
1051 |
|
1052 returnTypesSect = ( |
|
1053 TemplatesListsStyleCSS.returnTypesTemplate.format( |
|
1054 html_uencode('\n'.join(returnTypes))) |
|
1055 if returnTypes else |
|
1056 "" |
|
1057 ) |
|
1058 |
|
1059 yieldSect = ( |
|
1060 TemplatesListsStyleCSS.yieldsTemplate.format( |
|
1061 html_uencode('\n'.join(yields))) |
|
1062 if yields else |
|
1063 "" |
|
1064 ) |
|
1065 |
|
1066 yieldTypesSect = ( |
|
1067 TemplatesListsStyleCSS.yieldTypesTemplate.format( |
|
1068 html_uencode('\n'.join(yieldTypes))) |
|
1069 if yieldTypes else |
|
1070 "" |
|
1071 ) |
|
1072 |
|
1073 exceptionSect = ( |
|
1074 TemplatesListsStyleCSS.exceptionsListTemplate.format( |
|
1075 **{'Exceptions': self.__genDescriptionListSection( |
|
1076 exceptionDict, |
|
1077 TemplatesListsStyleCSS.exceptionsListEntryTemplate)} |
|
1078 ) |
|
1079 if exceptionDict else |
|
1080 "" |
|
1081 ) |
|
1082 |
|
1083 signalSect = ( |
|
1084 TemplatesListsStyleCSS.signalsListTemplate.format( |
|
1085 **{'Signals': self.__genDescriptionListSection( |
|
1086 signalDict, |
|
1087 TemplatesListsStyleCSS.signalsListEntryTemplate)} |
|
1088 ) |
|
1089 if signalDict else |
|
1090 "" |
|
1091 ) |
|
1092 |
|
1093 eventSect = ( |
|
1094 TemplatesListsStyleCSS.eventsListTemplate.format( |
|
1095 **{'Events': self.__genDescriptionListSection( |
|
1096 eventDict, |
|
1097 TemplatesListsStyleCSS.eventsListEntryTemplate)} |
|
1098 ) |
|
1099 if eventDict else |
|
1100 "" |
|
1101 ) |
|
1102 |
|
1103 deprecatedSect = ( |
|
1104 TemplatesListsStyleCSS.deprecatedTemplate.format( |
|
1105 **{'Lines': html_uencode('\n'.join(deprecated))}) |
|
1106 if deprecated else |
|
1107 "" |
|
1108 ) |
|
1109 |
|
1110 authorInfoSect = ( |
|
1111 TemplatesListsStyleCSS.authorInfoTemplate.format( |
|
1112 **{'Authors': html_uencode('\n'.join(authorInfo))}) |
|
1113 if authorInfo else |
|
1114 "" |
|
1115 ) |
|
1116 |
|
1117 sinceInfoSect = ( |
|
1118 TemplatesListsStyleCSS.sinceInfoTemplate.format( |
|
1119 **{'Info': html_uencode(sinceInfo[0])}) |
|
1120 if sinceInfo else |
|
1121 "" |
|
1122 ) |
|
1123 |
|
1124 seeSect = ( |
|
1125 TemplatesListsStyleCSS.seeListTemplate.format( |
|
1126 **{'Links': self.__genSeeListSection( |
|
1127 seeList, TemplatesListsStyleCSS.seeListEntryTemplate)}) |
|
1128 if seeList else |
|
1129 '' |
|
1130 ) |
|
1131 |
|
1132 return "".join([ |
|
1133 deprecatedSect, description, parameterSect, returnSect, |
|
1134 returnTypesSect, yieldSect, yieldTypesSect, exceptionSect, |
|
1135 signalSect, eventSect, authorInfoSect, seeSect, sinceInfoSect, |
|
1136 ]) |
|
1137 |
|
1138 def getQtHelpKeywords(self): |
|
1139 """ |
|
1140 Public method to retrieve the parts for the QtHelp keywords section. |
|
1141 |
|
1142 @return list of tuples containing the name (string) and the ref |
|
1143 (string). The ref is without the filename part. |
|
1144 """ |
|
1145 if not self.generated: |
|
1146 self.genDocument() |
|
1147 |
|
1148 return self.keywords |