~mailman-coders/mailman/2.1

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