# HG changeset patch # User cmlenz # Date 1181306460 0 # Node ID 84d400066b71661b94227211ce8eaa35ec01284f # Parent da7efa40a9e252771f78f20935d9e24c75c25311 The order of extraction methods is now preserved (see #10). diff --git a/babel/messages/extract.py b/babel/messages/extract.py --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -46,7 +46,7 @@ 'dngettext': (2, 3), } -DEFAULT_MAPPING = {'**.py': 'python'} +DEFAULT_MAPPING = [('**.py', 'python')] def extract_from_dir(dirname=os.getcwd(), method_map=DEFAULT_MAPPING, options_map=None, keywords=DEFAULT_KEYWORDS, @@ -61,9 +61,9 @@ parameter, which maps extended glob patterns to extraction method names. For example, the following is the default mapping: - >>> method_map = { - ... '**.py': 'python' - ... } + >>> method_map = [ + ... ('**.py', 'python') + ... ] This basically says that files with the filename extension ".py" at any level inside the directory should be processed by the "python" extraction @@ -71,21 +71,21 @@ the documentation of the `pathmatch` function for details on the pattern syntax. - The following extended mapping would also use the "genshi" extraction method - on any file in "templates" subdirectory: + The following extended mapping would also use the "genshi" extraction + method on any file in "templates" subdirectory: - >>> method_map = { - ... '**/templates/**.*': 'genshi', - ... '**.py': 'python' - ... } + >>> method_map = [ + ... ('**/templates/**.*', 'genshi'), + ... ('**.py', 'python') + ... ] The dictionary provided by the optional `options_map` parameter augments - the mapping data. It too uses extended glob patterns as keys, but the values - are dictionaries mapping options names to option values (both strings). + these mappings. It uses extended glob patterns as keys, and the values are + dictionaries mapping options names to option values (both strings). The glob patterns of the `options_map` do not necessarily need to be the - same as those used in the pattern. For example, while all files in the - ``templates`` folders in an application may be Genshi applications, the + same as those used in the method mapping. For example, while all files in + the ``templates`` folders in an application may be Genshi applications, the options for those files may differ based on extension: >>> options_map = { @@ -99,8 +99,8 @@ ... } :param dirname: the path to the directory to extract messages from - :param method_map: a mapping of extraction method names to extended glob - patterns + :param method_map: a list of ``(pattern, method)`` tuples that maps of + extraction method names to extended glob patterns :param options_map: a dictionary of additional options (optional) :param keywords: a dictionary mapping keywords (i.e. names of functions that should be recognized as translation functions) to @@ -117,12 +117,6 @@ if options_map is None: options_map = {} - # Sort methods by pattern length - # FIXME: we'll probably need to preserve the user-supplied order in the - # frontends - methods = method_map.items() - methods.sort(lambda a,b: -cmp(len(a[0]), len(b[0]))) - absname = os.path.abspath(dirname) for root, dirnames, filenames in os.walk(absname): for subdir in dirnames: @@ -133,7 +127,7 @@ os.path.join(root, filename).replace(os.sep, '/'), dirname ) - for pattern, method in methods: + for pattern, method in method_map: if pathmatch(pattern, filename): filepath = os.path.join(absname, filename) options = {} diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2007 Edgewall Software @@ -31,6 +32,7 @@ DEFAULT_MAPPING from babel.messages.pofile import write_po, write_pot from babel.messages.plurals import PLURALS +from babel.util import odict __all__ = ['CommandLineInterface', 'extract_messages', 'check_message_extractors', 'main'] @@ -120,32 +122,11 @@ ]).keys() def run(self): - if self.mapping_file: - fileobj = open(self.mapping_file, 'U') - try: - method_map, options_map = parse_mapping(fileobj) - finally: - fileobj.close() - elif self.distribution.message_extractors: - message_extractors = self.distribution.message_extractors - if isinstance(message_extractors, basestring): - method_map, options_map = parse_mapping(StringIO( - message_extractors - )) - else: - method_map = {} - options_map = {} - for pattern, (method, options) in message_extractors.items(): - method_map[pattern] = method - options_map[pattern] = options - else: - method_map = DEFAULT_MAPPING - options_map = {} - + mappings = self._get_mappings() outfile = open(self.output_file, 'w') try: catalog = Catalog() - for dirname in self.input_dirs: + for dirname, (method_map, options_map) in mappings.items(): def callback(filename, method, options): if method == 'ignore': return @@ -172,6 +153,36 @@ finally: outfile.close() + def _get_mappings(self): + mappings = {} + + if self.mapping_file: + fileobj = open(self.mapping_file, 'U') + try: + method_map, options_map = parse_mapping(fileobj) + for dirname in self.input_dirs: + mappings[dirname] = method_map, options_map + finally: + fileobj.close() + + elif self.distribution.message_extractors: + message_extractors = self.distribution.message_extractors + for dirname, mapping in message_extractors.items(): + if isinstance(mapping, basestring): + method_map, options_map = parse_mapping(StringIO(mapping)) + else: + method_map, options_map = [], {} + for pattern, method, options in mapping: + method_map.append((pattern, method)) + options_map[pattern] = options or {} + mappings[dirname] = method_map, options_map + + else: + for dirname in self.input_dirs: + mappings[dirname] = DEFAULT_MAPPING, {} + + return mappings + def check_message_extractors(dist, name, value): """Validate the ``message_extractors`` keyword argument to ``setup()``. @@ -185,9 +196,9 @@ `_ """ assert name == 'message_extractors' - if not isinstance(value, (basestring, dict)): - raise DistutilsSetupError('the value of the "extract_messages" ' - 'parameter must be a string or dictionary') + if not isinstance(value, dict): + raise DistutilsSetupError('the value of the "message_extractors" ' + 'parameter must be a dictionary') class new_catalog(Command): @@ -429,31 +440,31 @@ >>> buf = StringIO(''' ... # Python source files - ... [python: foobar/**.py] + ... [python: **.py] ... ... # Genshi templates - ... [genshi: foobar/**/templates/**.html] + ... [genshi: **/templates/**.html] ... include_attrs = - ... [genshi: foobar/**/templates/**.txt] + ... [genshi: **/templates/**.txt] ... template_class = genshi.template.text.TextTemplate ... encoding = latin-1 ... ''') >>> method_map, options_map = parse_mapping(buf) - >>> method_map['foobar/**.py'] - 'python' - >>> options_map['foobar/**.py'] + >>> method_map[0] + ('**.py', 'python') + >>> options_map['**.py'] {} - >>> method_map['foobar/**/templates/**.html'] - 'genshi' - >>> options_map['foobar/**/templates/**.html']['include_attrs'] + >>> method_map[1] + ('**/templates/**.html', 'genshi') + >>> options_map['**/templates/**.html']['include_attrs'] '' - >>> method_map['foobar/**/templates/**.txt'] - 'genshi' - >>> options_map['foobar/**/templates/**.txt']['template_class'] + >>> method_map[2] + ('**/templates/**.txt', 'genshi') + >>> options_map['**/templates/**.txt']['template_class'] 'genshi.template.text.TextTemplate' - >>> options_map['foobar/**/templates/**.txt']['encoding'] + >>> options_map['**/templates/**.txt']['encoding'] 'latin-1' :param fileobj: a readable file-like object containing the configuration @@ -462,14 +473,15 @@ :rtype: `tuple` :see: `extract_from_directory` """ - method_map = {} + method_map = [] options_map = {} parser = RawConfigParser() + parser._sections = odict(parser._sections) # We need ordered sections parser.readfp(fileobj, filename) for section in parser.sections(): method, pattern = [part.strip() for part in section.split(':', 1)] - method_map[pattern] = method + method_map.append((pattern, method)) options_map[pattern] = dict(parser.items(section)) return (method_map, options_map) diff --git a/babel/tests/numbers.py b/babel/tests/numbers.py --- a/babel/tests/numbers.py +++ b/babel/tests/numbers.py @@ -26,14 +26,14 @@ locale='en_US'), '(12,345)') def test_default_rounding(self): - """Testing Round-Half-Even (Banker's rounding) + """ + Testing Round-Half-Even (Banker's rounding) A '5' is rounded to the closest 'even' number """ self.assertEqual(numbers.format_decimal(5.5, '0', locale='sv'), '6') self.assertEqual(numbers.format_decimal(6.5, '0', locale='sv'), '6') self.assertEqual(numbers.format_decimal(1.2325, locale='sv'), '1,232') - self.assertEqual(numbers.format_decimal(1.2325, locale='sv'), '1,232') self.assertEqual(numbers.format_decimal(1.2335, locale='sv'), '1,234') diff --git a/babel/util.py b/babel/util.py --- a/babel/util.py +++ b/babel/util.py @@ -72,8 +72,8 @@ :see: `http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747` """ - def __init__(self, dict=None): - dict.__init__(self, dict) + def __init__(self, data=None): + dict.__init__(self, data or {}) self._keys = [] def __delitem__(self, key): diff --git a/doc/setup.txt b/doc/setup.txt --- a/doc/setup.txt +++ b/doc/setup.txt @@ -58,6 +58,7 @@ --width (-w) set output line width (default 76) --no-wrap do not break long message lines, longer than the output line width, into several lines + --input-dirs directories that should be scanned for messages Running the command will produce a PO template file:: @@ -83,11 +84,13 @@ setup(... message_extractors = { - 'foobar/**.py': ('python', None), - 'foobar/**/templates/**.html': ('genshi', None), - 'foobar/**/templates/**.txt': ('genshi', { - 'template_class': 'genshi.template.text.TextTemplate' - }) + 'foobar': [ + ('**.py', ('python', None), + ('**/templates/**.html', ('genshi', None), + ('**/templates/**.txt', ('genshi', { + 'template_class': 'genshi.template.text.TextTemplate' + }) + ], }, ... @@ -124,3 +127,6 @@ | ``--no-wrap`` | do not break long message lines, longer than | | | the output line width, into several lines | +-----------------------------+----------------------------------------------+ + | ``--input-dirs`` | directories that should be scanned for | + | | messages | + +-----------------------------+----------------------------------------------+