Imported Upstream version 1.8.8
[platform/upstream/doxygen.git] / doc / translator.py
1 """Script to generate reports on translator classes from Doxygen sources.\r
2 \r
3   The main purpose of the script is to extract the information from sources\r
4   related to internationalization (the translator classes). It uses the\r
5   information to generate documentation (language.doc,\r
6   translator_report.txt) from templates (language.tpl, maintainers.txt).\r
7 \r
8   Simply run the script without parameters to get the reports and\r
9   documentation for all supported languages. If you want to generate the\r
10   translator report only for some languages, pass their codes as arguments\r
11   to the script. In that case, the language.doc will not be generated.\r
12   Example:\r
13 \r
14     python translator.py en nl cz\r
15 \r
16   Originally, the script was written in Perl and was known as translator.pl.\r
17   The last Perl version was dated 2002/05/21 (plus some later corrections)\r
18 \r
19                                          Petr Prikryl (prikryl at atlas dot cz)\r
20 \r
21   History:\r
22   --------\r
23   2002/05/21 - This was the last Perl version.\r
24   2003/05/16 - List of language marks can be passed as arguments.\r
25   2004/01/24 - Total reimplementation started: classes TrManager, and Transl.\r
26   2004/02/05 - First version that produces translator report. No language.doc yet.\r
27   2004/02/10 - First fully functional version that generates both the translator\r
28                report and the documentation. It is a bit slower than the\r
29                Perl version, but is much less tricky and much more flexible.\r
30                It also solves some problems that were not solved by the Perl\r
31                version. The translator report content should be more useful\r
32                for developers.\r
33   2004/02/11 - Some tuning-up to provide more useful information.\r
34   2004/04/16 - Added new tokens to the tokenizer (to remove some warnings).\r
35   2004/05/25 - Added from __future__ import generators not to force Python 2.3.\r
36   2004/06/03 - Removed dependency on textwrap module.\r
37   2004/07/07 - Fixed the bug in the fill() function.\r
38   2004/07/21 - Better e-mail mangling for HTML part of language.doc.\r
39              - Plural not used for reporting a single missing method.\r
40              - Removal of not used translator adapters is suggested only\r
41                when the report is not restricted to selected languages\r
42                explicitly via script arguments.\r
43   2004/07/26 - Better reporting of not-needed adapters.\r
44   2004/10/04 - Reporting of not called translator methods added.\r
45   2004/10/05 - Modified to check only doxygen/src sources for the previous report.\r
46   2005/02/28 - Slight modification to generate "mailto.txt" auxiliary file.\r
47   2005/08/15 - Doxygen's root directory determined primarily from DOXYGEN\r
48                environment variable. When not found, then relatively to the script.\r
49   2007/03/20 - The "translate me!" searched in comments and reported if found.\r
50   2008/06/09 - Warning when the MAX_DOT_GRAPH_HEIGHT is still part of trLegendDocs().\r
51   2009/05/09 - Changed HTML output to fit it with XHTML DTD\r
52   2009/09/02 - Added percentage info to the report (implemented / to be implemented).\r
53   2010/02/09 - Added checking/suggestion 'Reimplementation using UTF-8 suggested.\r
54   2010/03/03 - Added [unreachable] prefix used in maintainers.txt.\r
55   2010/05/28 - BOM skipped; minor code cleaning.\r
56   2010/05/31 - e-mail mangled already in maintainers.txt\r
57   2010/08/20 - maintainers.txt to UTF-8, related processin of unicode strings\r
58              - [any mark] introduced instead of [unreachable] only\r
59              - marks hihglighted in HTML\r
60   2010/08/30 - Highlighting in what will be the table in langhowto.html modified.\r
61   2010/09/27 - The underscore in \latexonly part of the generated language.doc\r
62                was prefixed by backslash (was LaTeX related error).\r
63   2013/02/19 - Better diagnostics when translator_xx.h is too crippled.\r
64   2013/06/25 - TranslatorDecoder checks removed after removing the class.\r
65   2013/09/04 - Coloured status in langhowto. *ALMOST up-to-date* category\r
66                of translators introduced.\r
67   2014/06/16 - unified for Python 2.6+ and 3.0+\r
68   """\r
69 \r
70 from __future__ import print_function\r
71 \r
72 import os\r
73 import platform\r
74 import re\r
75 import sys\r
76 import textwrap\r
77 \r
78 \r
79 def xopen(fname, mode='r', encoding='utf-8-sig'):\r
80     '''Unified file opening for Python 2 an Python 3.\r
81 \r
82     Python 2 does not have the encoding argument. Python 3 has one, and\r
83     the default 'utf-8-sig' is used (skips the BOM automatically).\r
84     '''\r
85 \r
86     major, minor, patch = (int(e) for e in platform.python_version_tuple())\r
87     if major == 2:\r
88         return open(fname, mode=mode) # Python 2 without encoding\r
89     else:\r
90         return open(fname, mode=mode, encoding=encoding) # Python 3 with encoding\r
91 \r
92 \r
93 def fill(s):\r
94     """Returns string formated to the wrapped paragraph multiline string.\r
95 \r
96     Replaces whitespaces by one space and then uses he textwrap.fill()."""\r
97 \r
98     # Replace all whitespace by spaces, remove whitespaces that are not\r
99     # necessary, strip the left and right whitespaces, and break the string\r
100     # to list of words.\r
101     rexWS = re.compile(r'\s+')\r
102     lst = rexWS.sub(' ', s).strip().split()\r
103 \r
104     # If the list is not empty, put the words together and form the lines\r
105     # of maximum 70 characters. Build the list of lines.\r
106     lines = []\r
107     if lst:\r
108         line = lst.pop(0)   # no separation space in front of the first word\r
109         for word in lst:\r
110             if len(line) + len(word) < 70:\r
111                 line += ' ' + word\r
112             else:\r
113                 lines.append(line)  # another full line formed\r
114                 line = word         # next line started\r
115         lines.append(line)          # the last line\r
116     return '\n'.join(lines)\r
117 \r
118 \r
119 class Transl:\r
120     """One instance is build for each translator.\r
121 \r
122     The abbreviation of the source file--part after 'translator_'--is used as\r
123     the identification of the object. The empty string is used for the\r
124     abstract Translator class from translator.h. The other information is\r
125     extracted from inside the source file."""\r
126 \r
127     def __init__(self, fname, manager):\r
128         """Bind to the manager and initialize."""\r
129 \r
130         # Store the filename and the reference to the manager object.\r
131         self.fname = fname\r
132         self.manager = manager\r
133 \r
134         # The instance is responsible for loading the source file, so it checks\r
135         # for its existence and quits if something goes wrong.\r
136         if not os.path.isfile(fname):\r
137             sys.stderr.write("\a\nFile '%s' not found!\n" % fname)\r
138             sys.exit(1)\r
139 \r
140         # Initialize the other collected information.\r
141         self.classId = None\r
142         self.baseClassId = None\r
143         self.readableStatus = None   # 'up-to-date', '1.2.3', '1.3', etc.\r
144         self.status = None           # '', '1.2.03', '1.3.00', etc.\r
145         self.lang = None             # like 'Brasilian'\r
146         self.langReadable = None     # like 'Brasilian Portuguese'\r
147         self.note = None             # like 'should be cleaned up'\r
148         self.prototypeDic = {}       # uniPrototype -> prototype\r
149         self.translateMeText = 'translate me!'\r
150         self.translateMeFlag = False # comments with "translate me!" found\r
151         self.txtMAX_DOT_GRAPH_HEIGHT_flag = False # found in string in trLegendDocs()\r
152         self.obsoleteMethods = None  # list of prototypes to be removed\r
153         self.missingMethods = None   # list of prototypes to be implemented\r
154         self.implementedMethods = None  # list of implemented required methods\r
155         self.adaptMinClass = None    # The newest adapter class that can be used\r
156 \r
157     def __tokenGenerator(self):\r
158         """Generator that reads the file and yields tokens as 4-tuples.\r
159 \r
160         The tokens have the form (tokenId, tokenString, lineNo). The\r
161         last returned token has the form ('eof', None, None). When trying\r
162         to access next token afer that, the exception would be raised."""\r
163 \r
164         # Set the dictionary for recognizing tokenId for keywords, separators\r
165         # and the similar categories. The key is the string to be recognized,\r
166         # the value says its token identification.\r
167         tokenDic = { 'class':     'class',\r
168                      'const':     'const',\r
169                      'public':    'public',\r
170                      'protected': 'protected',\r
171                      'private':   'private',\r
172                      'static':    'static',\r
173                      'virtual':   'virtual',\r
174                      ':':         'colon',\r
175                      ';':         'semic',\r
176                      ',':         'comma',\r
177                      '[':         'lsqbra',\r
178                      ']':         'rsqbra',\r
179                      '(':         'lpar',\r
180                      ')':         'rpar',\r
181                      '{':         'lcurly',\r
182                      '}':         'rcurly',\r
183                      '=':         'assign',\r
184                      '*':         'star',\r
185                      '&':         'amp',\r
186                      '+':         'plus',\r
187                      '-':         'minus',\r
188                      '!':         'excl',\r
189                      '?':         'qmark',\r
190                      '<':         'lt',\r
191                      '>':         'gt',\r
192                      "'":         'quot',\r
193                      '"':         'dquot',\r
194                      '.':         'dot',\r
195                      '%':         'perc',\r
196                      '~':         'tilde',\r
197                      '^':         'caret',\r
198                    }\r
199 \r
200         # Regular expression for recognizing identifiers.\r
201         rexId = re.compile(r'^[a-zA-Z]\w*$')\r
202 \r
203         # Open the file for reading and extracting tokens until the eof.\r
204         # Initialize the finite automaton.\r
205         f = xopen(self.fname)\r
206         lineNo = 0\r
207         line = ''         # init -- see the pos initialization below\r
208         linelen = 0       # init\r
209         pos = 100         # init -- pos after the end of line\r
210         status = 0\r
211 \r
212         tokenId = None    # init\r
213         tokenStr = ''     # init -- the characters will be appended.\r
214         tokenLineNo = 0\r
215 \r
216         while status != 777:\r
217 \r
218             # Get the next character. Read next line first, if necessary.\r
219             if pos < linelen:\r
220                 c = line[pos]\r
221             else:\r
222                 lineNo += 1\r
223                 line = f.readline()\r
224                 linelen = len(line)\r
225                 pos = 0\r
226                 if line == '':         # eof\r
227                     status = 777\r
228                 else:\r
229                     c = line[pos]\r
230 \r
231             # Consume the character based on the status\r
232 \r
233             if status == 0:     # basic status\r
234 \r
235                 # This is the initial status. If tokenId is set, yield the\r
236                 # token here and only here (except when eof is found).\r
237                 # Initialize the token variables after the yield.\r
238                 if tokenId:\r
239                     # If it is an unknown item, it can still be recognized\r
240                     # here. Keywords and separators are the example.\r
241                     if tokenId == 'unknown':\r
242                         if tokenStr in tokenDic:\r
243                             tokenId = tokenDic[tokenStr]\r
244                         elif tokenStr.isdigit():\r
245                             tokenId = 'num'\r
246                         elif rexId.match(tokenStr):\r
247                             tokenId = 'id'\r
248                         else:\r
249                             msg = '\aWarning: unknown token "' + tokenStr + '"'\r
250                             msg += '\tfound on line %d' % tokenLineNo\r
251                             msg += ' in "' + self.fname + '".\n'\r
252                             sys.stderr.write(msg)\r
253 \r
254                     yield (tokenId, tokenStr, tokenLineNo)\r
255 \r
256                     # If it is a comment that contains the self.translateMeText\r
257                     # string, set the flag -- the situation will be reported.\r
258                     if tokenId == 'comment' and tokenStr.find(self.translateMeText) >= 0:\r
259                         self.translateMeFlag = True\r
260 \r
261                     tokenId = None\r
262                     tokenStr = ''\r
263                     tokenLineNo = 0\r
264 \r
265                 # Now process the character. When we just skip it (spaces),\r
266                 # stay in this status. All characters that will be part of\r
267                 # some token cause moving to the specific status. And only\r
268                 # when moving to the status == 0 (or the final state 777),\r
269                 # the token is yielded. With respect to that the automaton\r
270                 # behaves as Moore's one (output bound to status). When\r
271                 # collecting tokens, the automaton is the Mealy's one\r
272                 # (actions bound to transitions).\r
273                 if c.isspace():\r
274                     pass                 # just skip whitespace characters\r
275                 elif c == '/':           # Possibly comment starts here, but\r
276                     tokenId = 'unknown'  # it could be only a slash in code.\r
277                     tokenStr = c\r
278                     tokenLineNo = lineNo\r
279                     status = 1\r
280                 elif c == '#':\r
281                     tokenId = 'preproc'  # preprocessor directive\r
282                     tokenStr = c\r
283                     tokenLineNo = lineNo\r
284                     status = 5\r
285                 elif c == '"':           # string starts here\r
286                     tokenId = 'string'\r
287                     tokenStr = c\r
288                     tokenLineNo = lineNo\r
289                     status = 6\r
290                 elif c == "'":           # char literal starts here\r
291                     tokenId = 'charlit'\r
292                     tokenStr = c\r
293                     tokenLineNo = lineNo\r
294                     status = 8\r
295                 elif c in tokenDic:  # known one-char token\r
296                     tokenId = tokenDic[c]\r
297                     tokenStr = c\r
298                     tokenLineNo = lineNo\r
299                     # stay in this state to yield token immediately\r
300                 else:\r
301                     tokenId = 'unknown'  # totally unknown\r
302                     tokenStr = c\r
303                     tokenLineNo = lineNo\r
304                     status = 333\r
305 \r
306                 pos += 1                 # move position in any case\r
307 \r
308             elif status == 1:            # possibly a comment\r
309                 if c == '/':             # ... definitely the C++ comment\r
310                     tokenId = 'comment'\r
311                     tokenStr += c\r
312                     pos += 1\r
313                     status = 2\r
314                 elif c == '*':           # ... definitely the C comment\r
315                     tokenId = 'comment'\r
316                     tokenStr += c\r
317                     pos += 1\r
318                     status = 3\r
319                 else:\r
320                     status = 0           # unrecognized, don't move pos\r
321 \r
322             elif status == 2:            # inside the C++ comment\r
323                 if c == '\n':            # the end of C++ comment\r
324                     status = 0           # yield the token\r
325                 else:\r
326                     tokenStr += c        # collect the C++ comment\r
327                 pos += 1\r
328 \r
329             elif status == 3:            # inside the C comment\r
330                 if c == '*':             # possibly the end of the C comment\r
331                     tokenStr += c\r
332                     status = 4\r
333                 else:\r
334                     tokenStr += c        # collect the C comment\r
335                 pos += 1\r
336 \r
337             elif status == 4:            # possibly the end of the C comment\r
338                 if c == '/':             # definitely the end of the C comment\r
339                     tokenStr += c\r
340                     status = 0           # yield the token\r
341                 elif c == '*':           # more stars inside the comment\r
342                     tokenStr += c\r
343                 else:\r
344                     tokenStr += c        # this cannot be the end of comment\r
345                     status = 3\r
346                 pos += 1\r
347 \r
348             elif status == 5:            # inside the preprocessor directive\r
349                 if c == '\n':            # the end of the preproc. command\r
350                     status = 0           # yield the token\r
351                 else:\r
352                     tokenStr += c        # collect the preproc\r
353                 pos += 1\r
354 \r
355             elif status == 6:            # inside the string\r
356                 if c == '\\':            # escaped char inside the string\r
357                     tokenStr += c\r
358                     status = 7\r
359                 elif c == '"':           # end of the string\r
360                     tokenStr += c\r
361                     status = 0\r
362                 else:\r
363                     tokenStr += c        # collect the chars of the string\r
364                 pos += 1\r
365 \r
366             elif status == 7:            # escaped char inside the string\r
367                 tokenStr += c            # collect the char of the string\r
368                 status = 6\r
369                 pos += 1\r
370 \r
371             elif status == 8:            # inside the char literal\r
372                 tokenStr += c            # collect the char of the literal\r
373                 status = 9\r
374                 pos += 1\r
375 \r
376             elif status == 9:            # end of char literal expected\r
377                 if c == "'":             # ... and found\r
378                     tokenStr += c\r
379                     status = 0\r
380                     pos += 1\r
381                 else:\r
382                     tokenId = 'error'    # end of literal was expected\r
383                     tokenStr += c\r
384                     status = 0\r
385 \r
386             elif status == 333:          # start of the unknown token\r
387                 if c.isspace():\r
388                     pos += 1\r
389                     status = 0           # tokenId may be determined later\r
390                 elif c in tokenDic:  # separator, don't move pos\r
391                     status = 0\r
392                 else:\r
393                     tokenStr += c        # collect\r
394                     pos += 1\r
395 \r
396         # We should have finished in the final status. If some token\r
397         # have been extracted, yield it first.\r
398         assert(status == 777)\r
399         if tokenId:\r
400             yield (tokenId, tokenStr, tokenLineNo)\r
401             tokenId = None\r
402             tokenStr = ''\r
403             tokenLineNo = 0\r
404 \r
405         # The file content is processed. Close the file. Then always yield\r
406         # the eof token.\r
407         f.close()\r
408         yield ('eof', None, None)\r
409 \r
410 \r
411     def __collectClassInfo(self, tokenIterator):\r
412         """Collect the information about the class and base class.\r
413 \r
414         The tokens including the opening left curly brace of the class are\r
415         consumed."""\r
416 \r
417         status = 0  # initial state\r
418 \r
419         while status != 777:   # final state\r
420 \r
421             # Always assume that the previous tokens were processed. Get\r
422             # the next one.\r
423             tokenId, tokenStr, tokenLineNo = next(tokenIterator)\r
424 \r
425             # Process the token and never return back.\r
426             if status == 0:    # waiting for the 'class' keyword.\r
427                 if tokenId == 'class':\r
428                     status = 1\r
429 \r
430             elif status == 1:  # expecting the class identification\r
431                 if tokenId == 'id':\r
432                     self.classId = tokenStr\r
433                     status = 2\r
434                 else:\r
435                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
436 \r
437             elif status == 2:  # expecting the curly brace or base class info\r
438                 if tokenId == 'lcurly':\r
439                     status = 777        # correctly finished\r
440                 elif tokenId == 'colon':\r
441                     status = 3\r
442                 else:\r
443                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
444 \r
445             elif status == 3:  # expecting the 'public' in front of base class id\r
446                 if tokenId == 'public':\r
447                     status = 4\r
448                 else:\r
449                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
450 \r
451             elif status == 4:  # expecting the base class id\r
452                 if tokenId == 'id':\r
453                     self.baseClassId = tokenStr\r
454                     status = 5\r
455                 else:\r
456                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
457 \r
458             elif status == 5:  # expecting the curly brace and quitting\r
459                 if tokenId == 'lcurly':\r
460                     status = 777        # correctly finished\r
461                 elif tokenId == 'comment':\r
462                     pass\r
463                 else:\r
464                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
465 \r
466         # Extract the status of the TranslatorXxxx class. The readable form\r
467         # will be used in reports the status form is a string that can be\r
468         # compared lexically (unified length, padding with zeros, etc.).\r
469         if self.baseClassId:\r
470             lst = self.baseClassId.split('_')\r
471             if lst[0] == 'Translator':\r
472                 self.readableStatus = 'up-to-date'\r
473                 self.status = ''\r
474             elif lst[0] == 'TranslatorAdapter':\r
475                 self.status = lst[1] + '.' + lst[2]\r
476                 self.readableStatus = self.status\r
477                 if len(lst) > 3:        # add the last part of the number\r
478                     self.status += '.' + ('%02d' % int(lst[3]))\r
479                     self.readableStatus += '.' + lst[3]\r
480                 else:\r
481                     self.status += '.00'\r
482             elif lst[0] == 'TranslatorEnglish':\r
483                 # Obsolete or Based on English.\r
484                 if self.classId[-2:] == 'En':\r
485                     self.readableStatus = 'English based'\r
486                     self.status = 'En'\r
487                 else:\r
488                     self.readableStatus = 'obsolete'\r
489                     self.status = '0.0.00'\r
490 \r
491             # Check whether status was set, or set 'strange'.\r
492             if self.status == None:\r
493                 self.status = 'strange'\r
494             if not self.readableStatus:\r
495                 self.readableStatus = 'strange'\r
496 \r
497             # Extract the name of the language and the readable form.\r
498             self.lang = self.classId[10:]  # without 'Translator'\r
499             if self.lang == 'Brazilian':\r
500                 self.langReadable = 'Brazilian Portuguese'\r
501             elif self.lang == 'Chinesetraditional':\r
502                 self.langReadable = 'Chinese Traditional'\r
503             else:\r
504                 self.langReadable = self.lang\r
505 \r
506 \r
507     def __unexpectedToken(self, status, tokenId, tokenLineNo):\r
508         """Reports unexpected token and quits with exit code 1."""\r
509 \r
510         import inspect\r
511         calledFrom = inspect.stack()[1][3]\r
512         msg = "\a\nUnexpected token '%s' on the line %d in '%s'.\n"\r
513         msg = msg % (tokenId, tokenLineNo, self.fname)\r
514         msg += 'status = %d in %s()\n' % (status, calledFrom)\r
515         sys.stderr.write(msg)\r
516         sys.exit(1)\r
517 \r
518 \r
519     def collectPureVirtualPrototypes(self):\r
520         """Returns dictionary 'unified prototype' -> 'full prototype'.\r
521 \r
522         The method is expected to be called only for the translator.h. It\r
523         extracts only the pure virtual method and build the dictionary where\r
524         key is the unified prototype without argument identifiers."""\r
525 \r
526         # Prepare empty dictionary that will be returned.\r
527         resultDic = {}\r
528 \r
529         # Start the token generator which parses the class source file.\r
530         tokenIterator = self.__tokenGenerator()\r
531 \r
532         # Collect the class and the base class identifiers.\r
533         self.__collectClassInfo(tokenIterator)\r
534         assert(self.classId == 'Translator')\r
535 \r
536         # Let's collect readable form of the public virtual pure method\r
537         # prototypes in the readable form -- as defined in translator.h.\r
538         # Let's collect also unified form of the same prototype that omits\r
539         # everything that can be omitted, namely 'virtual' and argument\r
540         # identifiers.\r
541         prototype = ''    # readable prototype (with everything)\r
542         uniPrototype = '' # unified prototype (without arg. identifiers)\r
543 \r
544         # Collect the pure virtual method prototypes. Stop on the closing\r
545         # curly brace followed by the semicolon (end of class).\r
546         status = 0\r
547         curlyCnt = 0      # counter for the level of curly braces\r
548 \r
549         # Loop until the final state 777 is reached. The errors are processed\r
550         # immediately. In this implementation, it always quits the application.\r
551         while status != 777:\r
552 \r
553             # Get the next token.\r
554             tokenId, tokenStr, tokenLineNo = next(tokenIterator)\r
555 \r
556             if status == 0:      # waiting for 'public:'\r
557                 if tokenId == 'public':\r
558                     status = 1\r
559 \r
560             elif status == 1:    # colon after the 'public'\r
561                 if tokenId == 'colon':\r
562                     status = 2\r
563                 else:\r
564                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
565 \r
566             elif status == 2:    # waiting for 'virtual'\r
567                 if tokenId == 'virtual':\r
568                     prototype = tokenStr  # but not to unified prototype\r
569                     status = 3\r
570                 elif tokenId == 'comment':\r
571                     pass\r
572                 elif tokenId == 'rcurly':\r
573                     status = 11         # expected end of class\r
574                 else:\r
575                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
576 \r
577             elif status == 3:    # return type of the method expected\r
578                 if tokenId == 'id':\r
579                     prototype += ' ' + tokenStr\r
580                     uniPrototype = tokenStr  # start collecting the unified prototype\r
581                     status = 4\r
582                 elif tokenId == 'tilde':\r
583                     status = 4\r
584                 else:\r
585                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
586 \r
587             elif status == 4:    # method identifier expected\r
588                 if tokenId == 'id':\r
589                     prototype += ' ' + tokenStr\r
590                     uniPrototype += ' ' + tokenStr\r
591                     status = 5\r
592                 else:\r
593                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
594 \r
595             elif status == 5:    # left bracket of the argument list expected\r
596                 if tokenId == 'lpar':\r
597                     prototype += tokenStr\r
598                     uniPrototype += tokenStr\r
599                     status = 6\r
600                 else:\r
601                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
602 \r
603             elif status == 6:    # collecting arguments of the method\r
604                 if tokenId == 'rpar':\r
605                     prototype += tokenStr\r
606                     uniPrototype += tokenStr\r
607                     status = 7\r
608                 elif tokenId == 'const':\r
609                     prototype += tokenStr\r
610                     uniPrototype += tokenStr\r
611                     status = 12\r
612                 elif tokenId == 'id':           # type identifier\r
613                     prototype += tokenStr\r
614                     uniPrototype += tokenStr\r
615                     status = 13\r
616                 else:\r
617                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
618 \r
619             elif status == 7:    # assignment expected or left curly brace\r
620                 if tokenId == 'assign':\r
621                     status = 8\r
622                 elif tokenId == 'lcurly':\r
623                     curlyCnt = 1      # method body entered\r
624                     status = 10\r
625                 else:\r
626                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
627 \r
628             elif status == 8:    # zero expected\r
629                 if tokenId == 'num' and tokenStr == '0':\r
630                     status = 9\r
631                 else:\r
632                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
633 \r
634             elif status == 9:    # after semicolon, produce the dic item\r
635                 if tokenId == 'semic':\r
636                     assert(uniPrototype not in resultDic)\r
637                     resultDic[uniPrototype] = prototype\r
638                     status = 2\r
639                 else:\r
640                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
641 \r
642             elif status == 10:   # consuming the body of the method\r
643                 if tokenId == 'rcurly':\r
644                     curlyCnt -= 1\r
645                     if curlyCnt == 0:\r
646                         status = 2     # body consumed\r
647                 elif tokenId == 'lcurly':\r
648                     curlyCnt += 1\r
649 \r
650             elif status == 11:   # probably the end of class\r
651                 if tokenId == 'semic':\r
652                     status = 777\r
653                 else:\r
654                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
655 \r
656             elif status == 12:   # type id for argument expected\r
657                 if tokenId == 'id':\r
658                     prototype += ' ' + tokenStr\r
659                     uniPrototype += ' ' + tokenStr\r
660                     status = 13\r
661                 else:\r
662                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
663 \r
664             elif status == 13:   # namespace qualification or * or & expected\r
665                 if tokenId == 'colon':        # was namespace id\r
666                     prototype += tokenStr\r
667                     uniPrototype += tokenStr\r
668                     status = 14\r
669                 elif tokenId == 'star' or tokenId == 'amp':  # pointer or reference\r
670                     prototype += ' ' + tokenStr\r
671                     uniPrototype += ' ' + tokenStr\r
672                     status = 16\r
673                 elif tokenId == 'id':         # argument identifier\r
674                     prototype += ' ' + tokenStr\r
675                     # don't put this into unified prototype\r
676                     status = 17\r
677                 else:\r
678                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
679 \r
680             elif status == 14:   # second colon for namespace:: expected\r
681                 if tokenId == 'colon':\r
682                     prototype += tokenStr\r
683                     uniPrototype += tokenStr\r
684                     status = 15\r
685                 else:\r
686                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
687 \r
688             elif status == 15:   # type after namespace:: expected\r
689                 if tokenId == 'id':\r
690                     prototype += tokenStr\r
691                     uniPrototype += tokenStr\r
692                     status = 13\r
693                 else:\r
694                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
695 \r
696             elif status == 16:   # argument identifier expected\r
697                 if tokenId == 'id':\r
698                     prototype += ' ' + tokenStr\r
699                     # don't put this into unified prototype\r
700                     status = 17\r
701                 else:\r
702                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
703 \r
704             elif status == 17:   # comma or ')' after argument identifier expected\r
705                 if tokenId == 'comma':\r
706                     prototype += ', '\r
707                     uniPrototype += ', '\r
708                     status = 6\r
709                 elif tokenId == 'rpar':\r
710                     prototype += tokenStr\r
711                     uniPrototype += tokenStr\r
712                     status = 7\r
713                 else:\r
714                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
715 \r
716         # Eat the rest of the source to cause closing the file.\r
717         while tokenId != 'eof':\r
718             tokenId, tokenStr, tokenLineNo = next(tokenIterator)\r
719 \r
720         # Return the resulting dictionary with 'uniPrototype -> prototype'.\r
721         return resultDic\r
722 \r
723 \r
724     def __collectPublicMethodPrototypes(self, tokenIterator):\r
725         """Collects prototypes of public methods and fills self.prototypeDic.\r
726 \r
727         The dictionary is filled by items: uniPrototype -> prototype.\r
728         The method is expected to be called only for TranslatorXxxx classes,\r
729         i.e. for the classes that implement translation to some language.\r
730         It assumes that the openning curly brace of the class was already\r
731         consumed. The source is consumed until the end of the class.\r
732         The caller should consume the source until the eof to cause closing\r
733         the source file."""\r
734 \r
735         assert(self.classId != 'Translator')\r
736         assert(self.baseClassId != None)\r
737 \r
738         # The following finite automaton slightly differs from the one\r
739         # inside self.collectPureVirtualPrototypes(). It produces the\r
740         # dictionary item just after consuming the body of the method\r
741         # (transition from from state 10 to state 2). It also does not allow\r
742         # definitions of public pure virtual methods, except for\r
743         # TranslatorAdapterBase (states 8 and 9). Argument identifier inside\r
744         # method argument lists can be omitted or commented.\r
745         #\r
746         # Let's collect readable form of all public method prototypes in\r
747         # the readable form -- as defined in the source file.\r
748         # Let's collect also unified form of the same prototype that omits\r
749         # everything that can be omitted, namely 'virtual' and argument\r
750         # identifiers.\r
751         prototype = ''    # readable prototype (with everything)\r
752         uniPrototype = '' # unified prototype (without arg. identifiers)\r
753         warning = ''      # warning message -- if something special detected\r
754         methodId = None   # processed method id\r
755 \r
756         # Collect the method prototypes. Stop on the closing\r
757         # curly brace followed by the semicolon (end of class).\r
758         status = 0\r
759         curlyCnt = 0      # counter for the level of curly braces\r
760 \r
761         # Loop until the final state 777 is reached. The errors are processed\r
762         # immediately. In this implementation, it always quits the application.\r
763         while status != 777:\r
764 \r
765             # Get the next token.\r
766             tokenId, tokenStr, tokenLineNo = next(tokenIterator)\r
767 \r
768             if status == 0:      # waiting for 'public:'\r
769                 if tokenId == 'public':\r
770                     status = 1\r
771                 elif tokenId == 'eof':  # non-public things until the eof\r
772                     status = 777\r
773 \r
774             elif status == 1:    # colon after the 'public'\r
775                 if tokenId == 'colon':\r
776                     status = 2\r
777                 else:\r
778                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
779 \r
780             elif status == 2:    # waiting for 'virtual' (can be omitted)\r
781                 if tokenId == 'virtual':\r
782                     prototype = tokenStr  # but not to unified prototype\r
783                     status = 3\r
784                 elif tokenId == 'id':     # 'virtual' was omitted\r
785                     prototype = tokenStr\r
786                     uniPrototype = tokenStr  # start collecting the unified prototype\r
787                     status = 4\r
788                 elif tokenId == 'comment':\r
789                     pass\r
790                 elif tokenId == 'protected' or tokenId == 'private':\r
791                     status = 0\r
792                 elif tokenId == 'rcurly':\r
793                     status = 11         # expected end of class\r
794                 else:\r
795                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
796 \r
797             elif status == 3:    # return type of the method expected\r
798                 if tokenId == 'id':\r
799                     prototype += ' ' + tokenStr\r
800                     uniPrototype = tokenStr  # start collecting the unified prototype\r
801                     status = 4\r
802                 else:\r
803                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
804 \r
805             elif status == 4:    # method identifier expected\r
806                 if tokenId == 'id':\r
807                     prototype += ' ' + tokenStr\r
808                     uniPrototype += ' ' + tokenStr\r
809                     methodId = tokenStr    # for reporting\r
810                     status = 5\r
811                 else:\r
812                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
813 \r
814             elif status == 5:    # left bracket of the argument list expected\r
815                 if tokenId == 'lpar':\r
816                     prototype += tokenStr\r
817                     uniPrototype += tokenStr\r
818                     status = 6\r
819                 else:\r
820                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
821 \r
822             elif status == 6:    # collecting arguments of the method\r
823                 if tokenId == 'rpar':\r
824                     prototype += tokenStr\r
825                     uniPrototype += tokenStr\r
826                     status = 7\r
827                 elif tokenId == 'const':\r
828                     prototype += tokenStr\r
829                     uniPrototype += tokenStr\r
830                     status = 12\r
831                 elif tokenId == 'id':           # type identifier\r
832                     prototype += tokenStr\r
833                     uniPrototype += tokenStr\r
834                     status = 13\r
835                 else:\r
836                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
837 \r
838             elif status == 7:    # left curly brace expected\r
839                 if tokenId == 'lcurly':\r
840                     curlyCnt = 1      # method body entered\r
841                     status = 10\r
842                 elif tokenId == 'comment':\r
843                     pass\r
844                 elif tokenId == 'assign': # allowed only for TranslatorAdapterBase\r
845                     assert(self.classId == 'TranslatorAdapterBase')\r
846                     status = 8\r
847                 else:\r
848                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
849 \r
850             elif status == 8:    # zero expected (TranslatorAdapterBase)\r
851                 assert(self.classId == 'TranslatorAdapterBase')\r
852                 if tokenId == 'num' and tokenStr == '0':\r
853                     status = 9\r
854                 else:\r
855                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
856 \r
857             elif status == 9:    # after semicolon (TranslatorAdapterBase)\r
858                 assert(self.classId == 'TranslatorAdapterBase')\r
859                 if tokenId == 'semic':\r
860                     status = 2\r
861                 else:\r
862                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
863 \r
864             elif status == 10:   # consuming the body of the method, then dic item\r
865                 if tokenId == 'rcurly':\r
866                     curlyCnt -= 1\r
867                     if curlyCnt == 0:\r
868                         # Check for possible copy/paste error when name\r
869                         # of the method was not corrected (i.e. the same\r
870                         # name already exists).\r
871                         if uniPrototype in self.prototypeDic:\r
872                             msg = "'%s' prototype found again (duplicity)\n"\r
873                             msg += "in '%s'.\n" % self.fname\r
874                             msg = msg % uniPrototype\r
875                             sys.stderr.write(msg)\r
876                             assert False\r
877 \r
878                         assert(uniPrototype not in self.prototypeDic)\r
879                         # Insert new dictionary item.\r
880                         self.prototypeDic[uniPrototype] = prototype\r
881                         status = 2      # body consumed\r
882                         methodId = None # outside of any method\r
883                 elif tokenId == 'lcurly':\r
884                     curlyCnt += 1\r
885 \r
886                 # Warn in special case.\r
887                 elif methodId == 'trLegendDocs' and tokenId == 'string' \\r
888                     and tokenStr.find('MAX_DOT_GRAPH_HEIGHT') >= 0:\r
889                         self.txtMAX_DOT_GRAPH_HEIGHT_flag = True\r
890 \r
891 \r
892             elif status == 11:   # probably the end of class\r
893                 if tokenId == 'semic':\r
894                     status = 777\r
895                 else:\r
896                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
897 \r
898             elif status == 12:   # type id for argument expected\r
899                 if tokenId == 'id':\r
900                     prototype += ' ' + tokenStr\r
901                     uniPrototype += ' ' + tokenStr\r
902                     status = 13\r
903                 else:\r
904                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
905 \r
906             elif status == 13:   # :: or * or & or id or ) expected\r
907                 if tokenId == 'colon':        # was namespace id\r
908                     prototype += tokenStr\r
909                     uniPrototype += tokenStr\r
910                     status = 14\r
911                 elif tokenId == 'star' or tokenId == 'amp':  # pointer or reference\r
912                     prototype += ' ' + tokenStr\r
913                     uniPrototype += ' ' + tokenStr\r
914                     status = 16\r
915                 elif tokenId == 'id':         # argument identifier\r
916                     prototype += ' ' + tokenStr\r
917                     # don't put this into unified prototype\r
918                     status = 17\r
919                 elif tokenId == 'comment':    # probably commented-out identifier\r
920                     prototype += tokenStr\r
921                 elif tokenId == 'rpar':\r
922                     prototype += tokenStr\r
923                     uniPrototype += tokenStr\r
924                     status = 7\r
925                 elif tokenId == 'comma':\r
926                     prototype += ', '\r
927                     uniPrototype += ', '\r
928                     status = 6\r
929                 else:\r
930                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
931 \r
932             elif status == 14:   # second colon for namespace:: expected\r
933                 if tokenId == 'colon':\r
934                     prototype += tokenStr\r
935                     uniPrototype += tokenStr\r
936                     status = 15\r
937                 else:\r
938                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
939 \r
940             elif status == 15:   # type after namespace:: expected\r
941                 if tokenId == 'id':\r
942                     prototype += tokenStr\r
943                     uniPrototype += tokenStr\r
944                     status = 13\r
945                 else:\r
946                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
947 \r
948             elif status == 16:   # argument identifier or ) expected\r
949                 if tokenId == 'id':\r
950                     prototype += ' ' + tokenStr\r
951                     # don't put this into unified prototype\r
952                     status = 17\r
953                 elif tokenId == 'rpar':\r
954                     prototype += tokenStr\r
955                     uniPrototype += tokenStr\r
956                     status = 7\r
957                 elif tokenId == 'comment':\r
958                     prototype += tokenStr\r
959                 else:\r
960                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
961 \r
962             elif status == 17:   # comma or ')' after argument identifier expected\r
963                 if tokenId == 'comma':\r
964                     prototype += ', '\r
965                     uniPrototype += ', '\r
966                     status = 6\r
967                 elif tokenId == 'rpar':\r
968                     prototype += tokenStr\r
969                     uniPrototype += tokenStr\r
970                     status = 7\r
971                 else:\r
972                     self.__unexpectedToken(status, tokenId, tokenLineNo)\r
973 \r
974 \r
975 \r
976     def collectAdapterPrototypes(self):\r
977         """Returns the dictionary of prototypes implemented by adapters.\r
978 \r
979         It is created to process the translator_adapter.h. The returned\r
980         dictionary has the form: unifiedPrototype -> (version, classId)\r
981         thus by looking for the prototype, we get the information what is\r
982         the newest (least adapting) adapter that is sufficient for\r
983         implementing the method."""\r
984 \r
985         # Start the token generator which parses the class source file.\r
986         assert(os.path.split(self.fname)[1] == 'translator_adapter.h')\r
987         tokenIterator = self.__tokenGenerator()\r
988 \r
989         # Get the references to the involved dictionaries.\r
990         reqDic = self.manager.requiredMethodsDic\r
991 \r
992         # Create the empty dictionary that will be returned.\r
993         adaptDic = {}\r
994 \r
995 \r
996         # Loop through the source of the adapter file until no other adapter\r
997         # class is found.\r
998         while True:\r
999             try:\r
1000                 # Collect the class and the base class identifiers.\r
1001                 self.__collectClassInfo(tokenIterator)\r
1002 \r
1003                 # Extract the comparable version of the adapter class.\r
1004                 # Note: The self.status as set by self.__collectClassInfo()\r
1005                 # contains similar version, but is related to the base class,\r
1006                 # not to the class itself.\r
1007                 lst = self.classId.split('_')\r
1008                 version = ''\r
1009                 if lst[0] == 'TranslatorAdapter': # TranslatorAdapterBase otherwise\r
1010                     version = lst[1] + '.' + lst[2]\r
1011                     if len(lst) > 3:        # add the last part of the number\r
1012                         version += '.' + ('%02d' % int(lst[3]))\r
1013                     else:\r
1014                         version += '.00'\r
1015 \r
1016                 # Collect the prototypes of implemented public methods.\r
1017                 self.__collectPublicMethodPrototypes(tokenIterator)\r
1018 \r
1019                 # For the required methods, update the dictionary of methods\r
1020                 # implemented by the adapter.\r
1021                 for protoUni in self.prototypeDic:\r
1022                     if protoUni in reqDic:\r
1023                         # This required method will be marked as implemented\r
1024                         # by this adapter class. This implementation assumes\r
1025                         # that newer adapters do not reimplement any required\r
1026                         # methods already implemented by older adapters.\r
1027                         assert(protoUni not in adaptDic)\r
1028                         adaptDic[protoUni] = (version, self.classId)\r
1029 \r
1030                 # Clear the dictionary object and the information related\r
1031                 # to the class as the next adapter class is to be processed.\r
1032                 self.prototypeDic.clear()\r
1033                 self.classId = None\r
1034                 self.baseClassId = None\r
1035 \r
1036             except StopIteration:\r
1037                 break\r
1038 \r
1039         # Return the result dictionary.\r
1040         return adaptDic\r
1041 \r
1042 \r
1043     def processing(self):\r
1044         """Processing of the source file -- only for TranslatorXxxx classes."""\r
1045 \r
1046         # Start the token generator which parses the class source file.\r
1047         tokenIterator = self.__tokenGenerator()\r
1048 \r
1049         # Collect the class and the base class identifiers.\r
1050         self.__collectClassInfo(tokenIterator)\r
1051         assert(self.classId != 'Translator')\r
1052         assert(self.classId[:17] != 'TranslatorAdapter')\r
1053 \r
1054         # Collect the prototypes of implemented public methods.\r
1055         self.__collectPublicMethodPrototypes(tokenIterator)\r
1056 \r
1057         # Eat the rest of the source to cause closing the file.\r
1058         while True:\r
1059             try:\r
1060                 t = next(tokenIterator)\r
1061             except StopIteration:\r
1062                 break\r
1063 \r
1064         # Shorthands for the used dictionaries.\r
1065         reqDic = self.manager.requiredMethodsDic\r
1066         adaptDic = self.manager.adaptMethodsDic\r
1067         myDic = self.prototypeDic\r
1068 \r
1069         # Build the list of obsolete methods.\r
1070         self.obsoleteMethods = []\r
1071         for p in myDic:\r
1072             if p not in reqDic:\r
1073                 self.obsoleteMethods.append(p)\r
1074         self.obsoleteMethods.sort()\r
1075 \r
1076         # Build the list of missing methods and the list of implemented\r
1077         # required methods.\r
1078         self.missingMethods = []\r
1079         self.implementedMethods = []\r
1080         for p in reqDic:\r
1081             if p in myDic:\r
1082                 self.implementedMethods.append(p)\r
1083             else:\r
1084                 self.missingMethods.append(p)\r
1085         self.missingMethods.sort()\r
1086         self.implementedMethods.sort()\r
1087 \r
1088         # Check whether adapter must be used or suggest the newest one.\r
1089         # Change the status and set the note accordingly.\r
1090         if self.baseClassId != 'Translator':\r
1091             if not self.missingMethods:\r
1092                 self.note = 'Change the base class to Translator.'\r
1093                 self.status = ''\r
1094                 self.readableStatus = 'almost up-to-date'\r
1095             elif self.baseClassId != 'TranslatorEnglish':\r
1096                 # The translator uses some of the adapters.\r
1097                 # Look at the missing methods and check what adapter\r
1098                 # implements them. Remember the one with the lowest version.\r
1099                 adaptMinVersion = '9.9.99'\r
1100                 adaptMinClass = 'TranslatorAdapter_9_9_99'\r
1101                 for uniProto in self.missingMethods:\r
1102                     if uniProto in adaptDic:\r
1103                         version, cls = adaptDic[uniProto]\r
1104                         if version < adaptMinVersion:\r
1105                             adaptMinVersion = version\r
1106                             adaptMinClass = cls\r
1107 \r
1108                 # Test against the current status -- preserve the self.status.\r
1109                 # Possibly, the translator implements enough methods to\r
1110                 # use some newer adapter.\r
1111                 status = self.status\r
1112 \r
1113                 # If the version of the used adapter is smaller than\r
1114                 # the required, set the note and update the status as if\r
1115                 # the newer adapter was used.\r
1116                 if adaptMinVersion > status:\r
1117                     self.note = 'Change the base class to %s.' % adaptMinClass\r
1118                     self.status = adaptMinVersion\r
1119                     self.adaptMinClass = adaptMinClass\r
1120                     self.readableStatus = adaptMinVersion # simplified\r
1121 \r
1122         # If everything seems OK, some explicit warning flags still could\r
1123         # be set.\r
1124         if not self.note and self.status == '' and \\r
1125            (self.translateMeFlag or self.txtMAX_DOT_GRAPH_HEIGHT_flag):\r
1126            self.note = ''\r
1127            if self.translateMeFlag:\r
1128                self.note += 'The "%s" found in a comment.' % self.translateMeText\r
1129            if self.note != '':\r
1130                self.note += '\n\t\t'\r
1131            if self.txtMAX_DOT_GRAPH_HEIGHT_flag:\r
1132                self.note += 'The MAX_DOT_GRAPH_HEIGHT found in trLegendDocs()'\r
1133 \r
1134         # If everything seems OK, but there are obsolete methods, set\r
1135         # the note to clean-up source. This note will be used only when\r
1136         # the previous code did not set another note (priority).\r
1137         if not self.note and self.status == '' and self.obsoleteMethods:\r
1138             self.note = 'Remove the obsolete methods (never used).'\r
1139 \r
1140         # If there is at least some note but the status suggests it is\r
1141         # otherwise up-to-date, mark is as ALMOST up-to-date.\r
1142         if self.note and self.status == '':\r
1143             self.readableStatus = 'almost up-to-date'\r
1144 \r
1145 \r
1146     def report(self, fout):\r
1147         """Returns the report part for the source as a multiline string.\r
1148 \r
1149         No output for up-to-date translators without problem."""\r
1150 \r
1151         # If there is nothing to report, return immediately.\r
1152         if self.status == '' and not self.note:\r
1153             return\r
1154 \r
1155         # Report the number of not implemented methods.\r
1156         fout.write('\n\n\n')\r
1157         fout.write(self.classId + '   (' + self.baseClassId + ')')\r
1158         percentImplemented = 100    # init\r
1159         allNum = len(self.manager.requiredMethodsDic)\r
1160         if self.missingMethods:\r
1161             num = len(self.missingMethods)\r
1162             percentImplemented = 100 * (allNum - num) / allNum\r
1163             fout.write('  %d' % num)\r
1164             fout.write(' method')\r
1165             if num > 1:\r
1166                 fout.write('s')\r
1167             fout.write(' to implement (%d %%)' % (100 * num / allNum))\r
1168         fout.write('\n' + '-' * len(self.classId))\r
1169 \r
1170         # Write the info about the implemented required methods.\r
1171         fout.write('\n\n  Implements %d' % len(self.implementedMethods))\r
1172         fout.write(' of the required methods (%d %%).' % percentImplemented)\r
1173 \r
1174         # Report the missing method, but only when it is not English-based\r
1175         # translator.\r
1176         if self.missingMethods and self.status != 'En':\r
1177             fout.write('\n\n  Missing methods (should be implemented):\n')\r
1178             reqDic = self.manager.requiredMethodsDic\r
1179             for p in self.missingMethods:\r
1180                 fout.write('\n    ' + reqDic[p])\r
1181 \r
1182         # Always report obsolete methods.\r
1183         if self.obsoleteMethods:\r
1184             fout.write('\n\n  Obsolete methods (should be removed, never used):\n')\r
1185             myDic = self.prototypeDic\r
1186             for p in self.obsoleteMethods:\r
1187                 fout.write('\n    ' + myDic[p])\r
1188 \r
1189         # For English-based translator, report the implemented methods.\r
1190         if self.status == 'En' and self.implementedMethods:\r
1191             fout.write('\n\n  This English-based translator implements ')\r
1192             fout.write('the following methods:\n')\r
1193             reqDic = self.manager.requiredMethodsDic\r
1194             for p in self.implementedMethods:\r
1195                 fout.write('\n    ' + reqDic[p])\r
1196 \r
1197 \r
1198     def getmtime(self):\r
1199         """Returns the last modification time of the source file."""\r
1200         assert(os.path.isfile(self.fname))\r
1201         return os.path.getmtime(self.fname)\r
1202 \r
1203 \r
1204 class TrManager:\r
1205     """Collects basic info and builds subordinate Transl objects."""\r
1206 \r
1207     def __init__(self):\r
1208         """Determines paths, creates and initializes structures.\r
1209 \r
1210         The arguments of the script may explicitly say what languages should\r
1211         be processed. Write the two letter identifications that are used\r
1212         for composing the source filenames, so...\r
1213 \r
1214             python translator.py cz\r
1215 \r
1216         this will process only translator_cz.h source.\r
1217         """\r
1218 \r
1219         # Determine the path to the script and its name.\r
1220         self.script = os.path.abspath(sys.argv[0])\r
1221         self.script_path, self.script_name = os.path.split(self.script)\r
1222         self.script_path = os.path.abspath(self.script_path)\r
1223 \r
1224         # Determine the absolute path to the Doxygen's root subdirectory.\r
1225         # If DOXYGEN environment variable is not found, the directory is\r
1226         # determined from the path of the script.\r
1227         doxy_default = os.path.join(self.script_path, '..')\r
1228         self.doxy_path = os.path.abspath(os.getenv('DOXYGEN', doxy_default))\r
1229 \r
1230         # Get the explicit arguments of the script.\r
1231         self.script_argLst = sys.argv[1:]\r
1232 \r
1233         # Build the path names based on the Doxygen's root knowledge.\r
1234         self.doc_path = os.path.join(self.doxy_path, 'doc')\r
1235         self.src_path = os.path.join(self.doxy_path, 'src')\r
1236 \r
1237         # Create the empty dictionary for Transl object identitied by the\r
1238         # class identifier of the translator.\r
1239         self.__translDic = {}\r
1240 \r
1241         # Create the None dictionary of required methods. The key is the\r
1242         # unified prototype, the value is the full prototype. Set inside\r
1243         # the self.__build().\r
1244         self.requiredMethodsDic = None\r
1245 \r
1246         # Create the empty dictionary that says what method is implemented\r
1247         # by what adapter.\r
1248         self.adaptMethodsDic = {}\r
1249 \r
1250         # The last modification time will capture the modification of this\r
1251         # script, of the translator.h, of the translator_adapter.h (see the\r
1252         # self.__build() for the last two) of all the translator_xx.h files\r
1253         # and of the template for generating the documentation. So, this\r
1254         # time can be compared with modification time of the generated\r
1255         # documentation to decide, whether the doc should be re-generated.\r
1256         self.lastModificationTime = os.path.getmtime(self.script)\r
1257 \r
1258         # Set the names of the translator report text file, of the template\r
1259         # for generating "Internationalization" document, for the generated\r
1260         # file itself, and for the maintainers list.\r
1261         self.translatorReportFileName = 'translator_report.txt'\r
1262         self.maintainersFileName = 'maintainers.txt'\r
1263         self.languageTplFileName = 'language.tpl'\r
1264         self.languageDocFileName = 'language.doc'\r
1265 \r
1266         # The information about the maintainers will be stored\r
1267         # in the dictionary with the following name.\r
1268         self.__maintainersDic = None\r
1269 \r
1270         # Define the other used structures and variables for information.\r
1271         self.langLst = None                   # including English based\r
1272         self.supportedLangReadableStr = None  # coupled En-based as a note\r
1273         self.numLang = None                   # excluding coupled En-based\r
1274         self.doxVersion = None                # Doxygen version\r
1275 \r
1276         # Build objects where each one is responsible for one translator.\r
1277         self.__build()\r
1278 \r
1279 \r
1280     def __build(self):\r
1281         """Find the translator files and build the objects for translators."""\r
1282 \r
1283         # The translator.h must exist (the Transl object will check it),\r
1284         # create the object for it and let it build the dictionary of\r
1285         # required methods.\r
1286         tr = Transl(os.path.join(self.src_path, 'translator.h'), self)\r
1287         self.requiredMethodsDic = tr.collectPureVirtualPrototypes()\r
1288         tim = tr.getmtime()\r
1289         if tim > self.lastModificationTime:\r
1290             self.lastModificationTime = tim\r
1291 \r
1292         # The translator_adapter.h must exist (the Transl object will check it),\r
1293         # create the object for it and store the reference in the dictionary.\r
1294         tr = Transl(os.path.join(self.src_path, 'translator_adapter.h'), self)\r
1295         self.adaptMethodsDic = tr.collectAdapterPrototypes()\r
1296         tim = tr.getmtime()\r
1297         if tim > self.lastModificationTime:\r
1298             self.lastModificationTime = tim\r
1299 \r
1300         # Create the list of the filenames with language translator sources.\r
1301         # If the explicit arguments of the script were typed, process only\r
1302         # those files.\r
1303         if self.script_argLst:\r
1304             lst = ['translator_' + x + '.h' for x in self.script_argLst]\r
1305             for fname in lst:\r
1306                 if not os.path.isfile(os.path.join(self.src_path, fname)):\r
1307                     sys.stderr.write("\a\nFile '%s' not found!\n" % fname)\r
1308                     sys.exit(1)\r
1309         else:\r
1310             lst = os.listdir(self.src_path)\r
1311             lst = [x for x in lst if x[:11] == 'translator_'\r
1312                                    and x[-2:] == '.h'\r
1313                                    and x != 'translator_adapter.h']\r
1314 \r
1315         # Build the object for the translator_xx.h files, and process the\r
1316         # content of the file. Then insert the object to the dictionary\r
1317         # accessed via classId.\r
1318         for fname in lst:\r
1319             fullname = os.path.join(self.src_path, fname)\r
1320             tr = Transl(fullname, self)\r
1321             tr.processing()\r
1322             assert(tr.classId != 'Translator')\r
1323             self.__translDic[tr.classId] = tr\r
1324 \r
1325         # Extract the global information of the processed info.\r
1326         self.__extractProcessedInfo()\r
1327 \r
1328 \r
1329     def __extractProcessedInfo(self):\r
1330         """Build lists and strings of the processed info."""\r
1331 \r
1332         # Build the auxiliary list with strings compound of the status,\r
1333         # readable form of the language, and classId.\r
1334         statLst = []\r
1335         for obj in list(self.__translDic.values()):\r
1336             assert(obj.classId != 'Translator')\r
1337             s = obj.status + '|' + obj.langReadable + '|' + obj.classId\r
1338             statLst.append(s)\r
1339 \r
1340         # Sort the list and extract the object identifiers (classId's) for\r
1341         # the up-to-date translators and English-based translators.\r
1342         statLst.sort()\r
1343         self.upToDateIdLst = [x.split('|')[2] for x in statLst if x[0] == '|']\r
1344         self.EnBasedIdLst = [x.split('|')[2] for x in statLst if x[:2] == 'En']\r
1345 \r
1346         # Reverse the list and extract the TranslatorAdapter based translators.\r
1347         statLst.reverse()\r
1348         self.adaptIdLst = [x.split('|')[2] for x in statLst if x[0].isdigit()]\r
1349 \r
1350         # Build the list of tuples that contain (langReadable, obj).\r
1351         # Sort it by readable name.\r
1352         self.langLst = []\r
1353         for obj in list(self.__translDic.values()):\r
1354             self.langLst.append((obj.langReadable, obj))\r
1355 \r
1356         self.langLst.sort(key=lambda x: x[0])\r
1357 \r
1358         # Create the list with readable language names. If the language has\r
1359         # also the English-based version, modify the item by appending\r
1360         # the note. Number of the supported languages is equal to the length\r
1361         # of the list.\r
1362         langReadableLst = []\r
1363         for name, obj in self.langLst:\r
1364             if obj.status == 'En': continue\r
1365 \r
1366             # Append the 'En' to the classId to possibly obtain the classId\r
1367             # of the English-based object. If the object exists, modify the\r
1368             # name for the readable list of supported languages.\r
1369             classIdEn = obj.classId + 'En'\r
1370             if classIdEn in self.__translDic:\r
1371                 name += ' (+En)'\r
1372 \r
1373             # Append the result name of the language, possibly with note.\r
1374             langReadableLst.append(name)\r
1375 \r
1376         # Create the multiline string of readable language names,\r
1377         # with punctuation, wrapped to paragraph.\r
1378         if len(langReadableLst) == 1:\r
1379             s = langReadableLst[0]\r
1380         elif len(langReadableLst) == 2:\r
1381             s = ' and '.join(langReadableLst)\r
1382         else:\r
1383             s = ', '.join(langReadableLst[:-1]) + ', and '\r
1384             s += langReadableLst[-1]\r
1385 \r
1386         self.supportedLangReadableStr = fill(s + '.')\r
1387 \r
1388         # Find the number of the supported languages. The English based\r
1389         # languages are not counted if the non-English based also exists.\r
1390         self.numLang = len(self.langLst)\r
1391         for name, obj in self.langLst:\r
1392             if obj.status == 'En':\r
1393                 classId = obj.classId[:-2]\r
1394                 if classId in self.__translDic:\r
1395                     self.numLang -= 1    # the couple will be counted as one\r
1396 \r
1397         # Extract the version of Doxygen.\r
1398         f = xopen(os.path.join(self.doxy_path, 'VERSION'))\r
1399         self.doxVersion = f.readline().strip()\r
1400         f.close()\r
1401 \r
1402         # Update the last modification time.\r
1403         for tr in list(self.__translDic.values()):\r
1404             tim = tr.getmtime()\r
1405             if tim > self.lastModificationTime:\r
1406                 self.lastModificationTime = tim\r
1407 \r
1408 \r
1409     def __getNoTrSourceFilesLst(self):\r
1410         """Returns the list of sources to be checked.\r
1411 \r
1412         All .cpp files and also .h files that do not declare or define\r
1413         the translator methods are included in the list. The file names\r
1414         are searched in doxygen/src directory.\r
1415         """\r
1416         files = []\r
1417         for item in os.listdir(self.src_path):\r
1418             # Split the bare name to get the extension.\r
1419             name, ext = os.path.splitext(item)\r
1420             ext = ext.lower()\r
1421 \r
1422             # Include only .cpp and .h files (case independent) and exclude\r
1423             # the files where the checked identifiers are defined.\r
1424             if ext == '.cpp' or (ext == '.h' and name.find('translator') == -1):\r
1425                 fname = os.path.join(self.src_path, item)\r
1426                 assert os.path.isfile(fname) # assumes no directory with the ext\r
1427                 files.append(fname)          # full name\r
1428         return files\r
1429 \r
1430 \r
1431     def __removeUsedInFiles(self, fname, dic):\r
1432         """Removes items for method identifiers that are found in fname.\r
1433 \r
1434         The method reads the content of the file as one string and searches\r
1435         for all identifiers from dic. The identifiers that were found in\r
1436         the file are removed from the dictionary.\r
1437 \r
1438         Note: If more files is to be checked, the files where most items are\r
1439         probably used should be checked first and the resulting reduced\r
1440         dictionary should be used for checking the next files (speed up).\r
1441         """\r
1442         lst_in = list(dic.keys())   # identifiers to be searched for\r
1443 \r
1444         # Read content of the file as one string.\r
1445         assert os.path.isfile(fname)\r
1446         f = xopen(fname)\r
1447         cont = f.read()\r
1448         f.close()\r
1449 \r
1450         # Remove the items for identifiers that were found in the file.\r
1451         while lst_in:\r
1452             item = lst_in.pop(0)\r
1453             if cont.find(item) != -1:\r
1454                 del dic[item]\r
1455 \r
1456 \r
1457     def __checkForNotUsedTrMethods(self):\r
1458         """Returns the dictionary of not used translator methods.\r
1459 \r
1460         The method can be called only after self.requiredMethodsDic has been\r
1461         built. The stripped prototypes are the values, the method identifiers\r
1462         are the keys.\r
1463         """\r
1464         # Build the dictionary of the required method prototypes with\r
1465         # method identifiers used as keys.\r
1466         trdic = {}\r
1467         for prototype in list(self.requiredMethodsDic.keys()):\r
1468             ri = prototype.split('(')[0]\r
1469             identifier = ri.split()[1].strip()\r
1470             trdic[identifier] = prototype\r
1471 \r
1472         # Build the list of source files where translator method identifiers\r
1473         # can be used.\r
1474         files = self.__getNoTrSourceFilesLst()\r
1475 \r
1476         # Loop through the files and reduce the dictionary of id -> proto.\r
1477         for fname in files:\r
1478             self.__removeUsedInFiles(fname, trdic)\r
1479 \r
1480         # Return the dictionary of not used translator methods.\r
1481         return trdic\r
1482 \r
1483 \r
1484     def __emails(self, classId):\r
1485         """Returns the list of maintainer emails.\r
1486 \r
1487         The method returns the list of e-mail adresses for the translator\r
1488         class, but only the addresses that were not marked as [xxx]."""\r
1489         lst = []\r
1490         for m in self.__maintainersDic[classId]:\r
1491             if not m[1].startswith('['):\r
1492                 email = m[1]\r
1493                 email = email.replace(' at ', '@') # Unmangle the mangled e-mail\r
1494                 email = email.replace(' dot ', '.')\r
1495                 lst.append(email)\r
1496         return lst\r
1497 \r
1498 \r
1499     def getBgcolorByReadableStatus(self, readableStatus):\r
1500         if readableStatus == 'up-to-date':\r
1501             color = '#ccffcc'    # green\r
1502         elif readableStatus.startswith('almost'):\r
1503             color = '#ffffff'    # white\r
1504         elif readableStatus.startswith('English'):\r
1505             color = '#ccffcc'    # green\r
1506         elif readableStatus.startswith('1.8'):\r
1507             color = '#ffffcc'    # yellow\r
1508         elif readableStatus.startswith('1.7'):\r
1509             color = '#ffcccc'    # pink\r
1510         elif readableStatus.startswith('1.6'):\r
1511             color = '#ffcccc'    # pink\r
1512         else:\r
1513             color = '#ff5555'    # red\r
1514         return color\r
1515 \r
1516 \r
1517     def generateTranslatorReport(self):\r
1518         """Generates the translator report."""\r
1519 \r
1520         output = os.path.join(self.doc_path, self.translatorReportFileName)\r
1521 \r
1522         # Open the textual report file for the output.\r
1523         f = xopen(output, 'w')\r
1524 \r
1525         # Output the information about the version.\r
1526         f.write('(' + self.doxVersion + ')\n\n')\r
1527 \r
1528         # Output the information about the number of the supported languages\r
1529         # and the list of the languages, or only the note about the explicitly\r
1530         # given languages to process.\r
1531         if self.script_argLst:\r
1532             f.write('The report was generated for the following, explicitly')\r
1533             f.write(' identified languages:\n\n')\r
1534             f.write(self.supportedLangReadableStr + '\n\n')\r
1535         else:\r
1536             f.write('Doxygen supports the following ')\r
1537             f.write(str(self.numLang))\r
1538             f.write(' languages (sorted alphabetically):\n\n')\r
1539             f.write(self.supportedLangReadableStr + '\n\n')\r
1540 \r
1541             # Write the summary about the status of language translators (how\r
1542             # many translators) are up-to-date, etc.\r
1543             s = 'Of them, %d translators are up-to-date, ' % len(self.upToDateIdLst)\r
1544             s += '%d translators are based on some adapter class, ' % len(self.adaptIdLst)\r
1545             s += 'and %d are English based.' % len(self.EnBasedIdLst)\r
1546             f.write(fill(s) + '\n\n')\r
1547 \r
1548         # The e-mail addresses of the maintainers will be collected to\r
1549         # the auxiliary file in the order of translator classes listed\r
1550         # in the translator report.\r
1551         fmail = xopen('mailto.txt', 'w')\r
1552 \r
1553         # Write the list of "up-to-date" translator classes.\r
1554         if self.upToDateIdLst:\r
1555             s = '''The following translator classes are up-to-date (sorted\r
1556                 alphabetically). This means that they derive from the\r
1557                 Translator class, they implement all %d of the required\r
1558                 methods, and even minor problems were not spotted by the script:'''\r
1559             s = s % len(self.requiredMethodsDic)\r
1560             f.write('-' * 70 + '\n')\r
1561             f.write(fill(s) + '\n\n')\r
1562 \r
1563             mailtoLst = []\r
1564             for x in self.upToDateIdLst:\r
1565                 obj = self.__translDic[x]\r
1566                 if obj.note is None:\r
1567                     f.write('  ' + obj.classId + '\n')\r
1568                     mailtoLst.extend(self.__emails(obj.classId))\r
1569 \r
1570             fmail.write('up-to-date\n')\r
1571             fmail.write('; '.join(mailtoLst))\r
1572 \r
1573 \r
1574             # Write separately the list of "ALMOST up-to-date" translator classes.\r
1575             s = '''The following translator classes are ALMOST up-to-date (sorted\r
1576                 alphabetically). This means that they derive from the\r
1577                 Translator class, but there still may be some minor problems\r
1578                 listed for them:'''\r
1579             f.write('\n' + ('-' * 70) + '\n')\r
1580             f.write(fill(s) + '\n\n')\r
1581             mailtoLst = []\r
1582             for x in self.upToDateIdLst:\r
1583                 obj = self.__translDic[x]\r
1584                 if obj.note is not None:\r
1585                     f.write('  ' + obj.classId + '\t-- ' + obj.note + '\n')\r
1586                     mailtoLst.extend(self.__emails(obj.classId))\r
1587 \r
1588             fmail.write('\n\nalmost up-to-date\n')\r
1589             fmail.write('; '.join(mailtoLst))\r
1590 \r
1591         # Write the list of the adapter based classes. The very obsolete\r
1592         # translators that derive from TranslatorEnglish are included.\r
1593         if self.adaptIdLst:\r
1594             s = '''The following translator classes need maintenance\r
1595                 (the most obsolete at the end). The other info shows the\r
1596                 estimation of Doxygen version when the class was last\r
1597                 updated and number of methods that must be implemented to\r
1598                 become up-to-date:'''\r
1599             f.write('\n' + '-' * 70 + '\n')\r
1600             f.write(fill(s) + '\n\n')\r
1601 \r
1602             # Find also whether some adapter classes may be removed.\r
1603             adaptMinVersion = '9.9.99'\r
1604 \r
1605             mailtoLst = []\r
1606             numRequired = len(self.requiredMethodsDic)\r
1607             for x in self.adaptIdLst:\r
1608                 obj = self.__translDic[x]\r
1609                 f.write('  %-30s' % obj.classId)\r
1610                 f.write('  %-6s' % obj.readableStatus)\r
1611                 numimpl = len(obj.missingMethods)\r
1612                 pluralS = ''\r
1613                 if numimpl > 1: pluralS = 's'\r
1614                 percent = 100 * numimpl / numRequired\r
1615                 f.write('\t%2d method%s to implement (%d %%)' % (\r
1616                         numimpl, pluralS, percent))\r
1617                 if obj.note:\r
1618                     f.write('\n\tNote: ' + obj.note + '\n')\r
1619                 f.write('\n')\r
1620                 mailtoLst.extend(self.__emails(obj.classId)) # to maintainer\r
1621 \r
1622                 # Check the level of required adapter classes.\r
1623                 if obj.status != '0.0.00' and obj.status < adaptMinVersion:\r
1624                     adaptMinVersion = obj.status\r
1625 \r
1626             fmail.write('\n\ntranslator based\n')\r
1627             fmail.write('; '.join(mailtoLst))\r
1628 \r
1629             # Set the note if some old translator adapters are not needed\r
1630             # any more. Do it only when the script is called without arguments,\r
1631             # i.e. all languages were checked against the needed translator\r
1632             # adapters.\r
1633             if not self.script_argLst:\r
1634                 to_remove = {}\r
1635                 for version, adaptClassId in list(self.adaptMethodsDic.values()):\r
1636                     if version < adaptMinVersion:\r
1637                         to_remove[adaptClassId] = True\r
1638 \r
1639                 if to_remove:\r
1640                     lst = list(to_remove.keys())\r
1641                     lst.sort()\r
1642                     plural = len(lst) > 1\r
1643                     note = 'Note: The adapter class'\r
1644                     if plural: note += 'es'\r
1645                     note += ' ' + ', '.join(lst)\r
1646                     if not plural:\r
1647                         note += ' is'\r
1648                     else:\r
1649                         note += ' are'\r
1650                     note += ' not used and can be removed.'\r
1651                     f.write('\n' + fill(note) + '\n')\r
1652 \r
1653         # Write the list of the English-based classes.\r
1654         if self.EnBasedIdLst:\r
1655             s = '''The following translator classes derive directly from the\r
1656                 TranslatorEnglish. The class identifier has the suffix 'En'\r
1657                 that says that this is intentional. Usually, there is also\r
1658                 a non-English based version of the translator for\r
1659                 the language:'''\r
1660             f.write('\n' + '-' * 70 + '\n')\r
1661             f.write(fill(s) + '\n\n')\r
1662 \r
1663             for x in self.EnBasedIdLst:\r
1664                 obj = self.__translDic[x]\r
1665                 f.write('  ' + obj.classId)\r
1666                 f.write('\timplements %d methods' % len(obj.implementedMethods))\r
1667                 if obj.note:\r
1668                     f.write(' -- ' + obj.note)\r
1669                 f.write('\n')\r
1670 \r
1671         # Check for not used translator methods and generate warning if found.\r
1672         # The check is rather time consuming, so it is not done when report\r
1673         # is restricted to explicitly given language identifiers.\r
1674         if not self.script_argLst:\r
1675             dic = self.__checkForNotUsedTrMethods()\r
1676             if dic:\r
1677                 s = '''WARNING: The following translator methods are declared\r
1678                     in the Translator class but their identifiers do not appear\r
1679                     in source files. The situation should be checked. The .cpp\r
1680                     files and .h files excluding the '*translator*' files\r
1681                     in doxygen/src directory were simply searched for occurrence\r
1682                     of the method identifiers:'''\r
1683                 f.write('\n' + '=' * 70 + '\n')\r
1684                 f.write(fill(s) + '\n\n')\r
1685 \r
1686                 keys = list(dic.keys())\r
1687                 keys.sort()\r
1688                 for key in keys:\r
1689                     f.write('  ' + dic[key] + '\n')\r
1690                 f.write('\n')\r
1691 \r
1692         # Write the details for the translators.\r
1693         f.write('\n' + '=' * 70)\r
1694         f.write('\nDetails for translators (classes sorted alphabetically):\n')\r
1695 \r
1696         cls = list(self.__translDic.keys())\r
1697         cls.sort()\r
1698 \r
1699         for c in cls:\r
1700             obj = self.__translDic[c]\r
1701             assert(obj.classId != 'Translator')\r
1702             obj.report(f)\r
1703 \r
1704         # Close the report file and the auxiliary file with e-mails.\r
1705         f.close()\r
1706         fmail.close()\r
1707 \r
1708 \r
1709     def __loadMaintainers(self):\r
1710         """Load and process the file with the maintainers.\r
1711 \r
1712         Fills the dictionary classId -> [(name, e-mail), ...]."""\r
1713 \r
1714         fname = os.path.join(self.doc_path, self.maintainersFileName)\r
1715 \r
1716         # Include the maintainers file to the group of files checked with\r
1717         # respect to the modification time.\r
1718         tim = os.path.getmtime(fname)\r
1719         if tim > self.lastModificationTime:\r
1720             self.lastModificationTime = tim\r
1721 \r
1722         # Process the content of the maintainers file.\r
1723         f = xopen(fname)\r
1724         inside = False  # inside the record for the language\r
1725         lineReady = True\r
1726         classId = None\r
1727         maintainersLst = None\r
1728         self.__maintainersDic = {}\r
1729         while lineReady:\r
1730             line = f.readline()            # next line\r
1731             lineReady = line != ''         # when eof, then line == ''\r
1732 \r
1733             line = line.strip()            # eof should also behave as separator\r
1734             if line != '' and line[0] == '%':    # skip the comment line\r
1735                 continue\r
1736 \r
1737             if not inside:                 # if outside of the record\r
1738                 if line != '':            # should be language identifier\r
1739                     classId = line\r
1740                     maintainersLst = []\r
1741                     inside = True\r
1742                 # Otherwise skip empty line that do not act as separator.\r
1743 \r
1744             else:                          # if inside the record\r
1745                 if line == '':            # separator found\r
1746                     inside = False\r
1747                 else:\r
1748                     # If it is the first maintainer, create the empty list.\r
1749                     if classId not in self.__maintainersDic:\r
1750                         self.__maintainersDic[classId] = []\r
1751 \r
1752                     # Split the information about the maintainer and append\r
1753                     # the tuple. The address may be prefixed '[unreachable]'\r
1754                     # or whatever '[xxx]'. This will be processed later.\r
1755                     lst = line.split(':', 1)\r
1756                     assert(len(lst) == 2)\r
1757                     t = (lst[0].strip(), lst[1].strip())\r
1758                     self.__maintainersDic[classId].append(t)\r
1759         f.close()\r
1760 \r
1761 \r
1762     def generateLanguageDoc(self):\r
1763         """Checks the modtime of files and generates language.doc."""\r
1764         self.__loadMaintainers()\r
1765 \r
1766         # Check the last modification time of the template file. It is the\r
1767         # last file from the group that decide whether the documentation\r
1768         # should or should not be generated.\r
1769         fTplName = os.path.join(self.doc_path, self.languageTplFileName)\r
1770         tim = os.path.getmtime(fTplName)\r
1771         if tim > self.lastModificationTime:\r
1772             self.lastModificationTime = tim\r
1773 \r
1774         # If the generated documentation exists and is newer than any of\r
1775         # the source files from the group, do not generate it and quit\r
1776         # quietly.\r
1777         fDocName = os.path.join(self.doc_path, self.languageDocFileName)\r
1778         if os.path.isfile(fDocName):\r
1779             if os.path.getmtime(fDocName) > self.lastModificationTime:\r
1780                 return\r
1781 \r
1782         # The document or does not exist or is older than some of the\r
1783         # sources. It must be generated again.\r
1784         #\r
1785         # Read the template of the documentation, and remove the first\r
1786         # attention lines.\r
1787         f = xopen(fTplName)\r
1788         doctpl = f.read()\r
1789         f.close()\r
1790 \r
1791         pos = doctpl.find('/***')\r
1792         assert pos != -1\r
1793         doctpl = doctpl[pos:]\r
1794 \r
1795         # Fill the tplDic by symbols that will be inserted into the\r
1796         # document template.\r
1797         tplDic = {}\r
1798 \r
1799         s = 'Do not edit this file. It was generated by the %s script. * Instead edit %s and %s' % (self.script_name, self.languageTplFileName, self.maintainersFileName)\r
1800         tplDic['editnote'] = s\r
1801 \r
1802         tplDic['doxVersion'] = self.doxVersion\r
1803         tplDic['supportedLangReadableStr'] = self.supportedLangReadableStr\r
1804         tplDic['translatorReportFileName'] = self.translatorReportFileName\r
1805 \r
1806         ahref = '<a href="../doc/' + self.translatorReportFileName\r
1807         ahref += '"\n><code>doxygen/doc/'  + self.translatorReportFileName\r
1808         ahref += '</code></a>'\r
1809         tplDic['translatorReportLink'] = ahref\r
1810         tplDic['numLangStr'] = str(self.numLang)\r
1811 \r
1812         # Define templates for HTML table parts of the documentation.\r
1813         htmlTableTpl = '''\\r
1814             \\htmlonly\r
1815             <table align="center" cellspacing="0" cellpadding="0" border="0">\r
1816             <tr bgcolor="#000000">\r
1817             <td>\r
1818               <table cellspacing="1" cellpadding="2" border="0">\r
1819               <tr bgcolor="#4040c0">\r
1820               <td ><b><font size="+1" color="#ffffff"> Language </font></b></td>\r
1821               <td ><b><font size="+1" color="#ffffff"> Maintainer </font></b></td>\r
1822               <td ><b><font size="+1" color="#ffffff"> Contact address </font>\r
1823                       <font size="-2" color="#ffffff">(replace the at and dot)</font></b></td>\r
1824               <td ><b><font size="+1" color="#ffffff"> Status </font></b></td>\r
1825               </tr>\r
1826               <!-- table content begin -->\r
1827             %s\r
1828               <!-- table content end -->\r
1829               </table>\r
1830             </td>\r
1831             </tr>\r
1832             </table>\r
1833             \\endhtmlonly\r
1834             '''\r
1835         htmlTableTpl = textwrap.dedent(htmlTableTpl)\r
1836         htmlTrTpl = '\n  <tr bgcolor="#ffffff">%s\n  </tr>'\r
1837         htmlTdTpl = '\n    <td>%s</td>'\r
1838         htmlTdStatusColorTpl = '\n    <td bgcolor="%s">%s</td>'\r
1839 \r
1840         # Loop through transl objects in the order of sorted readable names\r
1841         # and add generate the content of the HTML table.\r
1842         trlst = []\r
1843         for name, obj in self.langLst:\r
1844             # Fill the table data elements for one row. The first element\r
1845             # contains the readable name of the language. Only the oldest\r
1846             # translator are colour marked in the language columnt. Less\r
1847             # "heavy" color is used (when compared with the Status column).\r
1848             if obj.readableStatus.startswith('1.4'):\r
1849                 bkcolor = self.getBgcolorByReadableStatus('1.4')\r
1850             else:\r
1851                 bkcolor = '#ffffff'\r
1852 \r
1853             lst = [ htmlTdStatusColorTpl % (bkcolor, obj.langReadable) ]\r
1854 \r
1855             # The next two elements contain the list of maintainers\r
1856             # and the list of their mangled e-mails. For English-based\r
1857             # translators that are coupled with the non-English based,\r
1858             # insert the 'see' note.\r
1859             mm = None  # init -- maintainer\r
1860             ee = None  # init -- e-mail address\r
1861             if obj.status == 'En':\r
1862                 # Check whether there is the coupled non-English.\r
1863                 classId = obj.classId[:-2]\r
1864                 if classId in self.__translDic:\r
1865                     lang = self.__translDic[classId].langReadable\r
1866                     mm = 'see the %s language' % lang\r
1867                     ee = '&nbsp;'\r
1868 \r
1869             if not mm and obj.classId in self.__maintainersDic:\r
1870                 # Build a string of names separated by the HTML break element.\r
1871                 # Special notes used instead of names are highlighted.\r
1872                 lm = []\r
1873                 for maintainer in self.__maintainersDic[obj.classId]:\r
1874                     name = maintainer[0]\r
1875                     if name.startswith('--'):\r
1876                         name = '<span style="color: red; background-color: yellow">'\\r
1877                                + name + '</span>'\r
1878                     lm.append(name)\r
1879                 mm = '<br/>'.join(lm)\r
1880 \r
1881                 # The marked adresses (they start with the mark '[unreachable]',\r
1882                 # '[resigned]', whatever '[xxx]') will not be displayed at all.\r
1883                 # Only the mark will be used instead.\r
1884                 rexMark = re.compile('(?P<mark>\\[.*?\\])')\r
1885                 le = []\r
1886                 for maintainer in self.__maintainersDic[obj.classId]:\r
1887                     address = maintainer[1]\r
1888                     m = rexMark.search(address)\r
1889                     if m is not None:\r
1890                         address = '<span style="color: brown">'\\r
1891                                   + m.group('mark') + '</span>'\r
1892                     le.append(address)\r
1893                 ee = '<br/>'.join(le)\r
1894 \r
1895             # Append the maintainer and e-mail elements.\r
1896             lst.append(htmlTdTpl % mm)\r
1897             lst.append(htmlTdTpl % ee)\r
1898 \r
1899             # The last element contains the readable form of the status.\r
1900             bgcolor = self.getBgcolorByReadableStatus(obj.readableStatus)\r
1901             lst.append(htmlTdStatusColorTpl % (bgcolor, obj.readableStatus))\r
1902 \r
1903             # Join the table data to one table row.\r
1904             trlst.append(htmlTrTpl % (''.join(lst)))\r
1905 \r
1906         # Join the table rows and insert into the template.\r
1907         htmlTable = htmlTableTpl % (''.join(trlst))\r
1908 \r
1909         # Define templates for LaTeX table parts of the documentation.\r
1910         latexTableTpl = r'''\r
1911             \latexonly\r
1912             \footnotesize\r
1913             \begin{longtable}{|l|l|l|l|}\r
1914               \hline\r
1915               {\bf Language} & {\bf Maintainer} & {\bf Contact address} & {\bf Status} \\\r
1916               \hline\r
1917             %s\r
1918               \hline\r
1919             \end{longtable}\r
1920             \normalsize\r
1921             \endlatexonly\r
1922             '''\r
1923         latexTableTpl = textwrap.dedent(latexTableTpl)\r
1924         latexLineTpl = '\n' + r'  %s & %s & {\tt\tiny %s} & %s \\'\r
1925 \r
1926         # Loop through transl objects in the order of sorted readable names\r
1927         # and add generate the content of the LaTeX table.\r
1928         trlst = []\r
1929         for name, obj in self.langLst:\r
1930             # For LaTeX, more maintainers for the same language are\r
1931             # placed on separate rows in the table.  The line separator\r
1932             # in the table is placed explicitly above the first\r
1933             # maintainer. Prepare the arguments for the LaTeX row template.\r
1934             maintainers = []\r
1935             if obj.classId in self.__maintainersDic:\r
1936                 maintainers = self.__maintainersDic[obj.classId]\r
1937 \r
1938             lang = obj.langReadable\r
1939             maintainer = None  # init\r
1940             email = None       # init\r
1941             if obj.status == 'En':\r
1942                 # Check whether there is the coupled non-English.\r
1943                 classId = obj.classId[:-2]\r
1944                 if classId in self.__translDic:\r
1945                     langNE = self.__translDic[classId].langReadable\r
1946                     maintainer = 'see the %s language' % langNE\r
1947                     email = '~'\r
1948 \r
1949             if not maintainer and (obj.classId in self.__maintainersDic):\r
1950                 lm = [ m[0] for m in self.__maintainersDic[obj.classId] ]\r
1951                 maintainer = maintainers[0][0]\r
1952                 email = maintainers[0][1]\r
1953 \r
1954             status = obj.readableStatus\r
1955 \r
1956             # Use the template to produce the line of the table and insert\r
1957             # the hline plus the constructed line into the table content.\r
1958             # The underscore character must be escaped.\r
1959             trlst.append('\n  \\hline')\r
1960             s = latexLineTpl % (lang, maintainer, email, status)\r
1961             s = s.replace('_', '\\_')\r
1962             trlst.append(s)\r
1963 \r
1964             # List the other maintainers for the language. Do not set\r
1965             # lang and status for them.\r
1966             lang = '~'\r
1967             status = '~'\r
1968             for m in maintainers[1:]:\r
1969                 maintainer = m[0]\r
1970                 email = m[1]\r
1971                 s = latexLineTpl % (lang, maintainer, email, status)\r
1972                 s = s.replace('_', '\\_')\r
1973                 trlst.append(s)\r
1974 \r
1975         # Join the table lines and insert into the template.\r
1976         latexTable = latexTableTpl % (''.join(trlst))\r
1977 \r
1978         # Put the HTML and LaTeX parts together and define the dic item.\r
1979         tplDic['informationTable'] = htmlTable + '\n' + latexTable\r
1980 \r
1981         # Insert the symbols into the document template and write it down.\r
1982         f = xopen(fDocName, 'w')\r
1983         f.write(doctpl % tplDic)\r
1984         f.close()\r
1985 \r
1986 if __name__ == '__main__':\r
1987 \r
1988     # The Python 2.6+ or 3.3+ is required.\r
1989     major, minor, patch = (int(e) for e in platform.python_version_tuple())\r
1990     print(major, minor, patch)\r
1991     if (major == 2 and minor < 6) or (major == 3 and minor < 0):\r
1992         print('Python 2.6+ or Python 3.0+ are required for the script')\r
1993         sys.exit(1)\r
1994 \r
1995     # The translator manager builds the transl objects, parses the related\r
1996     # sources, and keeps them in memory.\r
1997     trMan = TrManager()\r
1998 \r
1999     # Generate the language.doc.\r
2000     trMan.generateLanguageDoc()\r
2001 \r
2002     # Generate the translator report.\r
2003     trMan.generateTranslatorReport()\r