src/eric7/DocumentationTools/ModuleDocumentor.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
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

eric ide

mercurial