1 """Script to generate reports on translator classes from Doxygen sources.
3 The main purpose of the script is to extract the information from sources
4 related to internationalization (the translator classes). It uses the
5 information to generate documentation (language.doc,
6 translator_report.txt) from templates (language.tpl, maintainers.txt).
8 Simply run the script without parameters to get the reports and
9 documentation for all supported languages. If you want to generate the
10 translator report only for some languages, pass their codes as arguments
11 to the script. In that case, the language.doc will not be generated.
14 python translator.py en nl cz
16 Originally, the script was written in Perl and was known as translator.pl.
17 The last Perl version was dated 2002/05/21 (plus some later corrections)
19 Petr Prikryl (prikryl at atlas dot cz)
23 2002/05/21 - This was the last Perl version.
24 2003/05/16 - List of language marks can be passed as arguments.
25 2004/01/24 - Total reimplementation started: classes TrManager, and Transl.
26 2004/02/05 - First version that produces translator report. No language.doc yet.
27 2004/02/10 - First fully functional version that generates both the translator
28 report and the documentation. It is a bit slower than the
29 Perl version, but is much less tricky and much more flexible.
30 It also solves some problems that were not solved by the Perl
31 version. The translator report content should be more useful
33 2004/02/11 - Some tuning-up to provide more useful information.
34 2004/04/16 - Added new tokens to the tokenizer (to remove some warnings).
35 2004/05/25 - Added from __future__ import generators not to force Python 2.3.
36 2004/06/03 - Removed dependency on textwrap module.
37 2004/07/07 - Fixed the bug in the fill() function.
38 2004/07/21 - Better e-mail mangling for HTML part of language.doc.
39 - Plural not used for reporting a single missing method.
40 - Removal of not used translator adapters is suggested only
41 when the report is not restricted to selected languages
42 explicitly via script arguments.
43 2004/07/26 - Better reporting of not-needed adapters.
44 2004/10/04 - Reporting of not called translator methods added.
45 2004/10/05 - Modified to check only doxygen/src sources for the previous report.
46 2005/02/28 - Slight modification to generate "mailto.txt" auxiliary file.
47 2005/08/15 - Doxygen's root directory determined primarily from DOXYGEN
48 environment variable. When not found, then relatively to the script.
49 2007/03/20 - The "translate me!" searched in comments and reported if found.
50 2008/06/09 - Warning when the MAX_DOT_GRAPH_HEIGHT is still part of trLegendDocs().
51 2009/05/09 - Changed HTML output to fit it with XHTML DTD
52 2009/09/02 - Added percentage info to the report (implemented / to be implemented).
53 2010/02/09 - Added checking/suggestion 'Reimplementation using UTF-8 suggested.
54 2010/03/03 - Added [unreachable] prefix used in maintainers.txt.
55 2010/05/28 - BOM skipped; minor code cleaning.
56 2010/05/31 - e-mail mangled already in maintainers.txt
57 2010/08/20 - maintainers.txt to UTF-8, related processin of unicode strings
58 - [any mark] introduced instead of [unreachable] only
59 - marks hihglighted in HTML
60 2010/08/30 - Highlighting in what will be the table in langhowto.html modified.
61 2010/09/27 - The underscore in \latexonly part of the generated language.doc
62 was prefixed by backslash (was LaTeX related error).
63 2013/02/19 - Better diagnostics when translator_xx.h is too crippled.
64 2013/06/25 - TranslatorDecoder checks removed after removing the class.
65 2013/09/04 - Coloured status in langhowto. *ALMOST up-to-date* category
66 of translators introduced.
69 from __future__ import generators
77 """Returns string formated to the wrapped paragraph multiline string.
79 Replaces whitespaces by one space and then uses he textwrap.fill()."""
81 # Replace all whitespace by spaces, remove whitespaces that are not
82 # necessary, strip the left and right whitespaces, and break the string
84 rexWS = re.compile(r'\s+')
85 lst = rexWS.sub(' ', s).strip().split()
87 # If the list is not empty, put the words together and form the lines
88 # of maximum 70 characters. Build the list of lines.
91 line = lst.pop(0) # no separation space in front of the first word
93 if len(line) + len(word) < 70:
96 lines.append(line) # another full line formed
97 line = word # next line started
98 lines.append(line) # the last line
99 return '\n'.join(lines)
102 # The following function dedent() is the verbatim copy from the textwrap.py
103 # module. The textwrap.py was introduced in Python 2.3. To make this script
104 # working also in older Python versions, I have decided to copy it.
105 # Notice that the textwrap.py is copyrighted:
107 # Copyright (C) 1999-2001 Gregory P. Ward.
108 # Copyright (C) 2002, 2003 Python Software Foundation.
109 # Written by Greg Ward <gward@python.net>
111 # The explicit permission to use the code here was sent by Guido van Rossum
115 """dedent(text : string) -> string
117 Remove any whitespace than can be uniformly removed from the left
118 of every line in `text`.
120 This can be used e.g. to make triple-quoted strings line up with
121 the left edge of screen/whatever, while still presenting it in the
122 source code in indented form.
127 # end first line with \ to avoid the empty line!
132 print repr(s) # prints ' hello\n world\n '
133 print repr(dedent(s)) # prints 'hello\n world\n'
135 lines = text.expandtabs().split('\n')
138 content = line.lstrip()
141 indent = len(line) - len(content)
145 margin = min(margin, indent)
147 if margin is not None and margin > 0:
148 for i in range(len(lines)):
149 lines[i] = lines[i][margin:]
151 return '\n'.join(lines)
155 """One instance is build for each translator.
157 The abbreviation of the source file--part after 'translator_'--is used as
158 the identification of the object. The empty string is used for the
159 abstract Translator class from translator.h. The other information is
160 extracted from inside the source file."""
162 def __init__(self, fname, manager):
163 """Bind to the manager and initialize."""
165 # Store the filename and the reference to the manager object.
167 self.manager = manager
169 # The instance is responsible for loading the source file, so it checks
170 # for its existence and quits if something goes wrong.
171 if not os.path.isfile(fname):
172 sys.stderr.write("\a\nFile '%s' not found!\n" % fname)
175 # Initialize the other collected information.
177 self.baseClassId = None
178 self.readableStatus = None # 'up-to-date', '1.2.3', '1.3', etc.
179 self.status = None # '', '1.2.03', '1.3.00', etc.
180 self.lang = None # like 'Brasilian'
181 self.langReadable = None # like 'Brasilian Portuguese'
182 self.note = None # like 'should be cleaned up'
183 self.prototypeDic = {} # uniPrototype -> prototype
184 self.translateMeText = 'translate me!'
185 self.translateMeFlag = False # comments with "translate me!" found
186 self.txtMAX_DOT_GRAPH_HEIGHT_flag = False # found in string in trLegendDocs()
187 self.obsoleteMethods = None # list of prototypes to be removed
188 self.missingMethods = None # list of prototypes to be implemented
189 self.implementedMethods = None # list of implemented required methods
190 self.adaptMinClass = None # The newest adapter class that can be used
192 def __tokenGenerator(self):
193 """Generator that reads the file and yields tokens as 4-tuples.
195 The tokens have the form (tokenId, tokenString, lineNo). The
196 last returned token has the form ('eof', None, None). When trying
197 to access next token afer that, the exception would be raised."""
199 # Set the dictionary for recognizing tokenId for keywords, separators
200 # and the similar categories. The key is the string to be recognized,
201 # the value says its token identification.
202 tokenDic = { 'class': 'class',
205 'protected': 'protected',
206 'private': 'private',
208 'virtual': 'virtual',
235 # Regular expression for recognizing identifiers.
236 rexId = re.compile(r'^[a-zA-Z]\w*$')
238 # Open the file for reading and extracting tokens until the eof.
239 # Initialize the finite automaton.
242 line = '' # init -- see the pos initialization below
244 pos = 100 # init -- pos after the end of line
247 tokenId = None # init
248 tokenStr = '' # init -- the characters will be appended.
253 # Get the next character. Read next line first, if necessary.
259 if line.startswith('\xef\xbb\xbf'):
260 line = line[3:] # skip the BOM
268 # Consume the character based on the status
270 if status == 0: # basic status
272 # This is the initial status. If tokenId is set, yield the
273 # token here and only here (except when eof is found).
274 # Initialize the token variables after the yield.
276 # If it is an unknown item, it can still be recognized
277 # here. Keywords and separators are the example.
278 if tokenId == 'unknown':
279 if tokenDic.has_key(tokenStr):
280 tokenId = tokenDic[tokenStr]
281 elif tokenStr.isdigit():
283 elif rexId.match(tokenStr):
286 msg = '\aWarning: unknown token "' + tokenStr + '"'
287 msg += '\tfound on line %d' % tokenLineNo
288 msg += ' in "' + self.fname + '".\n'
289 sys.stderr.write(msg)
291 yield (tokenId, tokenStr, tokenLineNo)
293 # If it is a comment that contains the self.translateMeText
294 # string, set the flag -- the situation will be reported.
295 if tokenId == 'comment' and tokenStr.find(self.translateMeText) >= 0:
296 self.translateMeFlag = True
302 # Now process the character. When we just skip it (spaces),
303 # stay in this status. All characters that will be part of
304 # some token cause moving to the specific status. And only
305 # when moving to the status == 0 (or the final state 777),
306 # the token is yielded. With respect to that the automaton
307 # behaves as Moore's one (output bound to status). When
308 # collecting tokens, the automaton is the Mealy's one
309 # (actions bound to transitions).
311 pass # just skip whitespace characters
312 elif c == '/': # Possibly comment starts here, but
313 tokenId = 'unknown' # it could be only a slash in code.
318 tokenId = 'preproc' # preprocessor directive
322 elif c == '"': # string starts here
327 elif c == "'": # char literal starts here
332 elif tokenDic.has_key(c): # known one-char token
333 tokenId = tokenDic[c]
336 # stay in this state to yield token immediately
338 tokenId = 'unknown' # totally unknown
343 pos += 1 # move position in any case
345 elif status == 1: # possibly a comment
346 if c == '/': # ... definitely the C++ comment
351 elif c == '*': # ... definitely the C comment
357 status = 0 # unrecognized, don't move pos
359 elif status == 2: # inside the C++ comment
360 if c == '\n': # the end of C++ comment
361 status = 0 # yield the token
363 tokenStr += c # collect the C++ comment
366 elif status == 3: # inside the C comment
367 if c == '*': # possibly the end of the C comment
371 tokenStr += c # collect the C comment
374 elif status == 4: # possibly the end of the C comment
375 if c == '/': # definitely the end of the C comment
377 status = 0 # yield the token
378 elif c == '*': # more stars inside the comment
381 tokenStr += c # this cannot be the end of comment
385 elif status == 5: # inside the preprocessor directive
386 if c == '\n': # the end of the preproc. command
387 status = 0 # yield the token
389 tokenStr += c # collect the preproc
392 elif status == 6: # inside the string
393 if c == '\\': # escaped char inside the string
396 elif c == '"': # end of the string
400 tokenStr += c # collect the chars of the string
403 elif status == 7: # escaped char inside the string
404 tokenStr += c # collect the char of the string
408 elif status == 8: # inside the char literal
409 tokenStr += c # collect the char of the literal
413 elif status == 9: # end of char literal expected
414 if c == "'": # ... and found
419 tokenId = 'error' # end of literal was expected
423 elif status == 333: # start of the unknown token
426 status = 0 # tokenId may be determined later
427 elif tokenDic.has_key(c): # separator, don't move pos
430 tokenStr += c # collect
433 # We should have finished in the final status. If some token
434 # have been extracted, yield it first.
435 assert(status == 777)
437 yield (tokenId, tokenStr, tokenLineNo)
442 # The file content is processed. Close the file. Then always yield
445 yield ('eof', None, None)
448 def __collectClassInfo(self, tokenIterator):
449 """Collect the information about the class and base class.
451 The tokens including the opening left curly brace of the class are
454 status = 0 # initial state
456 while status != 777: # final state
458 # Always assume that the previous tokens were processed. Get
460 tokenId, tokenStr, tokenLineNo = tokenIterator.next()
462 # Process the token and never return back.
463 if status == 0: # waiting for the 'class' keyword.
464 if tokenId == 'class':
467 elif status == 1: # expecting the class identification
469 self.classId = tokenStr
472 self.__unexpectedToken(status, tokenId, tokenLineNo)
474 elif status == 2: # expecting the curly brace or base class info
475 if tokenId == 'lcurly':
476 status = 777 # correctly finished
477 elif tokenId == 'colon':
480 self.__unexpectedToken(status, tokenId, tokenLineNo)
482 elif status == 3: # expecting the 'public' in front of base class id
483 if tokenId == 'public':
486 self.__unexpectedToken(status, tokenId, tokenLineNo)
488 elif status == 4: # expecting the base class id
490 self.baseClassId = tokenStr
493 self.__unexpectedToken(status, tokenId, tokenLineNo)
495 elif status == 5: # expecting the curly brace and quitting
496 if tokenId == 'lcurly':
497 status = 777 # correctly finished
498 elif tokenId == 'comment':
501 self.__unexpectedToken(status, tokenId, tokenLineNo)
503 # Extract the status of the TranslatorXxxx class. The readable form
504 # will be used in reports the status form is a string that can be
505 # compared lexically (unified length, padding with zeros, etc.).
507 lst = self.baseClassId.split('_')
508 if lst[0] == 'Translator':
509 self.readableStatus = 'up-to-date'
511 elif lst[0] == 'TranslatorAdapter':
512 self.status = lst[1] + '.' + lst[2]
513 self.readableStatus = self.status
514 if len(lst) > 3: # add the last part of the number
515 self.status += '.' + ('%02d' % int(lst[3]))
516 self.readableStatus += '.' + lst[3]
519 elif lst[0] == 'TranslatorEnglish':
520 # Obsolete or Based on English.
521 if self.classId[-2:] == 'En':
522 self.readableStatus = 'English based'
525 self.readableStatus = 'obsolete'
526 self.status = '0.0.00'
528 # Check whether status was set, or set 'strange'.
529 if self.status == None:
530 self.status = 'strange'
531 if not self.readableStatus:
532 self.readableStatus = 'strange'
534 # Extract the name of the language and the readable form.
535 self.lang = self.classId[10:] # without 'Translator'
536 if self.lang == 'Brazilian':
537 self.langReadable = 'Brazilian Portuguese'
538 elif self.lang == 'Chinesetraditional':
539 self.langReadable = 'Chinese Traditional'
541 self.langReadable = self.lang
544 def __unexpectedToken(self, status, tokenId, tokenLineNo):
545 """Reports unexpected token and quits with exit code 1."""
548 calledFrom = inspect.stack()[1][3]
549 msg = "\a\nUnexpected token '%s' on the line %d in '%s'.\n"
550 msg = msg % (tokenId, tokenLineNo, self.fname)
551 msg += 'status = %d in %s()\n' % (status, calledFrom)
552 sys.stderr.write(msg)
556 def collectPureVirtualPrototypes(self):
557 """Returns dictionary 'unified prototype' -> 'full prototype'.
559 The method is expected to be called only for the translator.h. It
560 extracts only the pure virtual method and build the dictionary where
561 key is the unified prototype without argument identifiers."""
563 # Prepare empty dictionary that will be returned.
566 # Start the token generator which parses the class source file.
567 tokenIterator = self.__tokenGenerator()
569 # Collect the class and the base class identifiers.
570 self.__collectClassInfo(tokenIterator)
571 assert(self.classId == 'Translator')
573 # Let's collect readable form of the public virtual pure method
574 # prototypes in the readable form -- as defined in translator.h.
575 # Let's collect also unified form of the same prototype that omits
576 # everything that can be omitted, namely 'virtual' and argument
578 prototype = '' # readable prototype (with everything)
579 uniPrototype = '' # unified prototype (without arg. identifiers)
581 # Collect the pure virtual method prototypes. Stop on the closing
582 # curly brace followed by the semicolon (end of class).
584 curlyCnt = 0 # counter for the level of curly braces
586 # Loop until the final state 777 is reached. The errors are processed
587 # immediately. In this implementation, it always quits the application.
590 # Get the next token.
591 tokenId, tokenStr, tokenLineNo = tokenIterator.next()
593 if status == 0: # waiting for 'public:'
594 if tokenId == 'public':
597 elif status == 1: # colon after the 'public'
598 if tokenId == 'colon':
601 self.__unexpectedToken(status, tokenId, tokenLineNo)
603 elif status == 2: # waiting for 'virtual'
604 if tokenId == 'virtual':
605 prototype = tokenStr # but not to unified prototype
607 elif tokenId == 'comment':
609 elif tokenId == 'rcurly':
610 status = 11 # expected end of class
612 self.__unexpectedToken(status, tokenId, tokenLineNo)
614 elif status == 3: # return type of the method expected
616 prototype += ' ' + tokenStr
617 uniPrototype = tokenStr # start collecting the unified prototype
619 elif tokenId == 'tilde':
622 self.__unexpectedToken(status, tokenId, tokenLineNo)
624 elif status == 4: # method identifier expected
626 prototype += ' ' + tokenStr
627 uniPrototype += ' ' + tokenStr
630 self.__unexpectedToken(status, tokenId, tokenLineNo)
632 elif status == 5: # left bracket of the argument list expected
633 if tokenId == 'lpar':
634 prototype += tokenStr
635 uniPrototype += tokenStr
638 self.__unexpectedToken(status, tokenId, tokenLineNo)
640 elif status == 6: # collecting arguments of the method
641 if tokenId == 'rpar':
642 prototype += tokenStr
643 uniPrototype += tokenStr
645 elif tokenId == 'const':
646 prototype += tokenStr
647 uniPrototype += tokenStr
649 elif tokenId == 'id': # type identifier
650 prototype += tokenStr
651 uniPrototype += tokenStr
654 self.__unexpectedToken(status, tokenId, tokenLineNo)
656 elif status == 7: # assignment expected or left curly brace
657 if tokenId == 'assign':
659 elif tokenId == 'lcurly':
660 curlyCnt = 1 # method body entered
663 self.__unexpectedToken(status, tokenId, tokenLineNo)
665 elif status == 8: # zero expected
666 if tokenId == 'num' and tokenStr == '0':
669 self.__unexpectedToken(status, tokenId, tokenLineNo)
671 elif status == 9: # after semicolon, produce the dic item
672 if tokenId == 'semic':
673 assert(not resultDic.has_key(uniPrototype))
674 resultDic[uniPrototype] = prototype
677 self.__unexpectedToken(status, tokenId, tokenLineNo)
679 elif status == 10: # consuming the body of the method
680 if tokenId == 'rcurly':
683 status = 2 # body consumed
684 elif tokenId == 'lcurly':
687 elif status == 11: # probably the end of class
688 if tokenId == 'semic':
691 self.__unexpectedToken(status, tokenId, tokenLineNo)
693 elif status == 12: # type id for argument expected
695 prototype += ' ' + tokenStr
696 uniPrototype += ' ' + tokenStr
699 self.__unexpectedToken(status, tokenId, tokenLineNo)
701 elif status == 13: # namespace qualification or * or & expected
702 if tokenId == 'colon': # was namespace id
703 prototype += tokenStr
704 uniPrototype += tokenStr
706 elif tokenId == 'star' or tokenId == 'amp': # pointer or reference
707 prototype += ' ' + tokenStr
708 uniPrototype += ' ' + tokenStr
710 elif tokenId == 'id': # argument identifier
711 prototype += ' ' + tokenStr
712 # don't put this into unified prototype
715 self.__unexpectedToken(status, tokenId, tokenLineNo)
717 elif status == 14: # second colon for namespace:: expected
718 if tokenId == 'colon':
719 prototype += tokenStr
720 uniPrototype += tokenStr
723 self.__unexpectedToken(status, tokenId, tokenLineNo)
725 elif status == 15: # type after namespace:: expected
727 prototype += tokenStr
728 uniPrototype += tokenStr
731 self.__unexpectedToken(status, tokenId, tokenLineNo)
733 elif status == 16: # argument identifier expected
735 prototype += ' ' + tokenStr
736 # don't put this into unified prototype
739 self.__unexpectedToken(status, tokenId, tokenLineNo)
741 elif status == 17: # comma or ')' after argument identifier expected
742 if tokenId == 'comma':
746 elif tokenId == 'rpar':
747 prototype += tokenStr
748 uniPrototype += tokenStr
751 self.__unexpectedToken(status, tokenId, tokenLineNo)
753 # Eat the rest of the source to cause closing the file.
754 while tokenId != 'eof':
755 tokenId, tokenStr, tokenLineNo = tokenIterator.next()
757 # Return the resulting dictionary with 'uniPrototype -> prototype'.
761 def __collectPublicMethodPrototypes(self, tokenIterator):
762 """Collects prototypes of public methods and fills self.prototypeDic.
764 The dictionary is filled by items: uniPrototype -> prototype.
765 The method is expected to be called only for TranslatorXxxx classes,
766 i.e. for the classes that implement translation to some language.
767 It assumes that the openning curly brace of the class was already
768 consumed. The source is consumed until the end of the class.
769 The caller should consume the source until the eof to cause closing
772 assert(self.classId != 'Translator')
773 assert(self.baseClassId != None)
775 # The following finite automaton slightly differs from the one
776 # inside self.collectPureVirtualPrototypes(). It produces the
777 # dictionary item just after consuming the body of the method
778 # (transition from from state 10 to state 2). It also does not allow
779 # definitions of public pure virtual methods, except for
780 # TranslatorAdapterBase (states 8 and 9). Argument identifier inside
781 # method argument lists can be omitted or commented.
783 # Let's collect readable form of all public method prototypes in
784 # the readable form -- as defined in the source file.
785 # Let's collect also unified form of the same prototype that omits
786 # everything that can be omitted, namely 'virtual' and argument
788 prototype = '' # readable prototype (with everything)
789 uniPrototype = '' # unified prototype (without arg. identifiers)
790 warning = '' # warning message -- if something special detected
791 methodId = None # processed method id
793 # Collect the method prototypes. Stop on the closing
794 # curly brace followed by the semicolon (end of class).
796 curlyCnt = 0 # counter for the level of curly braces
798 # Loop until the final state 777 is reached. The errors are processed
799 # immediately. In this implementation, it always quits the application.
802 # Get the next token.
803 tokenId, tokenStr, tokenLineNo = tokenIterator.next()
805 if status == 0: # waiting for 'public:'
806 if tokenId == 'public':
808 elif tokenId == 'eof': # non-public things until the eof
811 elif status == 1: # colon after the 'public'
812 if tokenId == 'colon':
815 self.__unexpectedToken(status, tokenId, tokenLineNo)
817 elif status == 2: # waiting for 'virtual' (can be omitted)
818 if tokenId == 'virtual':
819 prototype = tokenStr # but not to unified prototype
821 elif tokenId == 'id': # 'virtual' was omitted
823 uniPrototype = tokenStr # start collecting the unified prototype
825 elif tokenId == 'comment':
827 elif tokenId == 'protected' or tokenId == 'private':
829 elif tokenId == 'rcurly':
830 status = 11 # expected end of class
832 self.__unexpectedToken(status, tokenId, tokenLineNo)
834 elif status == 3: # return type of the method expected
836 prototype += ' ' + tokenStr
837 uniPrototype = tokenStr # start collecting the unified prototype
840 self.__unexpectedToken(status, tokenId, tokenLineNo)
842 elif status == 4: # method identifier expected
844 prototype += ' ' + tokenStr
845 uniPrototype += ' ' + tokenStr
846 methodId = tokenStr # for reporting
849 self.__unexpectedToken(status, tokenId, tokenLineNo)
851 elif status == 5: # left bracket of the argument list expected
852 if tokenId == 'lpar':
853 prototype += tokenStr
854 uniPrototype += tokenStr
857 self.__unexpectedToken(status, tokenId, tokenLineNo)
859 elif status == 6: # collecting arguments of the method
860 if tokenId == 'rpar':
861 prototype += tokenStr
862 uniPrototype += tokenStr
864 elif tokenId == 'const':
865 prototype += tokenStr
866 uniPrototype += tokenStr
868 elif tokenId == 'id': # type identifier
869 prototype += tokenStr
870 uniPrototype += tokenStr
873 self.__unexpectedToken(status, tokenId, tokenLineNo)
875 elif status == 7: # left curly brace expected
876 if tokenId == 'lcurly':
877 curlyCnt = 1 # method body entered
879 elif tokenId == 'comment':
881 elif tokenId == 'assign': # allowed only for TranslatorAdapterBase
882 assert(self.classId == 'TranslatorAdapterBase')
885 self.__unexpectedToken(status, tokenId, tokenLineNo)
887 elif status == 8: # zero expected (TranslatorAdapterBase)
888 assert(self.classId == 'TranslatorAdapterBase')
889 if tokenId == 'num' and tokenStr == '0':
892 self.__unexpectedToken(status, tokenId, tokenLineNo)
894 elif status == 9: # after semicolon (TranslatorAdapterBase)
895 assert(self.classId == 'TranslatorAdapterBase')
896 if tokenId == 'semic':
899 self.__unexpectedToken(status, tokenId, tokenLineNo)
901 elif status == 10: # consuming the body of the method, then dic item
902 if tokenId == 'rcurly':
905 # Check for possible copy/paste error when name
906 # of the method was not corrected (i.e. the same
907 # name already exists).
908 if uniPrototype in self.prototypeDic:
909 msg = "'%s' prototype found again (duplicity)\n"
910 msg += "in '%s'.\n" % self.fname
911 msg = msg % uniPrototype
912 sys.stderr.write(msg)
915 assert(not self.prototypeDic.has_key(uniPrototype))
916 # Insert new dictionary item.
917 self.prototypeDic[uniPrototype] = prototype
918 status = 2 # body consumed
919 methodId = None # outside of any method
920 elif tokenId == 'lcurly':
923 # Warn in special case.
924 elif methodId == 'trLegendDocs' and tokenId == 'string' \
925 and tokenStr.find('MAX_DOT_GRAPH_HEIGHT') >= 0:
926 self.txtMAX_DOT_GRAPH_HEIGHT_flag = True
929 elif status == 11: # probably the end of class
930 if tokenId == 'semic':
933 self.__unexpectedToken(status, tokenId, tokenLineNo)
935 elif status == 12: # type id for argument expected
937 prototype += ' ' + tokenStr
938 uniPrototype += ' ' + tokenStr
941 self.__unexpectedToken(status, tokenId, tokenLineNo)
943 elif status == 13: # :: or * or & or id or ) expected
944 if tokenId == 'colon': # was namespace id
945 prototype += tokenStr
946 uniPrototype += tokenStr
948 elif tokenId == 'star' or tokenId == 'amp': # pointer or reference
949 prototype += ' ' + tokenStr
950 uniPrototype += ' ' + tokenStr
952 elif tokenId == 'id': # argument identifier
953 prototype += ' ' + tokenStr
954 # don't put this into unified prototype
956 elif tokenId == 'comment': # probably commented-out identifier
957 prototype += tokenStr
958 elif tokenId == 'rpar':
959 prototype += tokenStr
960 uniPrototype += tokenStr
962 elif tokenId == 'comma':
967 self.__unexpectedToken(status, tokenId, tokenLineNo)
969 elif status == 14: # second colon for namespace:: expected
970 if tokenId == 'colon':
971 prototype += tokenStr
972 uniPrototype += tokenStr
975 self.__unexpectedToken(status, tokenId, tokenLineNo)
977 elif status == 15: # type after namespace:: expected
979 prototype += tokenStr
980 uniPrototype += tokenStr
983 self.__unexpectedToken(status, tokenId, tokenLineNo)
985 elif status == 16: # argument identifier or ) expected
987 prototype += ' ' + tokenStr
988 # don't put this into unified prototype
990 elif tokenId == 'rpar':
991 prototype += tokenStr
992 uniPrototype += tokenStr
994 elif tokenId == 'comment':
995 prototype += tokenStr
997 self.__unexpectedToken(status, tokenId, tokenLineNo)
999 elif status == 17: # comma or ')' after argument identifier expected
1000 if tokenId == 'comma':
1002 uniPrototype += ', '
1004 elif tokenId == 'rpar':
1005 prototype += tokenStr
1006 uniPrototype += tokenStr
1009 self.__unexpectedToken(status, tokenId, tokenLineNo)
1013 def collectAdapterPrototypes(self):
1014 """Returns the dictionary of prototypes implemented by adapters.
1016 It is created to process the translator_adapter.h. The returned
1017 dictionary has the form: unifiedPrototype -> (version, classId)
1018 thus by looking for the prototype, we get the information what is
1019 the newest (least adapting) adapter that is sufficient for
1020 implementing the method."""
1022 # Start the token generator which parses the class source file.
1023 assert(os.path.split(self.fname)[1] == 'translator_adapter.h')
1024 tokenIterator = self.__tokenGenerator()
1026 # Get the references to the involved dictionaries.
1027 reqDic = self.manager.requiredMethodsDic
1029 # Create the empty dictionary that will be returned.
1033 # Loop through the source of the adapter file until no other adapter
1037 # Collect the class and the base class identifiers.
1038 self.__collectClassInfo(tokenIterator)
1040 # Extract the comparable version of the adapter class.
1041 # Note: The self.status as set by self.__collectClassInfo()
1042 # contains similar version, but is related to the base class,
1043 # not to the class itself.
1044 lst = self.classId.split('_')
1046 if lst[0] == 'TranslatorAdapter': # TranslatorAdapterBase otherwise
1047 version = lst[1] + '.' + lst[2]
1048 if len(lst) > 3: # add the last part of the number
1049 version += '.' + ('%02d' % int(lst[3]))
1053 # Collect the prototypes of implemented public methods.
1054 self.__collectPublicMethodPrototypes(tokenIterator)
1056 # For the required methods, update the dictionary of methods
1057 # implemented by the adapter.
1058 for protoUni in self.prototypeDic:
1059 if reqDic.has_key(protoUni):
1060 # This required method will be marked as implemented
1061 # by this adapter class. This implementation assumes
1062 # that newer adapters do not reimplement any required
1063 # methods already implemented by older adapters.
1064 assert(not adaptDic.has_key(protoUni))
1065 adaptDic[protoUni] = (version, self.classId)
1067 # Clear the dictionary object and the information related
1068 # to the class as the next adapter class is to be processed.
1069 self.prototypeDic.clear()
1071 self.baseClassId = None
1073 except StopIteration:
1076 # Return the result dictionary.
1080 def processing(self):
1081 """Processing of the source file -- only for TranslatorXxxx classes."""
1083 # Start the token generator which parses the class source file.
1084 tokenIterator = self.__tokenGenerator()
1086 # Collect the class and the base class identifiers.
1087 self.__collectClassInfo(tokenIterator)
1088 assert(self.classId != 'Translator')
1089 assert(self.classId[:17] != 'TranslatorAdapter')
1091 # Collect the prototypes of implemented public methods.
1092 self.__collectPublicMethodPrototypes(tokenIterator)
1094 # Eat the rest of the source to cause closing the file.
1097 t = tokenIterator.next()
1098 except StopIteration:
1101 # Shorthands for the used dictionaries.
1102 reqDic = self.manager.requiredMethodsDic
1103 adaptDic = self.manager.adaptMethodsDic
1104 myDic = self.prototypeDic
1106 # Build the list of obsolete methods.
1107 self.obsoleteMethods = []
1109 if not reqDic.has_key(p):
1110 self.obsoleteMethods.append(p)
1112 # Build the list of missing methods and the list of implemented
1114 self.missingMethods = []
1115 self.implementedMethods = []
1117 if myDic.has_key(p):
1118 self.implementedMethods.append(p)
1120 self.missingMethods.append(p)
1122 # Check whether adapter must be used or suggest the newest one.
1123 # Change the status and set the note accordingly.
1124 if self.baseClassId != 'Translator':
1125 if not self.missingMethods:
1126 self.note = 'Change the base class to Translator.'
1128 self.readableStatus = 'almost up-to-date'
1129 elif self.baseClassId != 'TranslatorEnglish':
1130 # The translator uses some of the adapters.
1131 # Look at the missing methods and check what adapter
1132 # implements them. Remember the one with the lowest version.
1133 adaptMinVersion = '9.9.99'
1134 adaptMinClass = 'TranslatorAdapter_9_9_99'
1135 for uniProto in self.missingMethods:
1136 if adaptDic.has_key(uniProto):
1137 version, cls = adaptDic[uniProto]
1138 if version < adaptMinVersion:
1139 adaptMinVersion = version
1142 # Test against the current status -- preserve the self.status.
1143 # Possibly, the translator implements enough methods to
1144 # use some newer adapter.
1145 status = self.status
1147 # If the version of the used adapter is smaller than
1148 # the required, set the note and update the status as if
1149 # the newer adapter was used.
1150 if adaptMinVersion > status:
1151 self.note = 'Change the base class to %s.' % adaptMinClass
1152 self.status = adaptMinVersion
1153 self.adaptMinClass = adaptMinClass
1154 self.readableStatus = adaptMinVersion # simplified
1156 # If everything seems OK, some explicit warning flags still could
1158 if not self.note and self.status == '' and \
1159 (self.translateMeFlag or self.txtMAX_DOT_GRAPH_HEIGHT_flag):
1161 if self.translateMeFlag:
1162 self.note += 'The "%s" found in a comment.' % self.translateMeText
1164 self.note += '\n\t\t'
1165 if self.txtMAX_DOT_GRAPH_HEIGHT_flag:
1166 self.note += 'The MAX_DOT_GRAPH_HEIGHT found in trLegendDocs()'
1168 # If everything seems OK, but there are obsolete methods, set
1169 # the note to clean-up source. This note will be used only when
1170 # the previous code did not set another note (priority).
1171 if not self.note and self.status == '' and self.obsoleteMethods:
1172 self.note = 'Remove the obsolete methods (never used).'
1174 # If there is at least some note but the status suggests it is
1175 # otherwise up-to-date, mark is as ALMOST up-to-date.
1176 if self.note and self.status == '':
1177 self.readableStatus = 'almost up-to-date'
1180 def report(self, fout):
1181 """Returns the report part for the source as a multiline string.
1183 No output for up-to-date translators without problem."""
1185 # If there is nothing to report, return immediately.
1186 if self.status == '' and not self.note:
1189 # Report the number of not implemented methods.
1190 fout.write('\n\n\n')
1191 fout.write(self.classId + ' (' + self.baseClassId + ')')
1192 percentImplemented = 100 # init
1193 allNum = len(self.manager.requiredMethodsDic)
1194 if self.missingMethods:
1195 num = len(self.missingMethods)
1196 percentImplemented = 100 * (allNum - num) / allNum
1197 fout.write(' %d' % num)
1198 fout.write(' method')
1201 fout.write(' to implement (%d %%)' % (100 * num / allNum))
1202 fout.write('\n' + '-' * len(self.classId))
1204 # Write the info about the implemented required methods.
1205 fout.write('\n\n Implements %d' % len(self.implementedMethods))
1206 fout.write(' of the required methods (%d %%).' % percentImplemented)
1208 # Report the missing method, but only when it is not English-based
1210 if self.missingMethods and self.status != 'En':
1211 fout.write('\n\n Missing methods (should be implemented):\n')
1212 reqDic = self.manager.requiredMethodsDic
1213 for p in self.missingMethods:
1214 fout.write('\n ' + reqDic[p])
1216 # Always report obsolete methods.
1217 if self.obsoleteMethods:
1218 fout.write('\n\n Obsolete methods (should be removed, never used):\n')
1219 myDic = self.prototypeDic
1220 for p in self.obsoleteMethods:
1221 fout.write('\n ' + myDic[p])
1223 # For English-based translator, report the implemented methods.
1224 if self.status == 'En' and self.implementedMethods:
1225 fout.write('\n\n This English-based translator implements ')
1226 fout.write('the following methods:\n')
1227 reqDic = self.manager.requiredMethodsDic
1228 for p in self.implementedMethods:
1229 fout.write('\n ' + reqDic[p])
1233 """Returns the last modification time of the source file."""
1234 assert(os.path.isfile(self.fname))
1235 return os.path.getmtime(self.fname)
1239 """Collects basic info and builds subordinate Transl objects."""
1242 """Determines paths, creates and initializes structures.
1244 The arguments of the script may explicitly say what languages should
1245 be processed. Write the two letter identifications that are used
1246 for composing the source filenames, so...
1248 python translator.py cz
1250 this will process only translator_cz.h source.
1253 # Determine the path to the script and its name.
1254 self.script = os.path.abspath(sys.argv[0])
1255 self.script_path, self.script_name = os.path.split(self.script)
1256 self.script_path = os.path.abspath(self.script_path)
1258 # Determine the absolute path to the Doxygen's root subdirectory.
1259 # If DOXYGEN environment variable is not found, the directory is
1260 # determined from the path of the script.
1261 doxy_default = os.path.join(self.script_path, '..')
1262 self.doxy_path = os.path.abspath(os.getenv('DOXYGEN', doxy_default))
1264 # Get the explicit arguments of the script.
1265 self.script_argLst = sys.argv[1:]
1267 # Build the path names based on the Doxygen's root knowledge.
1268 self.doc_path = os.path.join(self.doxy_path, 'doc')
1269 self.src_path = os.path.join(self.doxy_path, 'src')
1271 # Create the empty dictionary for Transl object identitied by the
1272 # class identifier of the translator.
1273 self.__translDic = {}
1275 # Create the None dictionary of required methods. The key is the
1276 # unified prototype, the value is the full prototype. Set inside
1277 # the self.__build().
1278 self.requiredMethodsDic = None
1280 # Create the empty dictionary that says what method is implemented
1282 self.adaptMethodsDic = {}
1284 # The last modification time will capture the modification of this
1285 # script, of the translator.h, of the translator_adapter.h (see the
1286 # self.__build() for the last two) of all the translator_xx.h files
1287 # and of the template for generating the documentation. So, this
1288 # time can be compared with modification time of the generated
1289 # documentation to decide, whether the doc should be re-generated.
1290 self.lastModificationTime = os.path.getmtime(self.script)
1292 # Set the names of the translator report text file, of the template
1293 # for generating "Internationalization" document, for the generated
1294 # file itself, and for the maintainers list.
1295 self.translatorReportFileName = 'translator_report.txt'
1296 self.maintainersFileName = 'maintainers.txt'
1297 self.languageTplFileName = 'language.tpl'
1298 self.languageDocFileName = 'language.doc'
1300 # The information about the maintainers will be stored
1301 # in the dictionary with the following name.
1302 self.__maintainersDic = None
1304 # Define the other used structures and variables for information.
1305 self.langLst = None # including English based
1306 self.supportedLangReadableStr = None # coupled En-based as a note
1307 self.numLang = None # excluding coupled En-based
1308 self.doxVersion = None # Doxygen version
1310 # Build objects where each one is responsible for one translator.
1315 """Find the translator files and build the objects for translators."""
1317 # The translator.h must exist (the Transl object will check it),
1318 # create the object for it and let it build the dictionary of
1320 tr = Transl(os.path.join(self.src_path, 'translator.h'), self)
1321 self.requiredMethodsDic = tr.collectPureVirtualPrototypes()
1323 if tim > self.lastModificationTime:
1324 self.lastModificationTime = tim
1326 # The translator_adapter.h must exist (the Transl object will check it),
1327 # create the object for it and store the reference in the dictionary.
1328 tr = Transl(os.path.join(self.src_path, 'translator_adapter.h'), self)
1329 self.adaptMethodsDic = tr.collectAdapterPrototypes()
1331 if tim > self.lastModificationTime:
1332 self.lastModificationTime = tim
1334 # Create the list of the filenames with language translator sources.
1335 # If the explicit arguments of the script were typed, process only
1337 if self.script_argLst:
1338 lst = ['translator_' + x + '.h' for x in self.script_argLst]
1340 if not os.path.isfile(os.path.join(self.src_path, fname)):
1341 sys.stderr.write("\a\nFile '%s' not found!\n" % fname)
1344 lst = os.listdir(self.src_path)
1345 lst = filter(lambda x: x[:11] == 'translator_'
1347 and x != 'translator_adapter.h', lst)
1349 # Build the object for the translator_xx.h files, and process the
1350 # content of the file. Then insert the object to the dictionary
1351 # accessed via classId.
1353 fullname = os.path.join(self.src_path, fname)
1354 tr = Transl(fullname, self)
1356 assert(tr.classId != 'Translator')
1357 self.__translDic[tr.classId] = tr
1359 # Extract the global information of the processed info.
1360 self.__extractProcessedInfo()
1363 def __extractProcessedInfo(self):
1364 """Build lists and strings of the processed info."""
1366 # Build the auxiliary list with strings compound of the status,
1367 # readable form of the language, and classId.
1369 for obj in self.__translDic.values():
1370 assert(obj.classId != 'Translator')
1371 s = obj.status + '|' + obj.langReadable + '|' + obj.classId
1374 # Sort the list and extract the object identifiers (classId's) for
1375 # the up-to-date translators and English-based translators.
1377 self.upToDateIdLst = [x.split('|')[2] for x in statLst if x[0] == '|']
1378 self.EnBasedIdLst = [x.split('|')[2] for x in statLst if x[:2] == 'En']
1380 # Reverse the list and extract the TranslatorAdapter based translators.
1382 self.adaptIdLst = [x.split('|')[2] for x in statLst if x[0].isdigit()]
1384 # Build the list of tuples that contain (langReadable, obj).
1385 # Sort it by readable name.
1387 for obj in self.__translDic.values():
1388 self.langLst.append((obj.langReadable, obj))
1389 self.langLst.sort(lambda a, b: cmp(a[0], b[0]))
1391 # Create the list with readable language names. If the language has
1392 # also the English-based version, modify the item by appending
1393 # the note. Number of the supported languages is equal to the length
1395 langReadableLst = []
1396 for name, obj in self.langLst:
1397 if obj.status == 'En': continue
1399 # Append the 'En' to the classId to possibly obtain the classId
1400 # of the English-based object. If the object exists, modify the
1401 # name for the readable list of supported languages.
1402 classIdEn = obj.classId + 'En'
1403 if self.__translDic.has_key(classIdEn):
1406 # Append the result name of the language, possibly with note.
1407 langReadableLst.append(name)
1409 # Create the multiline string of readable language names,
1410 # with punctuation, wrapped to paragraph.
1411 if len(langReadableLst) == 1:
1412 s = langReadableLst[0]
1413 elif len(langReadableLst) == 2:
1414 s = ' and '.join(langReadableLst)
1416 s = ', '.join(langReadableLst[:-1]) + ', and '
1417 s += langReadableLst[-1]
1419 self.supportedLangReadableStr = fill(s + '.')
1421 # Find the number of the supported languages. The English based
1422 # languages are not counted if the non-English based also exists.
1423 self.numLang = len(self.langLst)
1424 for name, obj in self.langLst:
1425 if obj.status == 'En':
1426 classId = obj.classId[:-2]
1427 if self.__translDic.has_key(classId):
1428 self.numLang -= 1 # the couple will be counted as one
1430 # Extract the version of Doxygen.
1431 f = open(os.path.join(self.doxy_path, 'VERSION'))
1432 self.doxVersion = f.readline().strip()
1435 # Update the last modification time.
1436 for tr in self.__translDic.values():
1438 if tim > self.lastModificationTime:
1439 self.lastModificationTime = tim
1442 def __getNoTrSourceFilesLst(self):
1443 """Returns the list of sources to be checked.
1445 All .cpp files and also .h files that do not declare or define
1446 the translator methods are included in the list. The file names
1447 are searched in doxygen/src directory.
1450 for item in os.listdir(self.src_path):
1451 # Split the bare name to get the extension.
1452 name, ext = os.path.splitext(item)
1455 # Include only .cpp and .h files (case independent) and exclude
1456 # the files where the checked identifiers are defined.
1457 if ext == '.cpp' or (ext == '.h' and name.find('translator') == -1):
1458 fname = os.path.join(self.src_path, item)
1459 assert os.path.isfile(fname) # assumes no directory with the ext
1460 files.append(fname) # full name
1464 def __removeUsedInFiles(self, fname, dic):
1465 """Removes items for method identifiers that are found in fname.
1467 The method reads the content of the file as one string and searches
1468 for all identifiers from dic. The identifiers that were found in
1469 the file are removed from the dictionary.
1471 Note: If more files is to be checked, the files where most items are
1472 probably used should be checked first and the resulting reduced
1473 dictionary should be used for checking the next files (speed up).
1475 lst_in = dic.keys() # identifiers to be searched for
1477 # Read content of the file as one string.
1478 assert os.path.isfile(fname)
1483 # Remove the items for identifiers that were found in the file.
1485 item = lst_in.pop(0)
1486 if cont.find(item) != -1:
1490 def __checkForNotUsedTrMethods(self):
1491 """Returns the dictionary of not used translator methods.
1493 The method can be called only after self.requiredMethodsDic has been
1494 built. The stripped prototypes are the values, the method identifiers
1497 # Build the dictionary of the required method prototypes with
1498 # method identifiers used as keys.
1500 for prototype in self.requiredMethodsDic.keys():
1501 ri = prototype.split('(')[0]
1502 identifier = ri.split()[1].strip()
1503 trdic[identifier] = prototype
1505 # Build the list of source files where translator method identifiers
1507 files = self.__getNoTrSourceFilesLst()
1509 # Loop through the files and reduce the dictionary of id -> proto.
1511 self.__removeUsedInFiles(fname, trdic)
1513 # Return the dictionary of not used translator methods.
1517 def __emails(self, classId):
1518 """Returns the list of maintainer emails.
1520 The method returns the list of e-mail adresses for the translator
1521 class, but only the addresses that were not marked as [xxx]."""
1523 for m in self.__maintainersDic[classId]:
1524 if not m[1].startswith('['):
1526 email = email.replace(' at ', '@') # Unmangle the mangled e-mail
1527 email = email.replace(' dot ', '.')
1532 def getBgcolorByReadableStatus(self, readableStatus):
1533 if readableStatus == 'up-to-date':
1534 color = '#ccffcc' # green
1535 elif readableStatus.startswith('almost'):
1536 color = '#ffffff' # white
1537 elif readableStatus.startswith('English'):
1538 color = '#ccffcc' # green
1539 elif readableStatus.startswith('1.8'):
1540 color = '#ffffcc' # yellow
1541 elif readableStatus.startswith('1.7'):
1542 color = '#ffcccc' # pink
1543 elif readableStatus.startswith('1.6'):
1544 color = '#ffcccc' # pink
1546 color = '#ff5555' # red
1550 def generateTranslatorReport(self):
1551 """Generates the translator report."""
1553 output = os.path.join(self.doc_path, self.translatorReportFileName)
1555 # Open the textual report file for the output.
1556 f = open(output, 'w')
1558 # Output the information about the version.
1559 f.write('(' + self.doxVersion + ')\n\n')
1561 # Output the information about the number of the supported languages
1562 # and the list of the languages, or only the note about the explicitly
1563 # given languages to process.
1564 if self.script_argLst:
1565 f.write('The report was generated for the following, explicitly')
1566 f.write(' identified languages:\n\n')
1567 f.write(self.supportedLangReadableStr + '\n\n')
1569 f.write('Doxygen supports the following ')
1570 f.write(str(self.numLang))
1571 f.write(' languages (sorted alphabetically):\n\n')
1572 f.write(self.supportedLangReadableStr + '\n\n')
1574 # Write the summary about the status of language translators (how
1575 # many translators) are up-to-date, etc.
1576 s = 'Of them, %d translators are up-to-date, ' % len(self.upToDateIdLst)
1577 s += '%d translators are based on some adapter class, ' % len(self.adaptIdLst)
1578 s += 'and %d are English based.' % len(self.EnBasedIdLst)
1579 f.write(fill(s) + '\n\n')
1581 # The e-mail addresses of the maintainers will be collected to
1582 # the auxiliary file in the order of translator classes listed
1583 # in the translator report.
1584 fmail = open('mailto.txt', 'w')
1586 # Write the list of "up-to-date" translator classes.
1587 if self.upToDateIdLst:
1588 s = '''The following translator classes are up-to-date (sorted
1589 alphabetically). This means that they derive from the
1590 Translator class, they implement all %d of the required
1591 methods, and even minor problems were not spotted by the script:'''
1592 s = s % len(self.requiredMethodsDic)
1593 f.write('-' * 70 + '\n')
1594 f.write(fill(s) + '\n\n')
1597 for x in self.upToDateIdLst:
1598 obj = self.__translDic[x]
1599 if obj.note is None:
1600 f.write(' ' + obj.classId + '\n')
1601 mailtoLst.extend(self.__emails(obj.classId))
1603 fmail.write('up-to-date\n')
1604 fmail.write('; '.join(mailtoLst))
1607 # Write separately the list of "ALMOST up-to-date" translator classes.
1608 s = '''The following translator classes are ALMOST up-to-date (sorted
1609 alphabetically). This means that they derive from the
1610 Translator class, but there still may be some minor problems
1612 f.write('\n' + ('-' * 70) + '\n')
1613 f.write(fill(s) + '\n\n')
1615 for x in self.upToDateIdLst:
1616 obj = self.__translDic[x]
1617 if obj.note is not None:
1618 f.write(' ' + obj.classId + '\t-- ' + obj.note + '\n')
1619 mailtoLst.extend(self.__emails(obj.classId))
1621 fmail.write('\n\nalmost up-to-date\n')
1622 fmail.write('; '.join(mailtoLst))
1624 # Write the list of the adapter based classes. The very obsolete
1625 # translators that derive from TranslatorEnglish are included.
1627 s = '''The following translator classes need maintenance
1628 (the most obsolete at the end). The other info shows the
1629 estimation of Doxygen version when the class was last
1630 updated and number of methods that must be implemented to
1631 become up-to-date:'''
1632 f.write('\n' + '-' * 70 + '\n')
1633 f.write(fill(s) + '\n\n')
1635 # Find also whether some adapter classes may be removed.
1636 adaptMinVersion = '9.9.99'
1639 numRequired = len(self.requiredMethodsDic)
1640 for x in self.adaptIdLst:
1641 obj = self.__translDic[x]
1642 f.write(' %-30s' % obj.classId)
1643 f.write(' %-6s' % obj.readableStatus)
1644 numimpl = len(obj.missingMethods)
1646 if numimpl > 1: pluralS = 's'
1647 percent = 100 * numimpl / numRequired
1648 f.write('\t%2d method%s to implement (%d %%)' % (
1649 numimpl, pluralS, percent))
1651 f.write('\n\tNote: ' + obj.note + '\n')
1653 mailtoLst.extend(self.__emails(obj.classId)) # to maintainer
1655 # Check the level of required adapter classes.
1656 if obj.status != '0.0.00' and obj.status < adaptMinVersion:
1657 adaptMinVersion = obj.status
1659 fmail.write('\n\ntranslator based\n')
1660 fmail.write('; '.join(mailtoLst))
1662 # Set the note if some old translator adapters are not needed
1663 # any more. Do it only when the script is called without arguments,
1664 # i.e. all languages were checked against the needed translator
1666 if not self.script_argLst:
1668 for version, adaptClassId in self.adaptMethodsDic.values():
1669 if version < adaptMinVersion:
1670 to_remove[adaptClassId] = True
1673 lst = to_remove.keys()
1675 plural = len(lst) > 1
1676 note = 'Note: The adapter class'
1677 if plural: note += 'es'
1678 note += ' ' + ', '.join(lst)
1683 note += ' not used and can be removed.'
1684 f.write('\n' + fill(note) + '\n')
1686 # Write the list of the English-based classes.
1687 if self.EnBasedIdLst:
1688 s = '''The following translator classes derive directly from the
1689 TranslatorEnglish. The class identifier has the suffix 'En'
1690 that says that this is intentional. Usually, there is also
1691 a non-English based version of the translator for
1693 f.write('\n' + '-' * 70 + '\n')
1694 f.write(fill(s) + '\n\n')
1696 for x in self.EnBasedIdLst:
1697 obj = self.__translDic[x]
1698 f.write(' ' + obj.classId)
1699 f.write('\timplements %d methods' % len(obj.implementedMethods))
1701 f.write(' -- ' + obj.note)
1704 # Check for not used translator methods and generate warning if found.
1705 # The check is rather time consuming, so it is not done when report
1706 # is restricted to explicitly given language identifiers.
1707 if not self.script_argLst:
1708 dic = self.__checkForNotUsedTrMethods()
1710 s = '''WARNING: The following translator methods are declared
1711 in the Translator class but their identifiers do not appear
1712 in source files. The situation should be checked. The .cpp
1713 files and .h files excluding the '*translator*' files
1714 in doxygen/src directory were simply searched for occurrence
1715 of the method identifiers:'''
1716 f.write('\n' + '=' * 70 + '\n')
1717 f.write(fill(s) + '\n\n')
1722 f.write(' ' + dic[key] + '\n')
1725 # Write the details for the translators.
1726 f.write('\n' + '=' * 70)
1727 f.write('\nDetails for translators (classes sorted alphabetically):\n')
1729 cls = self.__translDic.keys()
1733 obj = self.__translDic[c]
1734 assert(obj.classId != 'Translator')
1737 # Close the report file and the auxiliary file with e-mails.
1742 def __loadMaintainers(self):
1743 """Load and process the file with the maintainers.
1745 Fills the dictionary classId -> [(name, e-mail), ...]."""
1747 fname = os.path.join(self.doc_path, self.maintainersFileName)
1749 # Include the maintainers file to the group of files checked with
1750 # respect to the modification time.
1751 tim = os.path.getmtime(fname)
1752 if tim > self.lastModificationTime:
1753 self.lastModificationTime = tim
1755 # Process the content of the maintainers file.
1756 f = codecs.open(fname, 'r', 'utf-8')
1757 inside = False # inside the record for the language
1760 maintainersLst = None
1761 self.__maintainersDic = {}
1763 line = f.readline() # next line
1764 lineReady = line != '' # when eof, then line == ''
1766 line = line.strip() # eof should also behave as separator
1767 if line != u'' and line[0] == u'%': # skip the comment line
1770 if not inside: # if outside of the record
1771 if line != u'': # should be language identifier
1775 # Otherwise skip empty line that do not act as separator.
1777 else: # if inside the record
1778 if line == u'': # separator found
1781 # If it is the first maintainer, create the empty list.
1782 if not self.__maintainersDic.has_key(classId):
1783 self.__maintainersDic[classId] = []
1785 # Split the information about the maintainer and append
1786 # the tuple. The address may be prefixed '[unreachable]'
1787 # or whatever '[xxx]'. This will be processed later.
1788 lst = line.split(u':', 1)
1789 assert(len(lst) == 2)
1790 t = (lst[0].strip(), lst[1].strip())
1791 self.__maintainersDic[classId].append(t)
1795 def generateLanguageDoc(self):
1796 """Checks the modtime of files and generates language.doc."""
1797 self.__loadMaintainers()
1799 # Check the last modification time of the template file. It is the
1800 # last file from the group that decide whether the documentation
1801 # should or should not be generated.
1802 fTplName = os.path.join(self.doc_path, self.languageTplFileName)
1803 tim = os.path.getmtime(fTplName)
1804 if tim > self.lastModificationTime:
1805 self.lastModificationTime = tim
1807 # If the generated documentation exists and is newer than any of
1808 # the source files from the group, do not generate it and quit
1810 fDocName = os.path.join(self.doc_path, self.languageDocFileName)
1811 if os.path.isfile(fDocName):
1812 if os.path.getmtime(fDocName) > self.lastModificationTime:
1815 # The document or does not exist or is older than some of the
1816 # sources. It must be generated again.
1818 # Read the template of the documentation, and remove the first
1820 f = codecs.open(fTplName, 'r', 'utf-8')
1824 pos = doctpl.find(u'/***')
1826 doctpl = doctpl[pos:]
1828 # Fill the tplDic by symbols that will be inserted into the
1829 # document template.
1832 s = u'Do not edit this file. It was generated by the %s script.\n * Instead edit %s and %s' % (self.script_name, self.languageTplFileName, self.maintainersFileName)
1833 tplDic['editnote'] = s
1835 tplDic['doxVersion'] = self.doxVersion
1836 tplDic['supportedLangReadableStr'] = self.supportedLangReadableStr
1837 tplDic['translatorReportFileName'] = self.translatorReportFileName
1839 ahref = u'<a href="../doc/' + self.translatorReportFileName
1840 ahref += u'"\n><code>doxygen/doc/' + self.translatorReportFileName
1841 ahref += u'</code></a>'
1842 tplDic['translatorReportLink'] = ahref
1843 tplDic['numLangStr'] = str(self.numLang)
1845 # Define templates for HTML table parts of the documentation.
1846 htmlTableTpl = u'''\
1848 <table align="center" cellspacing="0" cellpadding="0" border="0">
1849 <tr bgcolor="#000000">
1851 <table cellspacing="1" cellpadding="2" border="0">
1852 <tr bgcolor="#4040c0">
1853 <td ><b><font size="+1" color="#ffffff"> Language </font></b></td>
1854 <td ><b><font size="+1" color="#ffffff"> Maintainer </font></b></td>
1855 <td ><b><font size="+1" color="#ffffff"> Contact address </font>
1856 <font size="-2" color="#ffffff">(replace the at and dot)</font></b></td>
1857 <td ><b><font size="+1" color="#ffffff"> Status </font></b></td>
1859 <!-- table content begin -->
1861 <!-- table content end -->
1868 htmlTableTpl = dedent(htmlTableTpl)
1869 htmlTrTpl = u'\n <tr bgcolor="#ffffff">%s\n </tr>'
1870 htmlTdTpl = u'\n <td>%s</td>'
1871 htmlTdStatusColorTpl = u'\n <td bgcolor="%s">%s</td>'
1873 # Loop through transl objects in the order of sorted readable names
1874 # and add generate the content of the HTML table.
1876 for name, obj in self.langLst:
1877 # Fill the table data elements for one row. The first element
1878 # contains the readable name of the language. Only the oldest
1879 # translator are colour marked in the language columnt. Less
1880 # "heavy" color is used (when compared with the Status column).
1881 if obj.readableStatus.startswith('1.4'):
1882 bkcolor = self.getBgcolorByReadableStatus('1.4')
1886 lst = [ htmlTdStatusColorTpl % (bkcolor, obj.langReadable) ]
1888 # The next two elements contain the list of maintainers
1889 # and the list of their mangled e-mails. For English-based
1890 # translators that are coupled with the non-English based,
1891 # insert the 'see' note.
1892 mm = None # init -- maintainer
1893 ee = None # init -- e-mail address
1894 if obj.status == 'En':
1895 # Check whether there is the coupled non-English.
1896 classId = obj.classId[:-2]
1897 if classId in self.__translDic:
1898 lang = self.__translDic[classId].langReadable
1899 mm = u'see the %s language' % lang
1902 if not mm and obj.classId in self.__maintainersDic:
1903 # Build a string of names separated by the HTML break element.
1904 # Special notes used instead of names are highlighted.
1906 for maintainer in self.__maintainersDic[obj.classId]:
1907 name = maintainer[0]
1908 if name.startswith(u'--'):
1909 name = u'<span style="color: red; background-color: yellow">'\
1912 mm = u'<br/>'.join(lm)
1914 # The marked adresses (they start with the mark '[unreachable]',
1915 # '[resigned]', whatever '[xxx]') will not be displayed at all.
1916 # Only the mark will be used instead.
1917 rexMark = re.compile(ur'(?P<mark>\[.*?\])')
1919 for maintainer in self.__maintainersDic[obj.classId]:
1920 address = maintainer[1]
1921 m = rexMark.search(address)
1923 address = u'<span style="color: brown">'\
1924 + m.group(u'mark') + u'</span>'
1926 ee = u'<br/>'.join(le)
1928 # Append the maintainer and e-mail elements.
1929 lst.append(htmlTdTpl % mm)
1930 lst.append(htmlTdTpl % ee)
1932 # The last element contains the readable form of the status.
1933 bgcolor = self.getBgcolorByReadableStatus(obj.readableStatus)
1934 lst.append(htmlTdStatusColorTpl % (bgcolor, obj.readableStatus))
1936 # Join the table data to one table row.
1937 trlst.append(htmlTrTpl % (''.join(lst)))
1939 # Join the table rows and insert into the template.
1940 htmlTable = htmlTableTpl % (''.join(trlst))
1942 # Define templates for LaTeX table parts of the documentation.
1943 latexTableTpl = ur'''
1946 \begin{longtable}{|l|l|l|l|}
1948 {\bf Language} & {\bf Maintainer} & {\bf Contact address} & {\bf Status} \\
1956 latexTableTpl = dedent(latexTableTpl)
1957 latexLineTpl = u'\n' + r' %s & %s & {\tt\tiny %s} & %s \\'
1959 # Loop through transl objects in the order of sorted readable names
1960 # and add generate the content of the LaTeX table.
1962 for name, obj in self.langLst:
1963 # For LaTeX, more maintainers for the same language are
1964 # placed on separate rows in the table. The line separator
1965 # in the table is placed explicitly above the first
1966 # maintainer. Prepare the arguments for the LaTeX row template.
1968 if self.__maintainersDic.has_key(obj.classId):
1969 maintainers = self.__maintainersDic[obj.classId]
1971 lang = obj.langReadable
1972 maintainer = None # init
1974 if obj.status == 'En':
1975 # Check whether there is the coupled non-English.
1976 classId = obj.classId[:-2]
1977 if classId in self.__translDic:
1978 langNE = self.__translDic[classId].langReadable
1979 maintainer = u'see the %s language' % langNE
1982 if not maintainer and (obj.classId in self.__maintainersDic):
1983 lm = [ m[0] for m in self.__maintainersDic[obj.classId] ]
1984 maintainer = maintainers[0][0]
1985 email = maintainers[0][1]
1987 status = obj.readableStatus
1989 # Use the template to produce the line of the table and insert
1990 # the hline plus the constructed line into the table content.
1991 # The underscore character must be escaped.
1992 trlst.append(u'\n \\hline')
1993 s = latexLineTpl % (lang, maintainer, email, status)
1994 s = s.replace(u'_', u'\\_')
1997 # List the other maintainers for the language. Do not set
1998 # lang and status for them.
2001 for m in maintainers[1:]:
2004 s = latexLineTpl % (lang, maintainer, email, status)
2005 s = s.replace(u'_', u'\\_')
2008 # Join the table lines and insert into the template.
2009 latexTable = latexTableTpl % (u''.join(trlst))
2011 # Put the HTML and LaTeX parts together and define the dic item.
2012 tplDic['informationTable'] = htmlTable + u'\n' + latexTable
2014 # Insert the symbols into the document template and write it down.
2015 f = codecs.open(fDocName, 'w', 'utf-8')
2016 f.write(doctpl % tplDic)
2019 if __name__ == '__main__':
2021 # Create the manager, build the transl objects, and parse the related
2025 # Generate the language.doc.
2026 trMan.generateLanguageDoc()
2028 # Generate the translator report.
2029 trMan.generateTranslatorReport()