eric7/DocumentationTools/ModuleDocumentor.py

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

eric ide

mercurial