Mercurial > p > roundup > code
annotate roundup/cgi/TAL/talgettext.py @ 8580:5cba36e42b8f
chore: refactor replace urlparse with urlsplit and use urllib_
Python docs recommend use of urlsplit() rather than
urlparse(). urlsplit() is a little faster and doesn't try to split the
path into path and params using the rules from an obsolete RFC.
actions.py, demo.py, rest.py, client.py
Replace urlparse() with urlsplit()
actions.py
urlsplit() produces a named tuple with one fewer elements (no
.param). So fixup calls to urlunparse() so they have the proper
number of elements in the tuple.
also merge url filtering for param and path.
demo.py, rest.py:
Replace imports from urlparse/urllib.parse with
roundup.anypy.urllib_ so we use the same interface throughout the
code base.
test/test_cgi.py:
Since actions.py filtering for invali urls not split by path/param,
fix tests for improperly quoted url's.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Sun, 19 Apr 2026 22:58:59 -0400 |
| parents | 9c3ec0a5c7fc |
| children |
| 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 | |
| 36 import sys | |
| 37 import time | |
| 38 import getopt | |
| 39 import traceback | |
| 40 | |
|
4572
3a407f0d5dad
Add import for version info
Ralf Schlatterbeck <rsc@runtux.com>
parents:
4570
diff
changeset
|
41 from roundup import __version__ |
| 2352 | 42 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser |
| 43 from roundup.cgi.TAL.TALInterpreter import TALInterpreter | |
| 44 from roundup.cgi.TAL.DummyEngine import DummyEngine | |
| 45 #from ITALES import ITALESEngine | |
| 46 from roundup.cgi.TAL.TALDefs import TALESError | |
| 47 | |
| 48 pot_header = '''\ | |
| 49 # SOME DESCRIPTIVE TITLE. | |
| 50 # Copyright (C) YEAR ORGANIZATION | |
| 51 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
| 52 # | |
| 53 msgid "" | |
| 54 msgstr "" | |
| 55 "Project-Id-Version: PACKAGE VERSION\\n" | |
| 56 "POT-Creation-Date: %(time)s\\n" | |
| 57 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" | |
| 58 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" | |
| 59 "Language-Team: LANGUAGE <LL@li.org>\\n" | |
| 60 "MIME-Version: 1.0\\n" | |
| 61 "Content-Type: text/plain; charset=CHARSET\\n" | |
| 62 "Content-Transfer-Encoding: ENCODING\\n" | |
| 63 "Generated-By: talgettext.py %(version)s\\n" | |
| 64 ''' | |
| 65 | |
| 66 NLSTR = '"\n"' | |
| 67 | |
| 68 def usage(code, msg=''): | |
| 69 # Python 2.1 required | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
70 print(__doc__, file=sys.stderr) |
| 2352 | 71 if msg: |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
72 print(msg, file=sys.stderr) |
| 2352 | 73 sys.exit(code) |
| 74 | |
| 75 | |
| 76 class POTALInterpreter(TALInterpreter): | |
| 77 def translate(self, msgid, default, i18ndict=None, obj=None): | |
| 78 # XXX is this right? | |
| 79 if i18ndict is None: | |
| 80 i18ndict = {} | |
| 81 if obj: | |
| 82 i18ndict.update(obj) | |
| 83 # XXX Mmmh, it seems that sometimes the msgid is None; is that really | |
| 84 # possible? | |
| 85 if msgid is None: | |
| 86 return None | |
| 87 # XXX We need to pass in one of context or target_language | |
| 88 return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, | |
| 89 position=self.position, default=default) | |
| 90 | |
| 91 | |
| 92 class POEngine(DummyEngine): | |
| 93 #__implements__ = ITALESEngine | |
| 94 | |
| 95 def __init__(self, macros=None): | |
| 96 self.catalog = {} | |
| 97 DummyEngine.__init__(self, macros) | |
| 98 | |
| 99 def evaluate(*args): | |
| 100 return '' # who cares | |
| 101 | |
| 102 def evaluatePathOrVar(*args): | |
| 103 return '' # who cares | |
| 104 | |
| 105 def evaluateSequence(self, expr): | |
| 106 return (0,) # dummy | |
| 107 | |
| 108 def evaluateBoolean(self, expr): | |
| 109 return True # dummy | |
| 110 | |
| 111 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 112 # XXX position is not part of the ITALESEngine | |
| 113 # interface | |
| 114 position=None): | |
| 115 | |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
116 if not msgid: return 'x' |
|
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
117 |
| 2352 | 118 if domain not in self.catalog: |
| 119 self.catalog[domain] = {} | |
| 120 domain = self.catalog[domain] | |
| 121 | |
| 122 if msgid not in domain: | |
| 123 domain[msgid] = [] | |
| 124 domain[msgid].append((self.file, position)) | |
| 125 return 'x' | |
| 126 | |
| 127 | |
| 128 class UpdatePOEngine(POEngine): | |
| 129 """A slightly-less braindead POEngine which supports loading an existing | |
| 130 .po file first.""" | |
| 131 | |
| 132 def __init__ (self, macros=None, filename=None): | |
| 133 POEngine.__init__(self, macros) | |
| 134 | |
| 135 self._filename = filename | |
| 136 self._loadFile() | |
| 137 self.base = self.catalog | |
| 138 self.catalog = {} | |
| 139 | |
| 140 def __add(self, id, s, fuzzy): | |
| 141 "Add a non-fuzzy translation to the dictionary." | |
| 142 if not fuzzy and str: | |
| 143 # check for multi-line values and munge them appropriately | |
| 144 if '\n' in s: | |
| 145 lines = s.rstrip().split('\n') | |
| 146 s = NLSTR.join(lines) | |
| 147 self.catalog[id] = s | |
| 148 | |
| 149 def _loadFile(self): | |
| 150 # shamelessly cribbed from Python's Tools/i18n/msgfmt.py | |
| 151 # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) | |
| 152 # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) | |
| 153 | |
| 154 ID = 1 | |
| 155 STR = 2 | |
| 156 | |
| 157 try: | |
| 158 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
|
159 except IOError as msg: |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
160 print(msg, file=sys.stderr) |
| 2352 | 161 sys.exit(1) |
| 162 | |
| 163 section = None | |
| 164 fuzzy = False | |
| 165 | |
| 166 # Parse the catalog | |
| 167 lno = 0 | |
| 168 for l in lines: | |
| 169 lno += True | |
| 170 # If we get a comment line after a msgstr, this is a new entry | |
| 171 if l[0] == '#' and section == STR: | |
| 172 self.__add(msgid, msgstr, fuzzy) | |
| 173 section = None | |
| 174 fuzzy = False | |
| 175 # Record a fuzzy mark | |
| 176 if l[:2] == '#,' and l.find('fuzzy'): | |
| 177 fuzzy = True | |
| 178 # Skip comments | |
| 179 if l[0] == '#': | |
| 180 continue | |
| 181 # Now we are in a msgid section, output previous section | |
| 182 if l.startswith('msgid'): | |
| 183 if section == STR: | |
| 184 self.__add(msgid, msgstr, fuzzy) | |
| 185 section = ID | |
| 186 l = l[5:] | |
| 187 msgid = msgstr = '' | |
| 188 # Now we are in a msgstr section | |
| 189 elif l.startswith('msgstr'): | |
| 190 section = STR | |
| 191 l = l[6:] | |
| 192 # Skip empty lines | |
| 193 if not l.strip(): | |
| 194 continue | |
| 195 # XXX: Does this always follow Python escape semantics? | |
| 196 l = eval(l) | |
| 197 if section == ID: | |
| 198 msgid += l | |
| 199 elif section == STR: | |
| 200 msgstr += '%s\n' % l | |
| 201 else: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
202 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
|
203 'before:', file=sys.stderr) |
|
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
204 print(l, file=sys.stderr) |
| 2352 | 205 sys.exit(1) |
| 206 # Add last entry | |
| 207 if section == STR: | |
| 208 self.__add(msgid, msgstr, fuzzy) | |
| 209 | |
| 210 def evaluate(self, expression): | |
| 211 try: | |
| 212 return POEngine.evaluate(self, expression) | |
| 213 except TALESError: | |
| 214 pass | |
| 215 | |
| 216 def evaluatePathOrVar(self, expr): | |
| 217 return 'who cares' | |
| 218 | |
| 219 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 220 position=None): | |
| 221 if msgid not in self.base: | |
| 222 POEngine.translate(self, msgid, domain, mapping, default, position) | |
| 223 return 'x' | |
| 224 | |
| 225 | |
| 226 def main(): | |
| 227 try: | |
| 228 opts, args = getopt.getopt( | |
| 229 sys.argv[1:], | |
| 230 'ho:u:', | |
| 231 ['help', 'output=', 'update=']) | |
|
5248
198b6e810c67
Use Python-3-compatible 'as' syntax for except statements
Eric S. Raymond <esr@thyrsus.com>
parents:
4572
diff
changeset
|
232 except getopt.error as msg: |
| 2352 | 233 usage(1, msg) |
| 234 | |
| 235 outfile = None | |
| 236 engine = None | |
| 237 update_mode = False | |
| 238 for opt, arg in opts: | |
| 239 if opt in ('-h', '--help'): | |
| 240 usage(0) | |
| 241 elif opt in ('-o', '--output'): | |
| 242 outfile = arg | |
| 243 elif opt in ('-u', '--update'): | |
| 244 update_mode = True | |
| 245 if outfile is None: | |
| 246 outfile = arg | |
| 247 engine = UpdatePOEngine(filename=arg) | |
| 248 | |
| 249 if not args: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
250 print('nothing to do') |
| 2352 | 251 return |
| 252 | |
| 253 # We don't care about the rendered output of the .pt file | |
| 254 class Devnull: | |
| 255 def write(self, s): | |
| 256 pass | |
| 257 | |
| 258 # check if we've already instantiated an engine; | |
| 259 # if not, use the stupidest one available | |
| 260 if not engine: | |
| 261 engine = POEngine() | |
| 262 | |
| 263 # process each file specified | |
| 264 for filename in args: | |
| 265 try: | |
| 266 engine.file = filename | |
| 267 p = HTMLTALParser() | |
| 268 p.parseFile(filename) | |
| 269 program, macros = p.getCode() | |
| 270 POTALInterpreter(program, macros, engine, stream=Devnull(), | |
| 271 metal=False)() | |
| 272 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
|
273 print('There was an error processing', filename) |
| 2352 | 274 traceback.print_exc() |
| 275 | |
| 276 # Now output the keys in the engine. Write them to a file if --output or | |
| 277 # --update was specified; otherwise use standard out. | |
| 278 if (outfile is None): | |
| 279 outfile = sys.stdout | |
| 280 else: | |
|
5383
6fbb7d52e38f
Python 3 preparation: use open() instead of file().
Joseph Myers <jsm@polyomino.org.uk>
parents:
5376
diff
changeset
|
281 outfile = open(outfile, update_mode and "a" or "w") |
| 2352 | 282 |
| 283 catalog = {} | |
| 284 for domain in engine.catalog.keys(): | |
| 285 catalog.update(engine.catalog[domain]) | |
| 286 | |
| 287 messages = catalog.copy() | |
| 288 try: | |
| 289 messages.update(engine.base) | |
| 290 except AttributeError: | |
| 291 pass | |
| 292 if '' not in messages: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
293 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
|
294 'version': __version__}, file=outfile) |
| 2352 | 295 |
| 296 # 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
|
297 msgids = sorted(catalog.keys()) |
| 2352 | 298 for msgid in msgids: |
| 299 positions = catalog[msgid] | |
| 300 for filename, position in positions: | |
| 301 outfile.write('#: %s:%s\n' % (filename, position[0])) | |
| 302 | |
|
2385
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
303 outfile.write('msgid "%s"\n' |
|
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
304 % msgid.replace('"', '\\"').replace("\n", '\\n"\n"')) |
| 2352 | 305 outfile.write('msgstr ""\n') |
| 306 outfile.write('\n') | |
| 307 | |
| 308 | |
| 309 if __name__ == '__main__': | |
| 310 main() |
