comparison babel/catalog/frontend.py @ 47:f8469ab4b257 trunk

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 b09e90803d1b
children 22b90b3b161a
comparison
equal deleted inserted replaced
46:29b88754e13a 47:f8469ab4b257
16 from distutils import log 16 from distutils import log
17 from distutils.cmd import Command 17 from distutils.cmd import Command
18 from distutils.errors import DistutilsOptionError 18 from distutils.errors import DistutilsOptionError
19 from optparse import OptionParser 19 from optparse import OptionParser
20 import os 20 import os
21 import re
21 import sys 22 import sys
22 23
23 from babel import __version__ as VERSION 24 from babel import __version__ as VERSION
24 from babel.catalog.extract import extract_from_dir, DEFAULT_KEYWORDS 25 from babel.catalog.extract import extract_from_dir, DEFAULT_KEYWORDS, \
26 DEFAULT_MAPPING
25 from babel.catalog.pofile import write_po 27 from babel.catalog.pofile import write_po
26 28
27 __all__ = ['extract_messages', 'main'] 29 __all__ = ['extract_messages', 'main']
28 __docformat__ = 'restructuredtext en' 30 __docformat__ = 'restructuredtext en'
29 31
53 ('keywords=', 'k', 55 ('keywords=', 'k',
54 'space-separated list of keywords to look for in addition to the ' 56 'space-separated list of keywords to look for in addition to the '
55 'defaults'), 57 'defaults'),
56 ('no-default-keywords', None, 58 ('no-default-keywords', None,
57 'do not include the default keywords'), 59 'do not include the default keywords'),
60 ('mapping-file=', 'F',
61 'path to the mapping configuration file'),
58 ('no-location', None, 62 ('no-location', None,
59 'do not include location comments with filename and line number'), 63 'do not include location comments with filename and line number'),
60 ('omit-header', None, 64 ('omit-header', None,
61 'do not include msgid "" entry in header'), 65 'do not include msgid "" entry in header'),
62 ('output-file=', 'o', 66 ('output-file=', 'o',
71 'no-default-keywords', 'no-location', 'omit-header', 'no-wrap' 75 'no-default-keywords', 'no-location', 'omit-header', 'no-wrap'
72 ] 76 ]
73 77
74 def initialize_options(self): 78 def initialize_options(self):
75 self.charset = 'utf-8' 79 self.charset = 'utf-8'
76 self.width = 76
77 self.no_wrap = False
78 self.keywords = self._keywords = DEFAULT_KEYWORDS.copy() 80 self.keywords = self._keywords = DEFAULT_KEYWORDS.copy()
79 self.no_default_keywords = False 81 self.no_default_keywords = False
82 self.mapping_file = None
80 self.no_location = False 83 self.no_location = False
81 self.omit_header = False 84 self.omit_header = False
82 self.output_file = None 85 self.output_file = None
83 self.input_dirs = None 86 self.width = 76
87 self.no_wrap = False
84 88
85 def finalize_options(self): 89 def finalize_options(self):
86 if not self.input_dirs:
87 self.input_dirs = dict.fromkeys([k.split('.',1)[0]
88 for k in self.distribution.packages
89 ]).keys()
90
91 if self.no_default_keywords and not self.keywords: 90 if self.no_default_keywords and not self.keywords:
92 raise DistutilsOptionError('you must specify new keywords if you ' 91 raise DistutilsOptionError('you must specify new keywords if you '
93 'disable the default ones') 92 'disable the default ones')
94 if self.no_default_keywords: 93 if self.no_default_keywords:
95 self._keywords = {} 94 self._keywords = {}
104 self.width = None 103 self.width = None
105 else: 104 else:
106 self.width = int(self.width) 105 self.width = int(self.width)
107 106
108 def run(self): 107 def run(self):
108 if self.mapping_file:
109 fileobj = open(self.mapping_file, 'U')
110 try:
111 method_map, options_map = parse_mapping(fileobj)
112 finally:
113 fileobj.close()
114 else:
115 method_map = DEFAULT_MAPPING
116 options_map = {}
117
109 outfile = open(self.output_file, 'w') 118 outfile = open(self.output_file, 'w')
110 try: 119 try:
120 def callback(filename, options):
121 optstr = ''
122 if options:
123 optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for k, v
124 in options.items()])
125 log.info('extracting messages from %s%s' % (filename, optstr))
126
111 messages = [] 127 messages = []
112 for dirname in self.input_dirs: 128 extracted = extract_from_dir(method_map=method_map,
113 log.info('extracting messages from %r' % dirname) 129 options_map=options_map,
114 extracted = extract_from_dir(dirname, keywords=self.keywords) 130 keywords=self.keywords,
115 for filename, lineno, funcname, message in extracted: 131 callback=callback)
116 messages.append((os.path.join(dirname, filename), lineno, 132 for filename, lineno, funcname, message in extracted:
117 funcname, message, None)) 133 filepath = os.path.normpath(filename)
134 messages.append((filepath, lineno, funcname, message, None))
118 135
119 log.info('writing PO file to %s' % self.output_file) 136 log.info('writing PO file to %s' % self.output_file)
120 write_po(outfile, messages, project=self.distribution.get_name(), 137 write_po(outfile, messages, project=self.distribution.get_name(),
121 version=self.distribution.get_version(), width=self.width, 138 version=self.distribution.get_version(), width=self.width,
122 charset=self.charset, no_location=self.no_location, 139 charset=self.charset, no_location=self.no_location,
141 help='keywords to look for in addition to the defaults. ' 158 help='keywords to look for in addition to the defaults. '
142 'You can specify multiple -k flags on the command ' 159 'You can specify multiple -k flags on the command '
143 'line.') 160 'line.')
144 parser.add_option('--no-default-keywords', dest='no_default_keywords', 161 parser.add_option('--no-default-keywords', dest='no_default_keywords',
145 action='store_true', default=False, 162 action='store_true', default=False,
146 help="do not include the default keywords defined by " 163 help="do not include the default keywords")
147 "Babel") 164 parser.add_option('--mapping', '-F', dest='mapping_file',
165 help='path to the extraction mapping file')
148 parser.add_option('--no-location', dest='no_location', default=False, 166 parser.add_option('--no-location', dest='no_location', default=False,
149 action='store_true', 167 action='store_true',
150 help='do not include location comments with filename and ' 168 help='do not include location comments with filename and '
151 'line number') 169 'line number')
152 parser.add_option('--omit-header', dest='omit_header', default=False, 170 parser.add_option('--omit-header', dest='omit_header', default=False,
175 parser.error('you must specify new keywords if you disable the ' 193 parser.error('you must specify new keywords if you disable the '
176 'default ones') 194 'default ones')
177 keywords = {} 195 keywords = {}
178 if options.keywords: 196 if options.keywords:
179 keywords.update(parse_keywords(options.keywords)) 197 keywords.update(parse_keywords(options.keywords))
180 198
199 if options.mapping_file:
200 fileobj = open(options.mapping_file, 'U')
201 try:
202 method_map, options_map = parse_mapping(fileobj)
203 finally:
204 fileobj.close()
205 else:
206 method_map = DEFAULT_MAPPING
207 options_map = {}
208
181 if options.width and options.no_wrap: 209 if options.width and options.no_wrap:
182 parser.error("'--no-wrap' and '--width' are mutually exclusive.") 210 parser.error("'--no-wrap' and '--width' are mutually exclusive.")
183 elif not options.width and not options.no_wrap: 211 elif not options.width and not options.no_wrap:
184 options.width = 76 212 options.width = 76
185 elif not options.width and options.no_wrap: 213 elif not options.width and options.no_wrap:
188 try: 216 try:
189 messages = [] 217 messages = []
190 for dirname in args: 218 for dirname in args:
191 if not os.path.isdir(dirname): 219 if not os.path.isdir(dirname):
192 parser.error('%r is not a directory' % dirname) 220 parser.error('%r is not a directory' % dirname)
193 extracted = extract_from_dir(dirname, keywords=keywords) 221 extracted = extract_from_dir(dirname, method_map, options_map,
222 keywords)
194 for filename, lineno, funcname, message in extracted: 223 for filename, lineno, funcname, message in extracted:
195 messages.append((os.path.join(dirname, filename), lineno, 224 filepath = os.path.normpath(os.path.join(dirname, filename))
196 funcname, message, None)) 225 messages.append((filepath, lineno, funcname, message, None))
197 write_po(outfile, messages, width=options.width, 226 write_po(outfile, messages, width=options.width,
198 charset=options.charset, no_location=options.no_location, 227 charset=options.charset, no_location=options.no_location,
199 omit_header=options.omit_header) 228 omit_header=options.omit_header)
200 finally: 229 finally:
201 if options.output: 230 if options.output:
202 outfile.close() 231 outfile.close()
232
233 def parse_mapping(fileobj):
234 """Parse an extraction method mapping from a file-like object.
235
236 >>> from StringIO import StringIO
237 >>> buf = StringIO('''
238 ... # Python source files
239 ... python: foobar/**.py
240 ...
241 ... # Genshi templates
242 ... genshi: foobar/**/templates/**.html
243 ... include_attrs =
244 ... genshi: foobar/**/templates/**.txt
245 ... template_class = genshi.template.text.TextTemplate
246 ... encoding = latin-1
247 ... ''')
248
249 >>> method_map, options_map = parse_mapping(buf)
250
251 >>> method_map['foobar/**.py']
252 'python'
253 >>> options_map['foobar/**.py']
254 {}
255 >>> method_map['foobar/**/templates/**.html']
256 'genshi'
257 >>> options_map['foobar/**/templates/**.html']['include_attrs']
258 ''
259 >>> method_map['foobar/**/templates/**.txt']
260 'genshi'
261 >>> options_map['foobar/**/templates/**.txt']['template_class']
262 'genshi.template.text.TextTemplate'
263 >>> options_map['foobar/**/templates/**.txt']['encoding']
264 'latin-1'
265
266 :param fileobj: a readable file-like object containing the configuration
267 text to parse
268 :return: a `(method_map, options_map)` tuple
269 :rtype: `tuple`
270 :see: `extract_from_directory`
271 """
272 method_map = {}
273 options_map = {}
274
275 method = None
276 for line in fileobj.readlines():
277 if line.startswith('#'): # comment
278 continue
279 match = re.match('(\w+): (.+)', line)
280 if match:
281 method, pattern = match.group(1, 2)
282 method_map[pattern] = method
283 options_map[pattern] = {}
284 elif method:
285 match = re.match('\s+(\w+)\s*=\s*(.*)', line)
286 if match:
287 option, value = match.group(1, 2)
288 options_map[pattern][option] = value.strip()
289
290 return (method_map, options_map)
203 291
204 def parse_keywords(strings=[]): 292 def parse_keywords(strings=[]):
205 """Parse keywords specifications from the given list of strings. 293 """Parse keywords specifications from the given list of strings.
206 294
207 >>> kw = parse_keywords(['_', 'dgettext:2', 'dngettext:2,3']) 295 >>> kw = parse_keywords(['_', 'dgettext:2', 'dngettext:2,3'])
Copyright (C) 2012-2017 Edgewall Software