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