changeset 49:9979a409589e

Support passing extraction method mapping and options from the frontends (see #4). No distutils/setuptools keyword supported yet, but the rest seems to be working okay.
author cmlenz
date Wed, 06 Jun 2007 21:03:24 +0000
parents 4da7c8f41674
children b6497cfd06d6
files babel/catalog/extract.py babel/catalog/frontend.py babel/util.py
diffstat 3 files changed, 137 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/babel/catalog/extract.py
+++ b/babel/catalog/extract.py
@@ -52,8 +52,9 @@
 }
 
 
-def extract_from_dir(dirname, method_map=DEFAULT_MAPPING,
-                     options_map=None, keywords=DEFAULT_KEYWORDS):
+def extract_from_dir(dirname=os.getcwd(), method_map=DEFAULT_MAPPING,
+                     options_map=None, keywords=DEFAULT_KEYWORDS,
+                     callback=None):
     """Extract messages from any source files found in the given directory.
     
     This function generates tuples of the form:
@@ -109,6 +110,10 @@
                      that should be recognized as translation functions) to
                      tuples that specify which of their arguments contain
                      localizable strings
+    :param callback: a function that is called for every file that message are
+                     extracted from, just before the extraction itself is
+                     performed; the function is passed the filename and the
+                     options dictionary as positional arguments, in that order
     :return: an iterator over ``(filename, lineno, funcname, message)`` tuples
     :rtype: ``iterator``
     :see: `pathmatch`
@@ -132,10 +137,12 @@
                     for opattern, odict in options_map.items():
                         if pathmatch(opattern, filename):
                             options = odict
+                    if callback:
+                        callback(filename, options)
                     for line, func, key in extract_from_file(method, filepath,
                                                              keywords=keywords,
                                                              options=options):
-                        yield filepath, line, func, key
+                        yield filename, line, func, key
 
 def extract_from_file(method, filename, keywords=DEFAULT_KEYWORDS,
                       options=None):
@@ -224,8 +231,23 @@
     """
     from genshi.filters.i18n import Translator
     from genshi.template import MarkupTemplate
-    tmpl = MarkupTemplate(fileobj, filename=getattr(fileobj, 'name'))
-    translator = Translator(None)
+
+    template_class = options.get('template_class', MarkupTemplate)
+    if isinstance(template_class, basestring):
+        module, clsname = template_class.split(':', 1)
+        template_class = getattr(__import__(module, {}, {}, [clsname]), clsname)
+    encoding = options.get('encoding', None)
+
+    ignore_tags = options.get('ignore_tags', Translator.IGNORE_TAGS)
+    if isinstance(ignore_tags, basestring):
+        ignore_tags = ignore_tags.split()
+    include_attrs = options.get('include_attrs', Translator.INCLUDE_ATTRS)
+    if isinstance(include_attrs, basestring):
+        include_attrs = include_attrs.split()
+
+    tmpl = template_class(fileobj, filename=getattr(fileobj, 'name'),
+                          encoding=encoding)
+    translator = Translator(None, ignore_tags, include_attrs)
     for message in translator.extract(tmpl.stream, gettext_functions=keywords):
         yield message
 
--- a/babel/catalog/frontend.py
+++ b/babel/catalog/frontend.py
@@ -18,10 +18,12 @@
 from distutils.errors import DistutilsOptionError
 from optparse import OptionParser
 import os
+import re
 import sys
 
 from babel import __version__ as VERSION
-from babel.catalog.extract import extract_from_dir, DEFAULT_KEYWORDS
+from babel.catalog.extract import extract_from_dir, DEFAULT_KEYWORDS, \
+                                  DEFAULT_MAPPING
 from babel.catalog.pofile import write_po
 
 __all__ = ['extract_messages', 'main']
@@ -55,6 +57,8 @@
          'defaults'),
         ('no-default-keywords', None,
          'do not include the default keywords'),
+        ('mapping-file=', 'F',
+         'path to the mapping configuration file'),
         ('no-location', None,
          'do not include location comments with filename and line number'),
         ('omit-header', None,
@@ -73,21 +77,16 @@
 
     def initialize_options(self):
         self.charset = 'utf-8'
-        self.width = 76
-        self.no_wrap = False
         self.keywords = self._keywords = DEFAULT_KEYWORDS.copy()
         self.no_default_keywords = False
+        self.mapping_file = None
         self.no_location = False
         self.omit_header = False
         self.output_file = None
-        self.input_dirs = None
+        self.width = 76
+        self.no_wrap = False
 
     def finalize_options(self):
-        if not self.input_dirs:
-            self.input_dirs = dict.fromkeys([k.split('.',1)[0]
-                for k in self.distribution.packages
-            ]).keys()
-
         if self.no_default_keywords and not self.keywords:
             raise DistutilsOptionError('you must specify new keywords if you '
                                        'disable the default ones')
@@ -106,15 +105,33 @@
             self.width = int(self.width)
 
     def run(self):
+        if self.mapping_file:
+            fileobj = open(self.mapping_file, 'U')
+            try:
+                method_map, options_map = parse_mapping(fileobj)
+            finally:
+                fileobj.close()
+        else:
+            method_map = DEFAULT_MAPPING
+            options_map = {}
+
         outfile = open(self.output_file, 'w')
         try:
+            def callback(filename, options):
+                optstr = ''
+                if options:
+                    optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for k, v
+                                                  in options.items()])
+                log.info('extracting messages from %s%s' % (filename, optstr))
+
             messages = []
