142
|
1 #!/usr/bin/env python
|
|
2 # -*- coding: utf-8 -*-
|
|
3 #
|
|
4 # Copyright (C) 2007 Edgewall Software
|
|
5 # All rights reserved.
|
|
6 #
|
|
7 # This software is licensed as described in the file COPYING, which
|
|
8 # you should have received as part of this distribution. The terms
|
|
9 # are also available at http://babel.edgewall.org/wiki/License.
|
|
10 #
|
|
11 # This software consists of voluntary contributions made by many
|
|
12 # individuals. For the exact contribution history, see the revision
|
|
13 # history and logs, available at http://babel.edgewall.org/log/.
|
|
14
|
|
15 """Frontends for the message extraction functionality."""
|
|
16
|
|
17 from ConfigParser import RawConfigParser
|
|
18 from datetime import datetime
|
|
19 from distutils import log
|
|
20 from distutils.cmd import Command
|
|
21 from distutils.errors import DistutilsOptionError, DistutilsSetupError
|
|
22 from optparse import OptionParser
|
|
23 import os
|
|
24 import re
|
|
25 from StringIO import StringIO
|
|
26 import sys
|
|
27
|
|
28 from babel import __version__ as VERSION
|
|
29 from babel import Locale
|
|
30 from babel.core import UnknownLocaleError
|
|
31 from babel.messages.catalog import Catalog
|
|
32 from babel.messages.extract import extract_from_dir, DEFAULT_KEYWORDS, \
|
|
33 DEFAULT_MAPPING
|
|
34 from babel.messages.pofile import read_po, write_po
|
|
35 from babel.messages.plurals import PLURALS
|
|
36 from babel.util import odict, LOCALTZ
|
|
37
|
|
38 __all__ = ['CommandLineInterface', 'extract_messages',
|
|
39 'check_message_extractors', 'main']
|
|
40 __docformat__ = 'restructuredtext en'
|
|
41
|
|
42
|
|
43 class extract_messages(Command):
|
|
44 """Message extraction command for use in ``setup.py`` scripts.
|
|
45
|
|
46 If correctly installed, this command is available to Setuptools-using
|
|
47 setup scripts automatically. For projects using plain old ``distutils``,
|
|
48 the command needs to be registered explicitly in ``setup.py``::
|
|
49
|
|
50 from babel.messages.frontend import extract_messages
|
|
51
|
|
52 setup(
|
|
53 ...
|
|
54 cmdclass = {'extract_messages': extract_messages}
|
|
55 )
|
|
56
|
|
57 :see: `Integrating new distutils commands <http://docs.python.org/dist/node32.html>`_
|
|
58 :see: `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
|
59 """
|
|
60
|
|
61 description = 'extract localizable strings from the project code'
|
|
62 user_options = [
|
|
63 ('charset=', None,
|
|
64 'charset to use in the output file'),
|
|
65 ('keywords=', 'k',
|
|
66 'space-separated list of keywords to look for in addition to the '
|
|
67 'defaults'),
|
|
68 ('no-default-keywords', None,
|
|
69 'do not include the default keywords'),
|
|
70 ('mapping-file=', 'F',
|
|
71 'path to the mapping configuration file'),
|
|
72 ('no-location', None,
|
|
73 'do not include location comments with filename and line number'),
|
|
74 ('omit-header', None,
|
|
75 'do not include msgid "" entry in header'),
|
|
76 ('output-file=', 'o',
|
|
77 'name of the output file'),
|
|
78 ('width=', 'w',
|
|
79 'set output line width (default 76)'),
|
|
80 ('no-wrap', None,
|
|
81 'do not break long message lines, longer than the output line width, '
|
|
82 'into several lines'),
|
|
83 ('sort-output', None,
|
|
84 'generate sorted output (default False)'),
|
|
85 ('sort-by-file', None,
|
|
86 'sort output by file location (default False)'),
|
|
87 ('msgid-bugs-address=', None,
|
|
88 'set report address for msgid'),
|
|
89 ('copyright-holder=', None,
|
|
90 'set copyright holder in output'),
|
|
91 ('add-comments=', 'c',
|
|
92 'place comment block with TAG (or those preceding keyword lines) in '
|
|
93 'output file. Seperate multiple TAGs with commas(,)'),
|
|
94 ('input-dirs=', None,
|
|
95 'directories that should be scanned for messages'),
|
|
96 ]
|
|
97 boolean_options = [
|
|
98 'no-default-keywords', 'no-location', 'omit-header', 'no-wrap',
|
|
99 'sort-output', 'sort-by-file'
|
|
100 ]
|
|
101
|
|
102 def initialize_options(self):
|
|
103 self.charset = 'utf-8'
|
|
104 self.keywords = ''
|
|
105 self._keywords = DEFAULT_KEYWORDS.copy()
|
|
106 self.no_default_keywords = False
|
|
107 self.mapping_file = None
|
|
108 self.no_location = False
|
|
109 self.omit_header = False
|
|
110 self.output_file = None
|
|
111 self.input_dirs = None
|
|
112 self.width = 76
|
|
113 self.no_wrap = False
|
|
114 self.sort_output = False
|
|
115 self.sort_by_file = False
|
|
116 self.msgid_bugs_address = None
|
|
117 self.copyright_holder = None
|
|
118 self.add_comments = None
|
|
119 self._add_comments = []
|
|
120
|
|
121 def finalize_options(self):
|
|
122 if self.no_default_keywords and not self.keywords:
|
|
123 raise DistutilsOptionError('you must specify new keywords if you '
|
|
124 'disable the default ones')
|
|
125 if self.no_default_keywords:
|
|
126 self._keywords = {}
|
|
127 if self.keywords:
|
|
128 self._keywords.update(parse_keywords(self.keywords.split()))
|
|
129
|
|
130 if not self.output_file:
|
|
131 raise DistutilsOptionError('no output file specified')
|
|
132 if self.no_wrap and self.width:
|
|
133 raise DistutilsOptionError("'--no-wrap' and '--width' are mutually "
|
|
134 "exclusive")
|
|
135 if self.no_wrap:
|
|
136 self.width = None
|
|
137 else:
|
|
138 self.width = int(self.width)
|
|
139
|
|
140 if self.sort_output and self.sort_by_file:
|
|
141 raise DistutilsOptionError("'--sort-output' and '--sort-by-file' "
|
|
142 "are mutually exclusive")
|
|
143
|
|
144 if not self.input_dirs:
|
|
145 self.input_dirs = dict.fromkeys([k.split('.',1)[0]
|
|
146 for k in self.distribution.packages
|
|
147 ]).keys()
|
|
148
|
|
149 if self.add_comments:
|
|
150 self._add_comments = self.add_comments.split(',')
|
|
151
|
|
152 def run(self):
|
|
153 mappings = self._get_mappings()
|
|
154 outfile = open(self.output_file, 'w')
|
|
155 try:
|
|
156 catalog = Catalog(project=self.distribution.get_name(),
|
|
157 version=self.distribution.get_version(),
|
|
158 msgid_bugs_address=self.msgid_bugs_address,
|
|
159 copyright_holder=self.copyright_holder,
|
|
160 charset=self.charset)
|
|
161
|
|
162 for dirname, (method_map, options_map) in mappings.items():
|
|
163 def callback(filename, method, options):
|
|
164 if method == 'ignore':
|
|
165 return
|
|
166 filepath = os.path.normpath(os.path.join(dirname, filename))
|
|
167 optstr = ''
|
|
168 if options:
|
|
169 optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for
|
|
170 k, v in options.items()])
|
|
171 log.info('extracting messages from %s%s'
|
|
172 % (filepath, optstr))
|
|
173
|
|
174 extracted = extract_from_dir(dirname, method_map, options_map,
|
|
175 keywords=self._keywords,
|
|
176 comment_tags=self._add_comments,
|
|
177 callback=callback)
|
|
178 for filename, lineno, message, comments in extracted:
|
|
179 filepath = os.path.normpath(os.path.join(dirname, filename))
|
|
180 catalog.add(message, None, [(filepath, lineno)],
|
|
181 auto_comments=comments)
|
|
182
|
|
183 log.info('writing PO template file to %s' % self.output_file)
|
|
184 write_po(outfile, catalog, width=self.width,
|
|
185 no_location=self.no_location,
|
|
186 omit_header=self.omit_header,
|
|
187 sort_output=self.sort_output,
|
|
188 sort_by_file=self.sort_by_file)
|
|
189 finally:
|
|
190 outfile.close()
|
|
191
|
|
192 def _get_mappings(self):
|
|
193 mappings = {}
|
|
194
|
|
195 if self.mapping_file:
|
|
196 fileobj = open(self.mapping_file, 'U')
|
|
197 try:
|
|
198 method_map, options_map = parse_mapping(fileobj)
|
|
199 for dirname in self.input_dirs:
|
|
200 mappings[dirname] = method_map, options_map
|
|
201 finally:
|
|
202 fileobj.close()
|
|
203
|
|
204 elif getattr(self.distribution, 'message_extractors', None):
|
|
205 message_extractors = self.distribution.message_extractors
|
|
206 for dirname, mapping in message_extractors.items():
|
|
207 if isinstance(mapping, basestring):
|
|
208 method_map, options_map = parse_mapping(StringIO(mapping))
|
|
209 else:
|
|
210 method_map, options_map = [], {}
|
|
211 for pattern, method, options in mapping:
|
|
212 method_map.append((pattern, method))
|
|
213 options_map[pattern] = options or {}
|
|
214 mappings[dirname] = method_map, options_map
|
|
215
|
|
216 else:
|
|
217 for dirname in self.input_dirs:
|
|
218 mappings[dirname] = DEFAULT_MAPPING, {}
|
|
219
|
|
220 return mappings
|
|
221
|
|
222
|
|
223 def check_message_extractors(dist, name, value):
|
|
224 """Validate the ``message_extractors`` keyword argument to ``setup()``.
|
|
225
|
|
226 :param dist: the distutils/setuptools ``Distribution`` object
|
|
227 :param name: the name of the keyword argument (should always be
|
|
228 "message_extractors")
|
|
229 :param value: the value of the keyword argument
|
|
230 :raise `DistutilsSetupError`: if the value is not valid
|
|
231 :see: `Adding setup() arguments
|
|
232 <http://peak.telecommunity.com/DevCenter/setuptools#adding-setup-arguments>`_
|
|
233 """
|
|
234 assert name == 'message_extractors'
|
|
235 if not isinstance(value, dict):
|
|
236 raise DistutilsSetupError('the value of the "message_extractors" '
|
|
237 'parameter must be a dictionary')
|
|
238
|
|
239
|
|
240 class new_catalog(Command):
|
|
241 """New catalog command for use in ``setup.py`` scripts.
|
|
242
|
|
243 If correctly installed, this command is available to Setuptools-using
|
|
244 setup scripts automatically. For projects using plain old ``distutils``,
|
|
245 the command needs to be registered explicitly in ``setup.py``::
|
|
246
|
|
247 from babel.messages.frontend import new_catalog
|
|
248
|
|
249 setup(
|
|
250 ...
|
|
251 cmdclass = {'new_catalog': new_catalog}
|
|
252 )
|
|
253
|
|
254 :see: `Integrating new distutils commands <http://docs.python.org/dist/node32.html>`_
|
|
255 :see: `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
|
256 """
|
|
257
|
|
258 description = 'create new catalogs based on a catalog template'
|
|
259 user_options = [
|
|
260 ('domain=', 'D',
|
|
261 "domain of PO file (default 'messages')"),
|
|
262 ('input-file=', 'i',
|
|
263 'name of the input file'),
|
|
264 ('output-dir=', 'd',
|
|
265 'path to output directory'),
|
|
266 ('output-file=', 'o',
|
|
267 "name of the output file (default "
|
|
268 "'<output_dir>/<locale>/LC_MESSAGES/<domain>.po')"),
|
|
269 ('locale=', 'l',
|
|
270 'locale for the new localized catalog'),
|
|
271 ]
|
|
272
|
|
273 def initialize_options(self):
|
|
274 self.output_dir = None
|
|
275 self.output_file = None
|
|
276 self.input_file = None
|
|
277 self.locale = None
|
|
278 self.domain = 'messages'
|
|
279
|
|
280 def finalize_options(self):
|
|
281 if not self.input_file:
|
|
282 raise DistutilsOptionError('you must specify the input file')
|
|
283
|
|
284 if not self.locale:
|
|
285 raise DistutilsOptionError('you must provide a locale for the '
|
|
286 'new catalog')
|
|
287 try:
|
|
288 self._locale = Locale.parse(self.locale)
|
|
289 except UnknownLocaleError, e:
|
|
290 raise DistutilsOptionError(e)
|
|
291
|
|
292 if not self.output_file and not self.output_dir:
|
|
293 raise DistutilsOptionError('you must specify the output directory')
|
|
294 if not self.output_file:
|
|
295 self.output_file = os.path.join(self.output_dir, self.locale,
|
|
296 'LC_MESSAGES', self.domain + '.po')
|
|
297
|
|
298 if not os.path.exists(os.path.dirname(self.output_file)):
|
|
299 os.makedirs(os.path.dirname(self.output_file))
|
|
300
|
|
301 def run(self):
|
|
302 log.info('creating catalog %r based on %r', self.output_file,
|
|
303 self.input_file)
|
|
304
|
|
305 infile = open(self.input_file, 'r')
|
|
306 try:
|
|
307 catalog = read_po(infile)
|
|
308 finally:
|
|
309 infile.close()
|
|
310
|
|
311 catalog.locale = self._locale
|
|
312
|
|
313 outfile = open(self.output_file, 'w')
|
|
314 try:
|
|
315 write_po(outfile, catalog)
|
|
316 finally:
|
|
317 outfile.close()
|
|
318
|
|
319
|
|
320 class CommandLineInterface(object):
|
|
321 """Command-line interface.
|
|
322
|
|
323 This class provides a simple command-line interface to the message
|
|
324 extraction and PO file generation functionality.
|
|
325 """
|
|
326
|
|
327 usage = '%%prog %s [options] %s'
|
|
328 version = '%%prog %s' % VERSION
|
|
329 commands = ['extract', 'init']
|
|
330 command_descriptions = {
|
|
331 'extract': 'extract messages from source files and generate a POT file',
|
|
332 'init': 'create new message catalogs from a template'
|
|
333 }
|
|
334
|
|
335 def run(self, argv=sys.argv):
|
|
336 """Main entry point of the command-line interface.
|
|
337
|
|
338 :param argv: list of arguments passed on the command-line
|
|
339 """
|
|
340 self.parser = OptionParser(usage=self.usage % ('command', '[args]'),
|
|
341 version=self.version)
|
|
342 self.parser.disable_interspersed_args()
|
|
343 self.parser.print_help = self._help
|
|
344 options, args = self.parser.parse_args(argv[1:])
|
|
345 if not args:
|
|
346 self.parser.error('incorrect number of arguments')
|
|
347
|
|
348 cmdname = args[0]
|
|
349 if cmdname not in self.commands:
|
|
350 self.parser.error('unknown command "%s"' % cmdname)
|
|
351
|
|
352 getattr(self, cmdname)(args[1:])
|
|
353
|
|
354 def _help(self):
|
|
355 print self.parser.format_help()
|
|
356 print "commands:"
|
|
357 longest = max([len(command) for command in self.commands])
|
|
358 format = " %%-%ds %%s" % max(11, longest)
|
|
359 self.commands.sort()
|
|
360 for command in self.commands:
|
|
361 print format % (command, self.command_descriptions[command])
|
|
362
|
|
363 def extract(self, argv):
|
|
364 """Subcommand for extracting messages from source files and generating
|
|
365 a POT file.
|
|
366
|
|
367 :param argv: the command arguments
|
|
368 """
|
|
369 parser = OptionParser(usage=self.usage % ('extract', 'dir1 <dir2> ...'),
|
|
370 description=self.command_descriptions['extract'])
|
|
371 parser.add_option('--charset', dest='charset',
|
|
372 help='charset to use in the output')
|
|
373 parser.add_option('-k', '--keyword', dest='keywords', action='append',
|
|
374 help='keywords to look for in addition to the '
|
|
375 'defaults. You can specify multiple -k flags on '
|
|
376 'the command line.')
|
|
377 parser.add_option('--no-default-keywords', dest='no_default_keywords',
|
|
378 action='store_true',
|
|
379 help="do not include the default keywords")
|
|
380 parser.add_option('--mapping', '-F', dest='mapping_file',
|
|
381 help='path to the extraction mapping file')
|
|
382 parser.add_option('--no-location', dest='no_location',
|
|
383 action='store_true',
|
|
384 help='do not include location comments with filename '
|
|
385 'and line number')
|
|
386 parser.add_option('--omit-header', dest='omit_header',
|
|
387 action='store_true',
|
|
388 help='do not include msgid "" entry in header')
|
|
389 parser.add_option('-o', '--output', dest='output',
|
|
390 help='path to the output POT file')
|
|
391 parser.add_option('-w', '--width', dest='width', type='int',
|
|
392 help="set output line width (default %default)")
|
|
393 parser.add_option('--no-wrap', dest='no_wrap', action = 'store_true',
|
|
394 help='do not break long message lines, longer than '
|
|
395 'the output line width, into several lines')
|
|
396 parser.add_option('--sort-output', dest='sort_output',
|
|
397 action='store_true',
|
|
398 help='generate sorted output (default False)')
|
|
399 parser.add_option('--sort-by-file', dest='sort_by_file',
|
|
400 action='store_true',
|
|
401 help='sort output by file location (default False)')
|
|
402 parser.add_option('--msgid-bugs-address', dest='msgid_bugs_address',
|
|
403 metavar='EMAIL@ADDRESS',
|
|
404 help='set report address for msgid')
|
|
405 parser.add_option('--copyright-holder', dest='copyright_holder',
|
|
406 help='set copyright holder in output')
|
|
407 parser.add_option('--add-comments', '-c', dest='comment_tags',
|
|
408 metavar='TAG', action='append',
|
|
409 help='place comment block with TAG (or those '
|
|
410 'preceding keyword lines) in output file. One '
|
|
411 'TAG per argument call')
|
|
412
|
|
413 parser.set_defaults(charset='utf-8', keywords=[],
|
|
414 no_default_keywords=False, no_location=False,
|
|
415 omit_header = False, width=76, no_wrap=False,
|
|
416 sort_output=False, sort_by_file=False,
|
|
417 comment_tags=[])
|
|
418 options, args = parser.parse_args(argv)
|
|
419 if not args:
|
|
420 parser.error('incorrect number of arguments')
|
|
421
|
|
422 if options.output not in (None, '-'):
|
|
423 outfile = open(options.output, 'w')
|
|
424 else:
|
|
425 outfile = sys.stdout
|
|
426
|
|
427 keywords = DEFAULT_KEYWORDS.copy()
|
|
428 if options.no_default_keywords:
|
|
429 if not options.keywords:
|
|
430 parser.error('you must specify new keywords if you disable the '
|
|
431 'default ones')
|
|
432 keywords = {}
|
|
433 if options.keywords:
|
|
434 keywords.update(parse_keywords(options.keywords))
|
|
435
|
|
436 if options.mapping_file:
|
|
437 fileobj = open(options.mapping_file, 'U')
|
|
438 try:
|
|
439 method_map, options_map = parse_mapping(fileobj)
|
|
440 finally:
|
|
441 fileobj.close()
|
|
442 else:
|
|
443 method_map = DEFAULT_MAPPING
|
|
444 options_map = {}
|
|
445
|
|
446 if options.width and options.no_wrap:
|
|
447 parser.error("'--no-wrap' and '--width' are mutually exclusive.")
|
|
448 elif not options.width and not options.no_wrap:
|
|
449 options.width = 76
|
|
450 elif not options.width and options.no_wrap:
|
|
451 options.width = 0
|
|
452
|
|
453 if options.sort_output and options.sort_by_file:
|
|
454 parser.error("'--sort-output' and '--sort-by-file' are mutually "
|
|
455 "exclusive")
|
|
456
|
|
457 try:
|
|
458 catalog = Catalog(msgid_bugs_address=options.msgid_bugs_address,
|
|
459 copyright_holder=options.copyright_holder,
|
|
460 charset=options.charset)
|
|
461
|
|
462 for dirname in args:
|
|
463 if not os.path.isdir(dirname):
|
|
464 parser.error('%r is not a directory' % dirname)
|
|
465 extracted = extract_from_dir(dirname, method_map, options_map,
|
|
466 keywords, options.comment_tags)
|
|
467 for filename, lineno, message, comments in extracted:
|
|
468 filepath = os.path.normpath(os.path.join(dirname, filename))
|
|
469 catalog.add(message, None, [(filepath, lineno)],
|
|
470 auto_comments=comments)
|
|
471
|
|
472 write_po(outfile, catalog, width=options.width,
|
|
473 no_location=options.no_location,
|
|
474 omit_header=options.omit_header,
|
|
475 sort_output=options.sort_output,
|
|
476 sort_by_file=options.sort_by_file)
|
|
477 finally:
|
|
478 if options.output:
|
|
479 outfile.close()
|
|
480
|
|
481 def init(self, argv):
|
|
482 """Subcommand for creating new message catalogs from a template.
|
|
483
|
|
484 :param argv: the command arguments
|
|
485 """
|
|
486 parser = OptionParser(usage=self.usage % ('init',''),
|
|
487 description=self.command_descriptions['init'])
|
|
488 parser.add_option('--domain', '-D', dest='domain',
|
|
489 help="domain of PO file (default '%default')")
|
|
490 parser.add_option('--input-file', '-i', dest='input_file',
|
|
491 metavar='FILE', help='name of the input file')
|
|
492 parser.add_option('--output-dir', '-d', dest='output_dir',
|
|
493 metavar='DIR', help='path to output directory')
|
|
494 parser.add_option('--output-file', '-o', dest='output_file',
|
|
495 metavar='FILE',
|
|
496 help="name of the output file (default "
|
|
497 "'<output_dir>/<locale>/LC_MESSAGES/"
|
|
498 "<domain>.po')")
|
|
499 parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE',
|
|
500 help='locale for the new localized catalog')
|
|
501
|
|
502 parser.set_defaults(domain='messages')
|
|
503 options, args = parser.parse_args(argv)
|
|
504
|
|
505 if not options.locale:
|
|
506 parser.error('you must provide a locale for the new catalog')
|
|
507 try:
|
|
508 locale = Locale.parse(options.locale)
|
|
509 except UnknownLocaleError, e:
|
|
510 parser.error(e)
|
|
511
|
|
512 if not options.input_file:
|
|
513 parser.error('you must specify the input file')
|
|
514
|
|
515 if not options.output_file and not options.output_dir:
|
|
516 parser.error('you must specify the output file or directory')
|
|
517
|
|
518 if not options.output_file:
|
|
519 options.output_file = os.path.join(options.output_dir,
|
|
520 options.locale, 'LC_MESSAGES',
|
|
521 options.domain + '.po')
|
|
522 if not os.path.exists(os.path.dirname(options.output_file)):
|
|
523 os.makedirs(os.path.dirname(options.output_file))
|
|
524
|
|
525 infile = open(options.input_file, 'r')
|
|
526 try:
|
|
527 catalog = read_po(infile)
|
|
528 finally:
|
|
529 infile.close()
|
|
530
|
|
531 catalog.locale = locale
|
|
532 catalog.revision_date = datetime.now(LOCALTZ)
|
|
533
|
|
534 print 'creating catalog %r based on %r' % (options.output_file,
|
|
535 options.input_file)
|
|
536
|
|
537 outfile = open(options.output_file, 'w')
|
|
538 try:
|
|
539 write_po(outfile, catalog)
|
|
540 finally:
|
|
541 outfile.close()
|
|
542
|
|
543 def main():
|
|
544 CommandLineInterface().run(sys.argv)
|
|
545
|
|
546 def parse_mapping(fileobj, filename=None):
|
|
547 """Parse an extraction method mapping from a file-like object.
|
|
548
|
|
549 >>> buf = StringIO('''
|
|
550 ... # Python source files
|
|
551 ... [python: **.py]
|
|
552 ...
|
|
553 ... # Genshi templates
|
|
554 ... [genshi: **/templates/**.html]
|
|
555 ... include_attrs =
|
|
556 ... [genshi: **/templates/**.txt]
|
|
557 ... template_class = genshi.template.text.TextTemplate
|
|
558 ... encoding = latin-1
|
|
559 ... ''')
|
|
560
|
|
561 >>> method_map, options_map = parse_mapping(buf)
|
|
562
|
|
563 >>> method_map[0]
|
|
564 ('**.py', 'python')
|
|
565 >>> options_map['**.py']
|
|
566 {}
|
|
567 >>> method_map[1]
|
|
568 ('**/templates/**.html', 'genshi')
|
|
569 >>> options_map['**/templates/**.html']['include_attrs']
|
|
570 ''
|
|
571 >>> method_map[2]
|
|
572 ('**/templates/**.txt', 'genshi')
|
|
573 >>> options_map['**/templates/**.txt']['template_class']
|
|
574 'genshi.template.text.TextTemplate'
|
|
575 >>> options_map['**/templates/**.txt']['encoding']
|
|
576 'latin-1'
|
|
577
|
|
578 :param fileobj: a readable file-like object containing the configuration
|
|
579 text to parse
|
|
580 :return: a `(method_map, options_map)` tuple
|
|
581 :rtype: `tuple`
|
|
582 :see: `extract_from_directory`
|
|
583 """
|
|
584 method_map = []
|
|
585 options_map = {}
|
|
586
|
|
587 parser = RawConfigParser()
|
|
588 parser._sections = odict(parser._sections) # We need ordered sections
|
|
589 parser.readfp(fileobj, filename)
|
|
590 for section in parser.sections():
|
|
591 method, pattern = [part.strip() for part in section.split(':', 1)]
|
|
592 method_map.append((pattern, method))
|
|
593 options_map[pattern] = dict(parser.items(section))
|
|
594
|
|
595 return (method_map, options_map)
|
|
596
|
|
597 def parse_keywords(strings=[]):
|
|
598 """Parse keywords specifications from the given list of strings.
|
|
599
|
|
600 >>> kw = parse_keywords(['_', 'dgettext:2', 'dngettext:2,3'])
|
|
601 >>> for keyword, indices in sorted(kw.items()):
|
|
602 ... print (keyword, indices)
|
|
603 ('_', None)
|
|
604 ('dgettext', (2,))
|
|
605 ('dngettext', (2, 3))
|
|
606 """
|
|
607 keywords = {}
|
|
608 for string in strings:
|
|
609 if ':' in string:
|
|
610 funcname, indices = string.split(':')
|
|
611 else:
|
|
612 funcname, indices = string, None
|
|
613 if funcname not in keywords:
|
|
614 if indices:
|
|
615 indices = tuple([(int(x)) for x in indices.split(',')])
|
|
616 keywords[funcname] = indices
|
|
617 return keywords
|
|
618
|
|
619
|
|
620 if __name__ == '__main__':
|
|
621 main()
|