Mercurial > p > roundup > code
annotate roundup/cgi/TAL/talgettext.py @ 5414:3fa026621f69
Python 3 preparation: comparisons.
Python 3 no longer has the cmp function, or cmp= arguments to sorting
functions / methods (key= must be used instead), and requires rich
comparison methods such as __lt__ to be defined instead of using
__cmp__. All of the comparison mechanisms supported in Python 3 are
also supported in Python 2.
This patch makes the corresponding changes in Roundup to use key
functions and rich comparison methods. In the case of the
JournalPassword and Permission classes, only __eq__ and __ne__ are
defined as I don't see ordered comparisons as useful there (and for
Permission, the old __cmp__ function didn't try to provide a valid
ordering). In the case of the Date class, I kept the __cmp__ method
and implemented the others in terms of it, to avoid excess
repetitiveness in duplicating implementation code for all six rich
comparison methods.
In roundup/admin.py, help_commands_html used operator.attrgetter to
produce the second argument of sorted() - which would be reasonable
for a key function, but the second argument is the cmp function in
Python 2, not a key function (and the key function must be a named
argument not a positional argument in Python 3). That function
appears to be completely unused, so I expect that code never worked.
This patch adds the missing key= to that sorted() call, but it would
also be reasonable to remove the unused function completely instead.
| author | Joseph Myers <jsm@polyomino.org.uk> |
|---|---|
| date | Wed, 25 Jul 2018 00:39:37 +0000 |
| parents | 23b8e6067f7c |
| children | f2fade4552c5 |
| rev | line source |
|---|---|
| 2352 | 1 #!/usr/bin/env python |
| 2 ############################################################################## | |
| 3 # | |
| 4 # Copyright (c) 2002 Zope Corporation and Contributors. | |
| 5 # All Rights Reserved. | |
| 6 # | |
| 7 # This software is subject to the provisions of the Zope Public License, | |
| 8 # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. | |
| 9 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |
| 10 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 11 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |
| 12 # FOR A PARTICULAR PURPOSE. | |
| 13 # | |
| 14 ############################################################################## | |
| 15 # Modifications for Roundup: | |
| 16 # 1. commented out ITALES references | |
|
2385
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
17 # 2. escape quotes and line feeds in msgids |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
18 # 3. don't collect empty msgids |
| 2352 | 19 |
| 20 """Program to extract internationalization markup from Page Templates. | |
| 21 | |
| 22 Once you have marked up a Page Template file with i18n: namespace tags, use | |
| 23 this program to extract GNU gettext .po file entries. | |
| 24 | |
| 25 Usage: talgettext.py [options] files | |
| 26 Options: | |
| 27 -h / --help | |
| 28 Print this message and exit. | |
| 29 -o / --output <file> | |
| 30 Output the translation .po file to <file>. | |
| 31 -u / --update <file> | |
| 32 Update the existing translation <file> with any new translation strings | |
| 33 found. | |
| 34 """ | |
| 35 | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
36 from __future__ import print_function |
| 2352 | 37 import sys |
| 38 import time | |
| 39 import getopt | |
| 40 import traceback | |
| 41 | |
|
4572
3a407f0d5dad
Add import for version info
Ralf Schlatterbeck <rsc@runtux.com>
parents:
4570
diff
changeset
|
42 from roundup import __version__ |
| 2352 | 43 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser |
| 44 from roundup.cgi.TAL.TALInterpreter import TALInterpreter | |
| 45 from roundup.cgi.TAL.DummyEngine import DummyEngine | |
| 46 #from ITALES import ITALESEngine | |
| 47 from roundup.cgi.TAL.TALDefs import TALESError | |
| 48 | |
| 49 pot_header = '''\ | |
| 50 # SOME DESCRIPTIVE TITLE. | |
| 51 # Copyright (C) YEAR ORGANIZATION | |
| 52 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
| 53 # | |
| 54 msgid "" | |
| 55 msgstr "" | |
| 56 "Project-Id-Version: PACKAGE VERSION\\n" | |
| 57 "POT-Creation-Date: %(time)s\\n" | |
| 58 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" | |
| 59 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" | |
| 60 "Language-Team: LANGUAGE <LL@li.org>\\n" | |
| 61 "MIME-Version: 1.0\\n" | |
| 62 "Content-Type: text/plain; charset=CHARSET\\n" | |
| 63 "Content-Transfer-Encoding: ENCODING\\n" | |
| 64 "Generated-By: talgettext.py %(version)s\\n" | |
| 65 ''' | |
| 66 | |
| 67 NLSTR = '"\n"' | |
| 68 | |
| 69 try: | |
| 70 True | |
| 71 except NameError: | |
| 72 True=1 | |
| 73 False=0 | |
| 74 | |
| 75 def usage(code, msg=''): | |
| 76 # Python 2.1 required | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
77 print(__doc__, file=sys.stderr) |
| 2352 | 78 if msg: |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
79 print(msg, file=sys.stderr) |
| 2352 | 80 sys.exit(code) |
| 81 | |
| 82 | |
| 83 class POTALInterpreter(TALInterpreter): | |
| 84 def translate(self, msgid, default, i18ndict=None, obj=None): | |
| 85 # XXX is this right? | |
| 86 if i18ndict is None: | |
| 87 i18ndict = {} | |
| 88 if obj: | |
| 89 i18ndict.update(obj) | |
| 90 # XXX Mmmh, it seems that sometimes the msgid is None; is that really | |
| 91 # possible? | |
| 92 if msgid is None: | |
| 93 return None | |
| 94 # XXX We need to pass in one of context or target_language | |
| 95 return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, | |
| 96 position=self.position, default=default) | |
| 97 | |
| 98 | |
| 99 class POEngine(DummyEngine): | |
| 100 #__implements__ = ITALESEngine | |
| 101 | |
| 102 def __init__(self, macros=None): | |
| 103 self.catalog = {} | |
| 104 DummyEngine.__init__(self, macros) | |
| 105 | |
| 106 def evaluate(*args): | |
| 107 return '' # who cares | |
| 108 | |
| 109 def evaluatePathOrVar(*args): | |
| 110 return '' # who cares | |
| 111 | |
| 112 def evaluateSequence(self, expr): | |
| 113 return (0,) # dummy | |
| 114 | |
| 115 def evaluateBoolean(self, expr): | |
| 116 return True # dummy | |
| 117 | |
| 118 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 119 # XXX position is not part of the ITALESEngine | |
| 120 # interface | |
| 121 position=None): | |
| 122 | |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
123 if not msgid: return 'x' |
|
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
124 |
| 2352 | 125 if domain not in self.catalog: |
| 126 self.catalog[domain] = {} | |
| 127 domain = self.catalog[domain] | |
| 128 | |
| 129 if msgid not in domain: | |
| 130 domain[msgid] = [] | |
| 131 domain[msgid].append((self.file, position)) | |
| 132 return 'x' | |
| 133 | |
| 134 | |
| 135 class UpdatePOEngine(POEngine): | |
| 136 """A slightly-less braindead POEngine which supports loading an existing | |
| 137 .po file first.""" | |
| 138 | |
| 139 def __init__ (self, macros=None, filename=None): | |
| 140 POEngine.__init__(self, macros) | |
| 141 | |
| 142 self._filename = filename | |
| 143 self._loadFile() | |
| 144 self.base = self.catalog | |
| 145 self.catalog = {} | |
| 146 | |
| 147 def __add(self, id, s, fuzzy): | |
| 148 "Add a non-fuzzy translation to the dictionary." | |
| 149 if not fuzzy and str: | |
| 150 # check for multi-line values and munge them appropriately | |
| 151 if '\n' in s: | |
| 152 lines = s.rstrip().split('\n') | |
| 153 s = NLSTR.join(lines) | |
| 154 self.catalog[id] = s | |
| 155 | |
| 156 def _loadFile(self): | |
| 157 # shamelessly cribbed from Python's Tools/i18n/msgfmt.py | |
| 158 # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) | |
| 159 # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) | |
| 160 | |
| 161 ID = 1 | |
| 162 STR = 2 | |
| 163 | |
| 164 try: | |
| 165 lines = open(self._filename).readlines() | |
|
5248
198b6e810c67
Use Python-3-compatible 'as' syntax for except statements
Eric S. Raymond <esr@thyrsus.com>
parents:
4572
diff
changeset
|
166 except IOError as msg: |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
167 print(msg, file=sys.stderr) |
| 2352 | 168 sys.exit(1) |
| 169 | |
| 170 section = None | |
| 171 fuzzy = False | |
| 172 | |
| 173 # Parse the catalog | |
| 174 lno = 0 | |
| 175 for l in lines: | |
| 176 lno += True | |
| 177 # If we get a comment line after a msgstr, this is a new entry | |
| 178 if l[0] == '#' and section == STR: | |
| 179 self.__add(msgid, msgstr, fuzzy) | |
| 180 section = None | |
| 181 fuzzy = False | |
| 182 # Record a fuzzy mark | |
| 183 if l[:2] == '#,' and l.find('fuzzy'): | |
| 184 fuzzy = True | |
| 185 # Skip comments | |
| 186 if l[0] == '#': | |
| 187 continue | |
| 188 # Now we are in a msgid section, output previous section | |
| 189 if l.startswith('msgid'): | |
| 190 if section == STR: | |
| 191 self.__add(msgid, msgstr, fuzzy) | |
| 192 section = ID | |
| 193 l = l[5:] | |
| 194 msgid = msgstr = '' | |
| 195 # Now we are in a msgstr section | |
| 196 elif l.startswith('msgstr'): | |
| 197 section = STR | |
| 198 l = l[6:] | |
| 199 # Skip empty lines | |
| 200 if not l.strip(): | |
| 201 continue | |
| 202 # XXX: Does this always follow Python escape semantics? | |
| 203 l = eval(l) | |
| 204 if section == ID: | |
| 205 msgid += l | |
| 206 elif section == STR: | |
| 207 msgstr += '%s\n' % l | |
| 208 else: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
209 print('Syntax error on %s:%d' % (infile, lno), |
|
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
210 'before:', file=sys.stderr) |
|
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
211 print(l, file=sys.stderr) |
| 2352 | 212 sys.exit(1) |
| 213 # Add last entry | |
| 214 if section == STR: | |
| 215 self.__add(msgid, msgstr, fuzzy) | |
| 216 | |
| 217 def evaluate(self, expression): | |
| 218 try: | |
| 219 return POEngine.evaluate(self, expression) | |
| 220 except TALESError: | |
| 221 pass | |
| 222 | |
| 223 def evaluatePathOrVar(self, expr): | |
| 224 return 'who cares' | |
| 225 | |
| 226 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 227 position=None): | |
| 228 if msgid not in self.base: | |
| 229 POEngine.translate(self, msgid, domain, mapping, default, position) | |
| 230 return 'x' | |
| 231 | |
| 232 | |
| 233 def main(): | |
| 234 try: | |
| 235 opts, args = getopt.getopt( | |
| 236 sys.argv[1:], | |
| 237 'ho:u:', | |
| 238 ['help', 'output=', 'update=']) | |
|
5248
198b6e810c67
Use Python-3-compatible 'as' syntax for except statements
Eric S. Raymond <esr@thyrsus.com>
parents:
4572
diff
changeset
|
239 except getopt.error as msg: |
| 2352 | 240 usage(1, msg) |
| 241 | |
| 242 outfile = None | |
| 243 engine = None | |
| 244 update_mode = False | |
| 245 for opt, arg in opts: | |
| 246 if opt in ('-h', '--help'): | |
| 247 usage(0) | |
| 248 elif opt in ('-o', '--output'): | |
| 249 outfile = arg | |
| 250 elif opt in ('-u', '--update'): | |
| 251 update_mode = True | |
| 252 if outfile is None: | |
| 253 outfile = arg | |
| 254 engine = UpdatePOEngine(filename=arg) | |
| 255 | |
| 256 if not args: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
257 print('nothing to do') |
| 2352 | 258 return |
| 259 | |
| 260 # We don't care about the rendered output of the .pt file | |
| 261 class Devnull: | |
| 262 def write(self, s): | |
| 263 pass | |
| 264 | |
| 265 # check if we've already instantiated an engine; | |
| 266 # if not, use the stupidest one available | |
| 267 if not engine: | |
| 268 engine = POEngine() | |
| 269 | |
| 270 # process each file specified | |
| 271 for filename in args: | |
| 272 try: | |
| 273 engine.file = filename | |
| 274 p = HTMLTALParser() | |
| 275 p.parseFile(filename) | |
| 276 program, macros = p.getCode() | |
| 277 POTALInterpreter(program, macros, engine, stream=Devnull(), | |
| 278 metal=False)() | |
| 279 except: # Hee hee, I love bare excepts! | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
280 print('There was an error processing', filename) |
| 2352 | 281 traceback.print_exc() |
| 282 | |
| 283 # Now output the keys in the engine. Write them to a file if --output or | |
| 284 # --update was specified; otherwise use standard out. | |
| 285 if (outfile is None): | |
| 286 outfile = sys.stdout | |
| 287 else: | |
|
5383
6fbb7d52e38f
Python 3 preparation: use open() instead of file().
Joseph Myers <jsm@polyomino.org.uk>
parents:
5376
diff
changeset
|
288 outfile = open(outfile, update_mode and "a" or "w") |
| 2352 | 289 |
| 290 catalog = {} | |
| 291 for domain in engine.catalog.keys(): | |
| 292 catalog.update(engine.catalog[domain]) | |
| 293 | |
| 294 messages = catalog.copy() | |
| 295 try: | |
| 296 messages.update(engine.base) | |
| 297 except AttributeError: | |
| 298 pass | |
| 299 if '' not in messages: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
300 print(pot_header % {'time': time.ctime(), |
|
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
301 'version': __version__}, file=outfile) |
| 2352 | 302 |
| 303 # XXX: You should not sort by msgid, but by filename and position. (SR) | |
|
5395
23b8e6067f7c
Python 3 preparation: update calls to dict methods.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5383
diff
changeset
|
304 msgids = sorted(catalog.keys()) |
| 2352 | 305 for msgid in msgids: |
| 306 positions = catalog[msgid] | |
| 307 for filename, position in positions: | |
| 308 outfile.write('#: %s:%s\n' % (filename, position[0])) | |
| 309 | |
|
2385
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
310 outfile.write('msgid "%s"\n' |
|
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
311 % msgid.replace('"', '\\"').replace("\n", '\\n"\n"')) |
| 2352 | 312 outfile.write('msgstr ""\n') |
| 313 outfile.write('\n') | |
| 314 | |
| 315 | |
| 316 if __name__ == '__main__': | |
| 317 main() |