-            for dirname in self.input_dirs:
-                log.info('extracting messages from %r' % dirname)
-                extracted = extract_from_dir(dirname, keywords=self.keywords)
-                for filename, lineno, funcname, message in extracted:
-                    messages.append((os.path.join(dirname, filename), lineno,
-                                     funcname, message, None))
+            extracted = extract_from_dir(method_map=method_map,
+                                         options_map=options_map,
+                                         keywords=self.keywords,
+                                         callback=callback)
+            for filename, lineno, funcname, message in extracted:
+                filepath = os.path.normpath(filename)
+                messages.append((filepath, lineno, funcname, message, None))
 
             log.info('writing PO file to %s' % self.output_file)
             write_po(outfile, messages, project=self.distribution.get_name(),
@@ -143,8 +160,9 @@
                            'line.')
     parser.add_option('--no-default-keywords', dest='no_default_keywords',
                       action='store_true', default=False,
-                      help="do not include the default keywords defined by "
-                           "Babel")
+                      help="do not include the default keywords")
+    parser.add_option('--mapping', '-F', dest='mapping_file',
+                      help='path to the extraction mapping file')
     parser.add_option('--no-location', dest='no_location', default=False,
                       action='store_true',
                       help='do not include location comments with filename and '
@@ -177,7 +195,17 @@
         keywords = {}
     if options.keywords:
         keywords.update(parse_keywords(options.keywords))
-        
+
+    if options.mapping_file:
+        fileobj = open(options.mapping_file, 'U')
+        try:
+            method_map, options_map = parse_mapping(fileobj)
+        finally:
+            fileobj.close()
+    else:
+        method_map = DEFAULT_MAPPING
+        options_map = {}
+
     if options.width and options.no_wrap:
         parser.error("'--no-wrap' and '--width' are mutually exclusive.")
     elif not options.width and not options.no_wrap:
@@ -190,10 +218,11 @@
         for dirname in args:
             if not os.path.isdir(dirname):
                 parser.error('%r is not a directory' % dirname)
-            extracted = extract_from_dir(dirname, keywords=keywords)
+            extracted = extract_from_dir(dirname, method_map, options_map,
+                                         keywords)
             for filename, lineno, funcname, message in extracted:
-                messages.append((os.path.join(dirname, filename), lineno,
-                                 funcname, message, None))
+                filepath = os.path.normpath(os.path.join(dirname, filename))
+                messages.append((filepath, lineno, funcname, message, None))
         write_po(outfile, messages, width=options.width,
                  charset=options.charset, no_location=options.no_location,
                  omit_header=options.omit_header)
@@ -201,6 +230,65 @@
         if options.output:
             outfile.close()
 
+def parse_mapping(fileobj):
+    """Parse an extraction method mapping from a file-like object.
+    
+    >>> from StringIO import StringIO
+    >>> buf = StringIO('''
+    ... # Python source files
+    ... python: foobar/**.py
+    ... 
+    ... # Genshi templates
+    ... genshi: foobar/**/templates/**.html
+    ...     include_attrs = 
+    ... genshi: foobar/**/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['foobar/**/templates/**.html']
+    'genshi'
+    >>> options_map['foobar/**/templates/**.html']['include_attrs']
+    ''
+    >>> method_map['foobar/**/templates/**.txt']
+    'genshi'
+    >>> options_map['foobar/**/templates/**.txt']['template_class']
+    'genshi.template.text.TextTemplate'
+    >>> options_map['foobar/**/templates/**.txt']['encoding']
+    'latin-1'
+    
+    :param fileobj: a readable file-like object containing the configuration
+                    text to parse
+    :return: a `(method_map, options_map)` tuple
+    :rtype: `tuple`
+    :see: `extract_from_directory`
+    """
+    method_map = {}
+    options_map = {}
+
+    method = None
+    for line in fileobj.readlines():
+        if line.startswith('#'): # comment
+            continue
+        match = re.match('(\w+): (.+)', line)
+        if match:
+            method, pattern = match.group(1, 2)
+            method_map[pattern] = method
+            options_map[pattern] = {}
+        elif method:
+            match = re.match('\s+(\w+)\s*=\s*(.*)', line)
+            if match:
+                option, value = match.group(1, 2)
+                options_map[pattern][option] = value.strip()
+
+    return (method_map, options_map)
+
 def parse_keywords(strings=[]):
     """Parse keywords specifications from the given list of strings.
     
--- a/babel/util.py
+++ b/babel/util.py
@@ -39,6 +39,7 @@
     True
     >>> pathmatch('**.py', 'templates/index.html')
     False
+    
     >>> pathmatch('**/templates/*.html', 'templates/index.html')
     True
     >>> pathmatch('**/templates/*.html', 'templates/foo/bar.html')
Copyright (C) 2012-2017 Edgewall Software