##// END OF EJS Templates
cleanup: rename check-translation.py checker function - don't hide global var
Mads Kiilerich -
r22203:35c2ea4c default
parent child Browse files
Show More
@@ -1,191 +1,191 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-translation.py - check Mercurial specific translation problems
3 # check-translation.py - check Mercurial specific translation problems
4
4
5 import polib
5 import polib
6 import re
6 import re
7
7
8 checkers = []
8 checkers = []
9
9
10 def checker(level, msgidpat):
10 def levelchecker(level, msgidpat):
11 def decorator(func):
11 def decorator(func):
12 if msgidpat:
12 if msgidpat:
13 match = re.compile(msgidpat).search
13 match = re.compile(msgidpat).search
14 else:
14 else:
15 match = lambda msgid: True
15 match = lambda msgid: True
16 checkers.append((func, level))
16 checkers.append((func, level))
17 func.match = match
17 func.match = match
18 return func
18 return func
19 return decorator
19 return decorator
20
20
21 def match(checker, pe):
21 def match(checker, pe):
22 """Examine whether POEntry "pe" is target of specified checker or not
22 """Examine whether POEntry "pe" is target of specified checker or not
23 """
23 """
24 if not checker.match(pe.msgid):
24 if not checker.match(pe.msgid):
25 return
25 return
26 # examine suppression by translator comment
26 # examine suppression by translator comment
27 nochecker = 'no-%s-check' % checker.__name__
27 nochecker = 'no-%s-check' % checker.__name__
28 for tc in pe.tcomment.split():
28 for tc in pe.tcomment.split():
29 if nochecker == tc:
29 if nochecker == tc:
30 return
30 return
31 return True
31 return True
32
32
33 ####################
33 ####################
34
34
35 def fatalchecker(msgidpat=None):
35 def fatalchecker(msgidpat=None):
36 return checker('fatal', msgidpat)
36 return levelchecker('fatal', msgidpat)
37
37
38 @fatalchecker(r'\$\$')
38 @fatalchecker(r'\$\$')
39 def promptchoice(pe):
39 def promptchoice(pe):
40 """Check translation of the string given to "ui.promptchoice()"
40 """Check translation of the string given to "ui.promptchoice()"
41
41
42 >>> pe = polib.POEntry(
42 >>> pe = polib.POEntry(
43 ... msgid ='prompt$$missing &sep$$missing &amp$$followed by &none',
43 ... msgid ='prompt$$missing &sep$$missing &amp$$followed by &none',
44 ... msgstr='prompt missing &sep$$missing amp$$followed by none&')
44 ... msgstr='prompt missing &sep$$missing amp$$followed by none&')
45 >>> match(promptchoice, pe)
45 >>> match(promptchoice, pe)
46 True
46 True
47 >>> for e in promptchoice(pe): print e
47 >>> for e in promptchoice(pe): print e
48 number of choices differs between msgid and msgstr
48 number of choices differs between msgid and msgstr
49 msgstr has invalid choice missing '&'
49 msgstr has invalid choice missing '&'
50 msgstr has invalid '&' followed by none
50 msgstr has invalid '&' followed by none
51 """
51 """
52 idchoices = [c.rstrip(' ') for c in pe.msgid.split('$$')[1:]]
52 idchoices = [c.rstrip(' ') for c in pe.msgid.split('$$')[1:]]
53 strchoices = [c.rstrip(' ') for c in pe.msgstr.split('$$')[1:]]
53 strchoices = [c.rstrip(' ') for c in pe.msgstr.split('$$')[1:]]
54
54
55 if len(idchoices) != len(strchoices):
55 if len(idchoices) != len(strchoices):
56 yield "number of choices differs between msgid and msgstr"
56 yield "number of choices differs between msgid and msgstr"
57
57
58 indices = [(c, c.find('&')) for c in strchoices]
58 indices = [(c, c.find('&')) for c in strchoices]
59 if [c for c, i in indices if i == -1]:
59 if [c for c, i in indices if i == -1]:
60 yield "msgstr has invalid choice missing '&'"
60 yield "msgstr has invalid choice missing '&'"
61 if [c for c, i in indices if len(c) == i + 1]:
61 if [c for c, i in indices if len(c) == i + 1]:
62 yield "msgstr has invalid '&' followed by none"
62 yield "msgstr has invalid '&' followed by none"
63
63
64 ####################
64 ####################
65
65
66 def warningchecker(msgidpat=None):
66 def warningchecker(msgidpat=None):
67 return checker('warning', msgidpat)
67 return levelchecker('warning', msgidpat)
68
68
69 @warningchecker()
69 @warningchecker()
70 def taildoublecolons(pe):
70 def taildoublecolons(pe):
71 """Check equality of tail '::'-ness between msgid and msgstr
71 """Check equality of tail '::'-ness between msgid and msgstr
72
72
73 >>> pe = polib.POEntry(
73 >>> pe = polib.POEntry(
74 ... msgid ='ends with ::',
74 ... msgid ='ends with ::',
75 ... msgstr='ends with ::')
75 ... msgstr='ends with ::')
76 >>> for e in taildoublecolons(pe): print e
76 >>> for e in taildoublecolons(pe): print e
77 >>> pe = polib.POEntry(
77 >>> pe = polib.POEntry(
78 ... msgid ='ends with ::',
78 ... msgid ='ends with ::',
79 ... msgstr='ends without double-colons')
79 ... msgstr='ends without double-colons')
80 >>> for e in taildoublecolons(pe): print e
80 >>> for e in taildoublecolons(pe): print e
81 tail '::'-ness differs between msgid and msgstr
81 tail '::'-ness differs between msgid and msgstr
82 >>> pe = polib.POEntry(
82 >>> pe = polib.POEntry(
83 ... msgid ='ends without double-colons',
83 ... msgid ='ends without double-colons',
84 ... msgstr='ends with ::')
84 ... msgstr='ends with ::')
85 >>> for e in taildoublecolons(pe): print e
85 >>> for e in taildoublecolons(pe): print e
86 tail '::'-ness differs between msgid and msgstr
86 tail '::'-ness differs between msgid and msgstr
87 """
87 """
88 if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
88 if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
89 yield "tail '::'-ness differs between msgid and msgstr"
89 yield "tail '::'-ness differs between msgid and msgstr"
90
90
91 @warningchecker()
91 @warningchecker()
92 def indentation(pe):
92 def indentation(pe):
93 """Check equality of initial indentation between msgid and msgstr
93 """Check equality of initial indentation between msgid and msgstr
94
94
95 This may report unexpected warning, because this doesn't aware
95 This may report unexpected warning, because this doesn't aware
96 the syntax of rst document and the context of msgstr.
96 the syntax of rst document and the context of msgstr.
97
97
98 >>> pe = polib.POEntry(
98 >>> pe = polib.POEntry(
99 ... msgid =' indented text',
99 ... msgid =' indented text',
100 ... msgstr=' narrowed indentation')
100 ... msgstr=' narrowed indentation')
101 >>> for e in indentation(pe): print e
101 >>> for e in indentation(pe): print e
102 initial indentation width differs betweeen msgid and msgstr
102 initial indentation width differs betweeen msgid and msgstr
103 """
103 """
104 idindent = len(pe.msgid) - len(pe.msgid.lstrip())
104 idindent = len(pe.msgid) - len(pe.msgid.lstrip())
105 strindent = len(pe.msgstr) - len(pe.msgstr.lstrip())
105 strindent = len(pe.msgstr) - len(pe.msgstr.lstrip())
106 if idindent != strindent:
106 if idindent != strindent:
107 yield "initial indentation width differs betweeen msgid and msgstr"
107 yield "initial indentation width differs betweeen msgid and msgstr"
108
108
109 ####################
109 ####################
110
110
111 def check(pofile, fatal=True, warning=False):
111 def check(pofile, fatal=True, warning=False):
112 targetlevel = { 'fatal': fatal, 'warning': warning }
112 targetlevel = { 'fatal': fatal, 'warning': warning }
113 targetcheckers = [(checker, level)
113 targetcheckers = [(checker, level)
114 for checker, level in checkers
114 for checker, level in checkers
115 if targetlevel[level]]
115 if targetlevel[level]]
116 if not targetcheckers:
116 if not targetcheckers:
117 return []
117 return []
118
118
119 detected = []
119 detected = []
120 for pe in pofile.translated_entries():
120 for pe in pofile.translated_entries():
121 errors = []
121 errors = []
122 for checker, level in targetcheckers:
122 for checker, level in targetcheckers:
123 if match(checker, pe):
123 if match(checker, pe):
124 errors.extend((level, checker.__name__, error)
124 errors.extend((level, checker.__name__, error)
125 for error in checker(pe))
125 for error in checker(pe))
126 if errors:
126 if errors:
127 detected.append((pe, errors))
127 detected.append((pe, errors))
128 return detected
128 return detected
129
129
130 ########################################
130 ########################################
131
131
132 if __name__ == "__main__":
132 if __name__ == "__main__":
133 import sys
133 import sys
134 import optparse
134 import optparse
135
135
136 optparser = optparse.OptionParser("""%prog [options] pofile ...
136 optparser = optparse.OptionParser("""%prog [options] pofile ...
137
137
138 This checks Mercurial specific translation problems in specified
138 This checks Mercurial specific translation problems in specified
139 '*.po' files.
139 '*.po' files.
140
140
141 Each detected problems are shown in the format below::
141 Each detected problems are shown in the format below::
142
142
143 filename:linenum:type(checker): problem detail .....
143 filename:linenum:type(checker): problem detail .....
144
144
145 "type" is "fatal" or "warning". "checker" is the name of the function
145 "type" is "fatal" or "warning". "checker" is the name of the function
146 detecting corresponded error.
146 detecting corresponded error.
147
147
148 Checking by checker "foo" on the specific msgstr can be suppressed by
148 Checking by checker "foo" on the specific msgstr can be suppressed by
149 the "translator comment" like below. Multiple "no-xxxx-check" should
149 the "translator comment" like below. Multiple "no-xxxx-check" should
150 be separated by whitespaces::
150 be separated by whitespaces::
151
151
152 # no-foo-check
152 # no-foo-check
153 msgid = "....."
153 msgid = "....."
154 msgstr = "....."
154 msgstr = "....."
155 """)
155 """)
156 optparser.add_option("", "--warning",
156 optparser.add_option("", "--warning",
157 help="show also warning level problems",
157 help="show also warning level problems",
158 action="store_true")
158 action="store_true")
159 optparser.add_option("", "--doctest",
159 optparser.add_option("", "--doctest",
160 help="run doctest of this tool, instead of check",
160 help="run doctest of this tool, instead of check",
161 action="store_true")
161 action="store_true")
162 (options, args) = optparser.parse_args()
162 (options, args) = optparser.parse_args()
163
163
164 if options.doctest:
164 if options.doctest:
165 import os
165 import os
166 if 'TERM' in os.environ:
166 if 'TERM' in os.environ:
167 del os.environ['TERM']
167 del os.environ['TERM']
168 import doctest
168 import doctest
169 failures, tests = doctest.testmod()
169 failures, tests = doctest.testmod()
170 sys.exit(failures and 1 or 0)
170 sys.exit(failures and 1 or 0)
171
171
172 # replace polib._POFileParser to show linenum of problematic msgstr
172 # replace polib._POFileParser to show linenum of problematic msgstr
173 class ExtPOFileParser(polib._POFileParser):
173 class ExtPOFileParser(polib._POFileParser):
174 def process(self, symbol, linenum):
174 def process(self, symbol, linenum):
175 super(ExtPOFileParser, self).process(symbol, linenum)
175 super(ExtPOFileParser, self).process(symbol, linenum)
176 if symbol == 'MS': # msgstr
176 if symbol == 'MS': # msgstr
177 self.current_entry.linenum = linenum
177 self.current_entry.linenum = linenum
178 polib._POFileParser = ExtPOFileParser
178 polib._POFileParser = ExtPOFileParser
179
179
180 detected = []
180 detected = []
181 warning = options.warning
181 warning = options.warning
182 for f in args:
182 for f in args:
183 detected.extend((f, pe, errors)
183 detected.extend((f, pe, errors)
184 for pe, errors in check(polib.pofile(f),
184 for pe, errors in check(polib.pofile(f),
185 warning=warning))
185 warning=warning))
186 if detected:
186 if detected:
187 for f, pe, errors in detected:
187 for f, pe, errors in detected:
188 for level, checker, error in errors:
188 for level, checker, error in errors:
189 sys.stderr.write('%s:%d:%s(%s): %s\n'
189 sys.stderr.write('%s:%d:%s(%s): %s\n'
190 % (f, pe.linenum, level, checker, error))
190 % (f, pe.linenum, level, checker, error))
191 sys.exit(1)
191 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now