changeset 62:2df27f49c320 trunk

The order of extraction methods is now preserved (see #10).
author cmlenz
date Fri, 08 Jun 2007 12:41:00 +0000
parents 9d13b9a5d727
children 47353672f1a8
files babel/messages/extract.py babel/messages/frontend.py babel/tests/numbers.py babel/util.py doc/setup.txt
diffstat 5 files changed, 85 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- 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 = {}
--- 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 @@
            <http://peak.telecommunity.com/DevCenter/setuptools#adding-setup-arguments>`_
     """
     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)
--- 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')
 
 
--- 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):
--- 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                                     |
+  +-----------------------------+----------------------------------------------+
Copyright (C) 2012-2017 Edgewall Software