Apply 79_archiver_slash.patch
[mspang/vmailman.git] / bin / transcheck
1 #! @PYTHON@
2 #
3 # transcheck - (c) 2002 by Simone Piunno <pioppo@ferrara.linux.it>
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the version 2.0 of the GNU General Public License as
7 # published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 """
19 Check a given Mailman translation, making sure that variables and
20 tags referenced in translation are the same variables and tags in
21 the original templates and catalog.
22
23 Usage:
24
25 cd $MAILMAN_DIR
26 %(program)s [-q] <lang>
27
28 Where <lang> is your country code (e.g. 'it' for Italy) and -q is
29 to ask for a brief summary.
30 """
31
32 import sys
33 import re
34 import os
35 import getopt
36
37 import paths
38 from Mailman.i18n import _
39
40 program = sys.argv[0]
41
42
43 \f
44 def usage(code, msg=''):
45     if code:
46         fd = sys.stderr
47     else:
48         fd = sys.stdout
49     print >> fd, _(__doc__)
50     if msg:
51         print >> fd, msg
52     sys.exit(code)
53
54
55 \f
56 class TransChecker:
57     "check a translation comparing with the original string"
58     def __init__(self, regexp, escaped=None):
59         self.dict = {}
60         self.errs = []
61         self.regexp = re.compile(regexp)
62         self.escaped = None
63         if escaped:
64             self.escaped = re.compile(escaped)
65
66     def checkin(self, string):
67         "scan a string from the original file"
68         for key in self.regexp.findall(string):
69             if self.escaped and self.escaped.match(key):
70                 continue
71             if self.dict.has_key(key):
72                 self.dict[key] += 1
73             else:
74                 self.dict[key] = 1
75
76     def checkout(self, string):
77         "scan a translated string"
78         for key in self.regexp.findall(string):
79             if self.escaped and self.escaped.match(key):
80                 continue
81             if self.dict.has_key(key):
82                 self.dict[key] -= 1
83             else:
84                 self.errs.append(
85                     "%(key)s was not found" %
86                     { 'key' : key }
87                 )
88
89     def computeErrors(self):
90         "check for differences between checked in and checked out"
91         for key in self.dict.keys():
92             if self.dict[key] < 0:
93                 self.errs.append(
94                     "Too much %(key)s" %
95                     { 'key'  : key }
96                 )
97             if self.dict[key] > 0:
98                 self.errs.append(
99                     "Too few %(key)s" %
100                     { 'key'  : key }
101                 )
102         return self.errs
103
104     def status(self):
105         if self.errs:
106             return "FAILED"
107         else:
108             return "OK"
109
110     def errorsAsString(self):
111         msg = ""
112         for err in self.errs:
113             msg += " - %(err)s" % { 'err': err }
114         return msg
115
116     def reset(self):
117         self.dict = {}
118         self.errs = []
119
120
121 \f
122 class POParser:
123     "parse a .po file extracting msgids and msgstrs"
124     def __init__(self, filename=""):
125         self.status = 0
126         self.files = []
127         self.msgid = ""
128         self.msgstr = ""
129         self.line = 1
130         self.f = None
131         self.esc = { "n": "\n", "r": "\r", "t": "\t" }
132         if filename:
133             self.f = open(filename)
134
135     def open(self, filename):
136         self.f = open(filename)
137
138     def close(self):
139         self.f.close()
140
141     def parse(self):
142         """States table for the finite-states-machine parser:
143             0  idle
144             1  filename-or-comment
145             2  msgid
146             3  msgstr
147             4  end
148         """
149         # each time we can safely re-initialize those vars
150         self.files = []
151         self.msgid = ""
152         self.msgstr = ""
153
154
155         # can't continue if status == 4, this is a dead status
156         if self.status == 4:
157             return 0
158
159         while 1:
160             # continue scanning, char-by-char
161             c = self.f.read(1)
162             if not c:
163                 # EOF -> maybe we have a msgstr to save?
164                 self.status = 4
165                 if self.msgstr:
166                     return 1
167                 else:
168                     return 0
169
170             # keep the line count up-to-date
171             if c == "\n":
172                 self.line += 1
173
174             # a pound was detected the previous char...
175             if self.status == 1:
176                 if c == ":":
177                     # was a line of filenames
178                     row = self.f.readline()
179                     self.files += row.split()
180                     self.line += 1
181                 elif c == "\n":
182                     # was a single pount on the line
183                     pass
184                 else:
185                     # was a comment... discard
186                     self.f.readline()
187                     self.line += 1
188                 # in every case, we switch to idle status
189                 self.status = 0;
190                 continue
191
192             # in idle status we search for a '#' or for a 'm'
193             if self.status == 0:
194                 if   c == "#":
195                     # this could be a comment or a filename
196                     self.status = 1;
197                     continue
198                 elif c == "m":
199                     # this should be a msgid start...
200                     s = self.f.read(4)
201                     assert s == "sgid"
202                     # so now we search for a '"'
203                     self.status = 2
204                     continue
205                 # in idle only those other chars are possibile
206                 assert c in [ "\n", " ", "\t" ]
207
208             # searching for the msgid string
209             if self.status == 2:
210                 if c == "\n":
211                     # a double LF is not possible here
212                     c = self.f.read(1)
213                     assert c != "\n"
214                 if c == "\"":
215                     # ok, this is the start of the string,
216                     # now search for the end
217                     while 1:
218                         c = self.f.read(1)
219                         if not c:
220                             # EOF, bailout
221                             self.status = 4
222                             return 0
223                         if c == "\\":
224                             # a quoted char...
225                             c = self.f.read(1)
226                             if self.esc.has_key(c):
227                                 self.msgid += self.esc[c]
228                             else:
229                                 self.msgid += c
230                             continue
231                         if c == "\"":
232                             # end of string found
233                             break
234                         # a normal char, add it
235                         self.msgid += c
236                 if c == "m":
237                     # this should be a msgstr identifier
238                     s = self.f.read(5)
239                     assert s == "sgstr"
240                     # ok, now search for the msgstr string
241                     self.status = 3
242
243             # searching for the msgstr string
244             if self.status == 3:
245                 if c == "\n":
246                     # a double LF is the end of the msgstr!
247                     c = self.f.read(1)
248                     if c == "\n":
249                         # ok, time to go idle and return
250                         self.status = 0
251                         self.line += 1
252                         return 1
253                 if c == "\"":
254                     # start of string found
255                     while 1:
256                         c = self.f.read(1)
257                         if not c:
258                             # EOF, bail out
259                             self.status = 4
260                             return 1
261                         if c == "\\":
262                             # a quoted char...
263                             c = self.f.read(1)
264                             if self.esc.has_key(c):
265                                 self.msgid += self.esc[c]
266                             else:
267                                 self.msgid += c
268                             continue
269                         if c == "\"":
270                             # end of string
271                             break
272                         # a normal char, add it
273                         self.msgstr += c
274
275
276
277 \f
278 def check_file(translatedFile, originalFile, html=0, quiet=0):
279     """check a translated template against the original one
280        search also <MM-*> tags if html is not zero"""
281
282     if html:
283         c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd]|</?MM-[^>]+>)", "^%%$")
284     else:
285         c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd])", "^%%$")
286
287     try:
288         f = open(originalFile)
289     except IOError:
290         if not quiet:
291             print " - Can'open original file " + originalFile
292         return 1
293
294     while 1:
295         line = f.readline()
296         if not line: break
297         c.checkin(line)
298
299     f.close()
300
301     try:
302         f = open(translatedFile)
303     except IOError:
304         if not quiet:
305             print " - Can'open translated file " + translatedFile
306         return 1
307
308     while 1:
309         line = f.readline()
310         if not line: break
311         c.checkout(line)
312
313     f.close()
314
315     n = 0
316     msg = ""
317     for desc in c.computeErrors():
318         n +=1
319         if not quiet:
320             print " - %(desc)s" % { 'desc': desc }
321     return n
322
323
324 \f
325 def check_po(file, quiet=0):
326     "scan the po file comparing msgids with msgstrs"
327     n = 0
328     p = POParser(file)
329     c = TransChecker("(%%|%\([^)]+\)[0-9]*[sdu]|%[0-9]*[sdu])", "^%%$")
330     while p.parse():
331         if p.msgstr:
332             c.reset()
333             c.checkin(p.msgid)
334             c.checkout(p.msgstr)
335             for desc in c.computeErrors():
336                 n += 1
337                 if not quiet:
338                     print " - near line %(line)d %(file)s: %(desc)s" % {
339                         'line': p.line,
340                         'file': p.files,
341                         'desc': desc
342                     }
343     p.close()
344     return n
345
346 \f
347 def main():
348     try:
349         opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help'])
350     except getopt.error, msg:
351         usage(1, msg)
352
353     quiet = 0
354     for opt, arg in opts:
355         if opt in ('-h', '--help'):
356             usage(0)
357         elif opt in ('-q', '--quiet'):
358             quiet = 1
359
360     if len(args) <> 1:
361         usage(1)
362
363     lang = args[0]
364
365     isHtml = re.compile("\.html$");
366     isTxt = re.compile("\.txt$");
367
368     numerrors = 0
369     numfiles = 0
370     try:
371         files = os.listdir("templates/" + lang + "/")
372     except:
373         print "can't open templates/%s/" % lang
374     for file in files:
375         fileEN = "templates/en/" + file
376         fileIT = "templates/" + lang + "/" + file
377         errlist = []
378         if isHtml.search(file):
379             if not quiet:
380                 print "HTML checking " + fileIT + "... "
381             n = check_file(fileIT, fileEN, html=1, quiet=quiet)
382             if n:
383                 numerrors += n
384                 numfiles += 1
385         elif isTxt.search(file):
386             if not quiet:
387                 print "TXT  checking " + fileIT + "... "
388             n = check_file(fileIT, fileEN, html=0, quiet=quiet)
389             if n:
390                 numerrors += n
391                 numfiles += 1
392
393         else:
394             continue
395
396     file = "messages/" + lang + "/LC_MESSAGES/mailman.po"
397     if not quiet:
398         print "PO   checking " + file + "... "
399     n = check_po(file, quiet=quiet)
400     if n:
401         numerrors += n
402         numfiles += 1
403
404     if quiet:
405         print "%(errs)u warnings in %(files)u files" % {
406             'errs':  numerrors,
407             'files': numfiles
408         }
409
410 \f
411 if __name__ == '__main__':
412     main()