Mercurial > p > roundup > code
annotate roundup/cgi/TAL/talgettext.py @ 8478:ed4ef394d5d6
doc: initial attempt to document setup of pgp support for email.
Used an AI assistant to help write this. Basic gpg commands seem to
work, but I have not tested this totally. Docs basically follow the
setup used for pgp testing in the test suite.
It looks like roundup accepts signed emails as well as encrypted
and signed emails. But it does not generate signed emails.
Also it looks like there is no PGP support for alternate email
addresses. Only primary addresses can do PGP emails.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Sat, 15 Nov 2025 16:59:24 -0500 |
| parents | f2fade4552c5 |
| children | 9c3ec0a5c7fc |
| 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 def usage(code, msg=''): | |
| 70 # Python 2.1 required | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
71 print(__doc__, file=sys.stderr) |
| 2352 | 72 if msg: |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
73 print(msg, file=sys.stderr) |
| 2352 | 74 sys.exit(code) |
| 75 | |
| 76 | |
| 77 class POTALInterpreter(TALInterpreter): | |
| 78 def translate(self, msgid, default, i18ndict=None, obj=None): | |
| 79 # XXX is this right? | |
| 80 if i18ndict is None: | |
| 81 i18ndict = {} | |
| 82 if obj: | |
| 83 i18ndict.update(obj) | |
| 84 # XXX Mmmh, it seems that sometimes the msgid is None; is that really | |
| 85 # possible? | |
| 86 if msgid is None: | |
| 87 return None | |
| 88 # XXX We need to pass in one of context or target_language | |
| 89 return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, | |
| 90 position=self.position, default=default) | |
| 91 | |
| 92 | |
| 93 class POEngine(DummyEngine): | |
| 94 #__implements__ = ITALESEngine | |
| 95 | |
| 96 def __init__(self, macros=None): | |
| 97 self.catalog = {} | |
| 98 DummyEngine.__init__(self, macros) | |
| 99 | |
| 100 def evaluate(*args): | |
| 101 return '' # who cares | |
| 102 | |
| 103 def evaluatePathOrVar(*args): | |
| 104 return '' # who cares | |
| 105 | |
| 106 def evaluateSequence(self, expr): | |
| 107 return (0,) # dummy | |
| 108 | |
| 109 def evaluateBoolean(self, expr): | |
| 110 return True # dummy | |
| 111 | |
| 112 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 113 # XXX position is not part of the ITALESEngine | |
| 114 # interface | |
| 115 position=None): | |
| 116 | |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
117 if not msgid: return 'x' |
|
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
118 |
| 2352 | 119 if domain not in self.catalog: |
| 120 self.catalog[domain] = {} | |
| 121 domain = self.catalog[domain] | |
| 122 | |
| 123 if msgid not in domain: | |
| 124 domain[msgid] = [] | |
| 125 domain[msgid].append((self.file, position)) | |
| 126 return 'x' | |
| 127 | |
| 128 | |
| 129 class UpdatePOEngine(POEngine): | |
| 130 """A slightly-less braindead POEngine which supports loading an existing | |
| 131 .po file first.""" | |
| 132 | |
| 133 def __init__ (self, macros=None, filename=None): | |
| 134 POEngine.__init__(self, macros) | |
| 135 | |
| 136 self._filename = filename | |
| 137 self._loadFile() | |
| 138 self.base = self.catalog | |
| 139 self.catalog = {} | |
| 140 | |
| 141 def __add(self, id, s, fuzzy): | |
| 142 "Add a non-fuzzy translation to the dictionary." | |
| 143 if not fuzzy and str: | |
| 144 # check for multi-line values and munge them appropriately | |
| 145 if '\n' in s: | |
| 146 lines = s.rstrip().split('\n') | |
| 147 s = NLSTR.join(lines) | |
| 148 self.catalog[id] = s | |
| 149 | |
| 150 def _loadFile(self): | |
| 151 # shamelessly cribbed from Python's Tools/i18n/msgfmt.py | |
| 152 # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) | |
| 153 # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) | |
| 154 | |
| 155 ID = 1 | |
| 156 STR = 2 | |
| 157 | |
| 158 try: | |
| 159 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
|
160 except IOError as msg: |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
161 print(msg, file=sys.stderr) |
| 2352 | 162 sys.exit(1) |
| 163 | |
| 164 section = None | |
| 165 fuzzy = False | |
| 166 | |
| 167 # Parse the catalog | |
| 168 lno = 0 | |
| 169 for l in lines: | |
| 170 lno += True | |
| 171 # If we get a comment line after a msgstr, this is a new entry | |
| 172 if l[0] == '#' and section == STR: | |
| 173 self.__add(msgid, msgstr, fuzzy) | |
| 174 section = None | |
| 175 fuzzy = False | |
| 176 # Record a fuzzy mark | |
| 177 if l[:2] == '#,' and l.find('fuzzy'): | |
| 178 fuzzy = True | |
| 179 # Skip comments | |
| 180 if l[0] == '#': | |
| 181 continue | |
| 182 # Now we are in a msgid section, output previous section | |
| 183 if l.startswith('msgid'): | |
| 184 if section == STR: | |
| 185 self.__add(msgid, msgstr, fuzzy) | |
| 186 section = ID | |
| 187 l = l[5:] | |
| 188 msgid = msgstr = '' | |
| 189 # Now we are in a msgstr section | |
| 190 elif l.startswith('msgstr'): | |
| 191 section = STR | |
| 192 l = l[6:] | |
| 193 # Skip empty lines | |
| 194 if not l.strip(): | |
| 195 continue | |
| 196 # XXX: Does this always follow Python escape semantics? | |
| 197 l = eval(l) | |
| 198 if section == ID: | |
| 199 msgid += l | |
| 200 elif section == STR: | |
| 201 msgstr += '%s\n' % l | |
| 202 else: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
203 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
|
204 'before:', file=sys.stderr) |
|
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
205 print(l, file=sys.stderr) |
| 2352 | 206 sys.exit(1) |
| 207 # Add last entry | |
| 208 if section == STR: | |
| 209 self.__add(msgid, msgstr, fuzzy) | |
| 210 | |
| 211 def evaluate(self, expression): | |
| 212 try: | |
| 213 return POEngine.evaluate(self, expression) | |
| 214 except TALESError: | |
| 215 pass | |
| 216 | |
| 217 def evaluatePathOrVar(self, expr): | |
| 218 return 'who cares' | |
| 219 | |
| 220 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 221 position=None): | |
| 222 if msgid not in self.base: | |
| 223 POEngine.translate(self, msgid, domain, mapping, default, position) | |
| 224 return 'x' | |
| 225 | |
| 226 | |
| 227 def main(): | |
| 228 try: | |
| 229 opts, args = getopt.getopt( | |
| 230 sys.argv[1:], | |
| 231 'ho:u:', | |
| 232 ['help', 'output=', 'update=']) | |
|
5248
198b6e810c67
Use Python-3-compatible 'as' syntax for except statements
Eric S. Raymond <esr@thyrsus.com>
parents:
4572
diff
changeset
|
233 except getopt.error as msg: |
| 2352 | 234 usage(1, msg) |
| 235 | |
| 236 outfile = None | |
| 237 engine = None | |
| 238 update_mode = False | |
| 239 for opt, arg in opts: | |
| 240 if opt in ('-h', '--help'): | |
| 241 usage(0) | |
| 242 elif opt in ('-o', '--output'): | |
| 243 outfile = arg | |
| 244 elif opt in ('-u', '--update'): | |
| 245 update_mode = True | |
| 246 if outfile is None: | |
| 247 outfile = arg | |
| 248 engine = UpdatePOEngine(filename=arg) | |
| 249 | |
| 250 if not args: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
251 print('nothing to do') |
| 2352 | 252 return |
| 253 | |
| 254 # We don't care about the rendered output of the .pt file | |
| 255 class Devnull: | |
| 256 def write(self, s): | |
| 257 pass | |
| 258 | |
| 259 # check if we've already instantiated an engine; | |
| 260 # if not, use the stupidest one available | |
| 261 if not engine: | |
| 262 engine = POEngine() | |
| 263 | |
| 264 # process each file specified | |
| 265 for filename in args: | |
| 266 try: | |
| 267 engine.file = filename | |
| 268 p = HTMLTALParser() | |
| 269 p.parseFile(filename) | |
| 270 program, macros = p.getCode() | |
| 271 POTALInterpreter(program, macros, engine, stream=Devnull(), | |
| 272 metal=False)() | |
| 273 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
|
274 print('There was an error processing', filename) |
| 2352 | 275 traceback.print_exc() |
| 276 | |
| 277 # Now output the keys in the engine. Write them to a file if --output or | |
| 278 # --update was specified; otherwise use standard out. | |
| 279 if (outfile is None): | |
| 280 outfile = sys.stdout | |
| 281 else: | |
|
5383
6fbb7d52e38f
Python 3 preparation: use open() instead of file().
Joseph Myers <jsm@polyomino.org.uk>
parents:
5376
diff
changeset
|
282 outfile = open(outfile, update_mode and "a" or "w") |
| 2352 | 283 |
| 284 catalog = {} | |
| 285 for domain in engine.catalog.keys(): | |
| 286 catalog.update(engine.catalog[domain]) | |
| 287 | |
| 288 messages = catalog.copy() | |
| 289 try: | |
| 290 messages.update(engine.base) | |
| 291 except AttributeError: | |
| 292 pass | |
| 293 if '' not in messages: | |
|
5376
64b05e24dbd8
Python 3 preparation: convert print to a function.
Joseph Myers <jsm@polyomino.org.uk>
parents:
5248
diff
changeset
|
294 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
|
295 'version': __version__}, file=outfile) |
| 2352 | 296 |
| 297 # 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
|
298 msgids = sorted(catalog.keys()) |
| 2352 | 299 for msgid in msgids: |
| 300 positions = catalog[msgid] | |
| 301 for filename, position in positions: | |
| 302 outfile.write('#: %s:%s\n' % (filename, position[0])) | |
| 303 | |
|
2385
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
304 outfile.write('msgid "%s"\n' |
|
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
305 % msgid.replace('"', '\\"').replace("\n", '\\n"\n"')) |
| 2352 | 306 outfile.write('msgstr ""\n') |
| 307 outfile.write('\n') | |
| 308 | |
| 309 | |
| 310 if __name__ == '__main__': | |
| 311 main() |
