diff options
Diffstat (limited to 'babel/messages/frontend.py')
-rw-r--r-- | babel/messages/frontend.py | 1194 |
1 files changed, 0 insertions, 1194 deletions
diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py deleted file mode 100644 index c9b5a57..0000000 --- a/babel/messages/frontend.py +++ /dev/null @@ -1,1194 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://babel.edgewall.org/wiki/License. -# -# This software consists of voluntary contributions made by many -# individuals. For the exact contribution history, see the revision -# history and logs, available at http://babel.edgewall.org/log/. - -"""Frontends for the message extraction functionality.""" - -from ConfigParser import RawConfigParser -from datetime import datetime -from distutils import log -from distutils.cmd import Command -from distutils.errors import DistutilsOptionError, DistutilsSetupError -from locale import getpreferredencoding -import logging -from optparse import OptionParser -import os -import re -import shutil -from StringIO import StringIO -import sys -import tempfile - -from babel import __version__ as VERSION -from babel import Locale, localedata -from babel.core import UnknownLocaleError -from babel.messages.catalog import Catalog -from babel.messages.extract import extract_from_dir, DEFAULT_KEYWORDS, \ - DEFAULT_MAPPING -from babel.messages.mofile import write_mo -from babel.messages.pofile import read_po, write_po -from babel.messages.plurals import PLURALS -from babel.util import odict, LOCALTZ - -__all__ = ['CommandLineInterface', 'compile_catalog', 'extract_messages', - 'init_catalog', 'check_message_extractors', 'update_catalog'] -__docformat__ = 'restructuredtext en' - - -class compile_catalog(Command): - """Catalog compilation command for use in ``setup.py`` scripts. - - If correctly installed, this command is available to Setuptools-using - setup scripts automatically. For projects using plain old ``distutils``, - the command needs to be registered explicitly in ``setup.py``:: - - from babel.messages.frontend import compile_catalog - - setup( - ... - cmdclass = {'compile_catalog': compile_catalog} - ) - - :since: version 0.9 - :see: `Integrating new distutils commands <http://docs.python.org/dist/node32.html>`_ - :see: `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ - """ - - description = 'compile message catalogs to binary MO files' - user_options = [ - ('domain=', 'D', - "domain of PO file (default 'messages')"), - ('directory=', 'd', - 'path to base directory containing the catalogs'), - ('input-file=', 'i', - 'name of the input file'), - ('output-file=', 'o', - "name of the output file (default " - "'<output_dir>/<locale>/LC_MESSAGES/<domain>.po')"), - ('locale=', 'l', - 'locale of the catalog to compile'), - ('use-fuzzy', 'f', - 'also include fuzzy translations'), - ('statistics', None, - 'print statistics about translations') - ] - boolean_options = ['use-fuzzy', 'statistics'] - - def initialize_options(self): - self.domain = 'messages' - self.directory = None - self.input_file = None - self.output_file = None - self.locale = None - self.use_fuzzy = False - self.statistics = False - - def finalize_options(self): - if not self.input_file and not self.directory: - raise DistutilsOptionError('you must specify either the input file ' - 'or the base directory') - if not self.output_file and not self.directory: - raise DistutilsOptionError('you must specify either the input file ' - 'or the base directory') - - def run(self): - po_files = [] - mo_files = [] - - if not self.input_file: - if self.locale: - po_files.append((self.locale, - os.path.join(self.directory, self.locale, - 'LC_MESSAGES', - self.domain + '.po'))) - mo_files.append(os.path.join(self.directory, self.locale, - 'LC_MESSAGES', - self.domain + '.mo')) - else: - for locale in os.listdir(self.directory): - po_file = os.path.join(self.directory, locale, - 'LC_MESSAGES', self.domain + '.po') - if os.path.exists(po_file): - po_files.append((locale, po_file)) - mo_files.append(os.path.join(self.directory, locale, - 'LC_MESSAGES', - self.domain + '.mo')) - else: - po_files.append((self.locale, self.input_file)) - if self.output_file: - mo_files.append(self.output_file) - else: - mo_files.append(os.path.join(self.directory, self.locale, - 'LC_MESSAGES', - self.domain + '.mo')) - - if not po_files: - raise DistutilsOptionError('no message catalogs found') - - for idx, (locale, po_file) in enumerate(po_files): - mo_file = mo_files[idx] - infile = open(po_file, 'r') - try: - catalog = read_po(infile, locale) - finally: - infile.close() - - if self.statistics: - translated = 0 - for message in list(catalog)[1:]: - if message.string: - translated +=1 - percentage = 0 - if len(catalog): - percentage = translated * 100 // len(catalog) - log.info('%d of %d messages (%d%%) translated in %r', - translated, len(catalog), percentage, po_file) - - if catalog.fuzzy and not self.use_fuzzy: - log.warn('catalog %r is marked as fuzzy, skipping', po_file) - continue - - for message, errors in catalog.check(): - for error in errors: - log.error('error: %s:%d: %s', po_file, message.lineno, - error) - - log.info('compiling catalog %r to %r', po_file, mo_file) - - outfile = open(mo_file, 'wb') - try: - write_mo(outfile, catalog, use_fuzzy=self.use_fuzzy) - finally: - outfile.close() - - -class extract_messages(Command): - """Message extraction command for use in ``setup.py`` scripts. - - If correctly installed, this command is available to Setuptools-using - setup scripts automatically. For projects using plain old ``distutils``, - the command needs to be registered explicitly in ``setup.py``:: - - from babel.messages.frontend import extract_messages - - setup( - ... - cmdclass = {'extract_messages': extract_messages} - ) - - :see: `Integrating new distutils commands <http://docs.python.org/dist/node32.html>`_ - :see: `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ - """ - - description = 'extract localizable strings from the project code' - user_options = [ - ('charset=', None, - 'charset to use in the output file'), - ('keywords=', 'k', - 'space-separated list of keywords to look for in addition to the ' - '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, - 'do not include msgid "" entry in header'), - ('output-file=', 'o', - 'name of the output file'), - ('width=', 'w', - 'set output line width (default 76)'), - ('no-wrap', None, - 'do not break long message lines, longer than the output line width, ' - 'into several lines'), - ('sort-output', None, - 'generate sorted output (default False)'), - ('sort-by-file', None, - 'sort output by file location (default False)'), - ('msgid-bugs-address=', None, - 'set report address for msgid'), - ('copyright-holder=', None, - 'set copyright holder in output'), - ('add-comments=', 'c', - 'place comment block with TAG (or those preceding keyword lines) in ' - 'output file. Seperate multiple TAGs with commas(,)'), - ('strip-comments', None, - 'strip the comment TAGs from the comments.'), - ('input-dirs=', None, - 'directories that should be scanned for messages'), - ] - boolean_options = [ - 'no-default-keywords', 'no-location', 'omit-header', 'no-wrap', - 'sort-output', 'sort-by-file', 'strip-comments' - ] - - def initialize_options(self): - self.charset = 'utf-8' - 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 - self.sort_output = False - self.sort_by_file = False - self.msgid_bugs_address = None - self.copyright_holder = None - self.add_comments = None - self._add_comments = [] - self.strip_comments = False - - def finalize_options(self): - if self.no_default_keywords and not self.keywords: - raise DistutilsOptionError('you must specify new keywords if you ' - 'disable the default ones') - if self.no_default_keywords: - self._keywords = {} - if self.keywords: - self._keywords.update(parse_keywords(self.keywords.split())) - - if not self.output_file: - raise DistutilsOptionError('no output file specified') - if self.no_wrap and self.width: - raise DistutilsOptionError("'--no-wrap' and '--width' are mutually " - "exclusive") - if self.no_wrap: - self.width = None - else: - self.width = int(self.width) - - if self.sort_output and self.sort_by_file: - raise DistutilsOptionError("'--sort-output' and '--sort-by-file' " - "are mutually exclusive") - - if not self.input_dirs: - self.input_dirs = dict.fromkeys([k.split('.',1)[0] - for k in self.distribution.packages - ]).keys() - - if self.add_comments: - self._add_comments = self.add_comments.split(',') - - def run(self): - mappings = self._get_mappings() - outfile = open(self.output_file, 'w') - try: - catalog = Catalog(project=self.distribution.get_name(), - version=self.distribution.get_version(), - msgid_bugs_address=self.msgid_bugs_address, - copyright_holder=self.copyright_holder, - charset=self.charset) - - for dirname, (method_map, options_map) in mappings.items(): - def callback(filename, method, options): - if method == 'ignore': - return - filepath = os.path.normpath(os.path.join(dirname, filename)) - optstr = '' - if options: - optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for - k, v in options.items()]) - log.info('extracting messages from %s%s', filepath, optstr) - - extracted = extract_from_dir(dirname, method_map, options_map, - keywords=self._keywords, - comment_tags=self._add_comments, - callback=callback, - strip_comment_tags= - self.strip_comments) - for filename, lineno, message, comments in extracted: - filepath = os.path.normpath(os.path.join(dirname, filename)) - catalog.add(message, None, [(filepath, lineno)], - auto_comments=comments) - - log.info('writing PO template file to %s' % self.output_file) - write_po(outfile, catalog, width=self.width, - no_location=self.no_location, - omit_header=self.omit_header, - sort_output=self.sort_output, - sort_by_file=self.sort_by_file) - 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 getattr(self.distribution, 'message_extractors', None): - 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()``. - - :param dist: the distutils/setuptools ``Distribution`` object - :param name: the name of the keyword argument (should always be - "message_extractors") - :param value: the value of the keyword argument - :raise `DistutilsSetupError`: if the value is not valid - :see: `Adding setup() arguments - <http://peak.telecommunity.com/DevCenter/setuptools#adding-setup-arguments>`_ - """ - assert name == 'message_extractors' - if not isinstance(value, dict): - raise DistutilsSetupError('the value of the "message_extractors" ' - 'parameter must be a dictionary') - - -class init_catalog(Command): - """New catalog initialization command for use in ``setup.py`` scripts. - - If correctly installed, this command is available to Setuptools-using - setup scripts automatically. For projects using plain old ``distutils``, - the command needs to be registered explicitly in ``setup.py``:: - - from babel.messages.frontend import init_catalog - - setup( - ... - cmdclass = {'init_catalog': init_catalog} - ) - - :see: `Integrating new distutils commands <http://docs.python.org/dist/node32.html>`_ - :see: `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ - """ - - description = 'create a new catalog based on a POT file' - user_options = [ - ('domain=', 'D', - "domain of PO file (default 'messages')"), - ('input-file=', 'i', - 'name of the input file'), - ('output-dir=', 'd', - 'path to output directory'), - ('output-file=', 'o', - "name of the output file (default " - "'<output_dir>/<locale>/LC_MESSAGES/<domain>.po')"), - ('locale=', 'l', - 'locale for the new localized catalog'), - ] - - def initialize_options(self): - self.output_dir = None - self.output_file = None - self.input_file = None - self.locale = None - self.domain = 'messages' - - def finalize_options(self): - if not self.input_file: - raise DistutilsOptionError('you must specify the input file') - - if not self.locale: - raise DistutilsOptionError('you must provide a locale for the ' - 'new catalog') - try: - self._locale = Locale.parse(self.locale) - except UnknownLocaleError, e: - raise DistutilsOptionError(e) - - if not self.output_file and not self.output_dir: - raise DistutilsOptionError('you must specify the output directory') - if not self.output_file: - self.output_file = os.path.join(self.output_dir, self.locale, - 'LC_MESSAGES', self.domain + '.po') - - if not os.path.exists(os.path.dirname(self.output_file)): - os.makedirs(os.path.dirname(self.output_file)) - - def run(self): - log.info('creating catalog %r based on %r', self.output_file, - self.input_file) - - infile = open(self.input_file, 'r') - try: - # Although reading from the catalog template, read_po must be fed - # the locale in order to correcly calculate plurals - catalog = read_po(infile, locale=self.locale) - finally: - infile.close() - - catalog.locale = self._locale - catalog.fuzzy = False - - outfile = open(self.output_file, 'w') - try: - write_po(outfile, catalog) - finally: - outfile.close() - - -class update_catalog(Command): - """Catalog merging command for use in ``setup.py`` scripts. - - If correctly installed, this command is available to Setuptools-using - setup scripts automatically. For projects using plain old ``distutils``, - the command needs to be registered explicitly in ``setup.py``:: - - from babel.messages.frontend import update_catalog - - setup( - ... - cmdclass = {'update_catalog': update_catalog} - ) - - :since: version 0.9 - :see: `Integrating new distutils commands <http://docs.python.org/dist/node32.html>`_ - :see: `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ - """ - - description = 'update message catalogs from a POT file' - user_options = [ - ('domain=', 'D', - "domain of PO file (default 'messages')"), - ('input-file=', 'i', - 'name of the input file'), - ('output-dir=', 'd', - 'path to base directory containing the catalogs'), - ('output-file=', 'o', - "name of the output file (default " - "'<output_dir>/<locale>/LC_MESSAGES/<domain>.po')"), - ('locale=', 'l', - 'locale of the catalog to compile'), - ('ignore-obsolete=', None, - 'whether to omit obsolete messages from the output'), - ('no-fuzzy-matching', 'N', - 'do not use fuzzy matching'), - ('previous', None, - 'keep previous msgids of translated messages') - ] - boolean_options = ['ignore_obsolete', 'no_fuzzy_matching', 'previous'] - - def initialize_options(self): - self.domain = 'messages' - self.input_file = None - self.output_dir = None - self.output_file = None - self.locale = None - self.ignore_obsolete = False - self.no_fuzzy_matching = False - self.previous = False - - def finalize_options(self): - if not self.input_file: - raise DistutilsOptionError('you must specify the input file') - if not self.output_file and not self.output_dir: - raise DistutilsOptionError('you must specify the output file or ' - 'directory') - if self.output_file and not self.locale: - raise DistutilsOptionError('you must specify the locale') - if self.no_fuzzy_matching and self.previous: - self.previous = False - - def run(self): - po_files = [] - if not self.output_file: - if self.locale: - po_files.append((self.locale, - os.path.join(self.output_dir, self.locale, - 'LC_MESSAGES', - self.domain + '.po'))) - else: - for locale in os.listdir(self.output_dir): - po_file = os.path.join(self.output_dir, locale, - 'LC_MESSAGES', - self.domain + '.po') - if os.path.exists(po_file): - po_files.append((locale, po_file)) - else: - po_files.append((self.locale, self.output_file)) - - domain = self.domain - if not domain: - domain = os.path.splitext(os.path.basename(self.input_file))[0] - - infile = open(self.input_file, 'U') - try: - template = read_po(infile) - finally: - infile.close() - - if not po_files: - raise DistutilsOptionError('no message catalogs found') - - for locale, filename in po_files: - log.info('updating catalog %r based on %r', filename, - self.input_file) - infile = open(filename, 'U') - try: - catalog = read_po(infile, locale=locale, domain=domain) - finally: - infile.close() - - catalog.update(template, self.no_fuzzy_matching) - - tmpname = os.path.join(os.path.dirname(filename), - tempfile.gettempprefix() + - os.path.basename(filename)) - tmpfile = open(tmpname, 'w') - try: - try: - write_po(tmpfile, catalog, - ignore_obsolete=self.ignore_obsolete, - include_previous=self.previous) - finally: - tmpfile.close() - except: - os.remove(tmpname) - raise - - try: - os.rename(tmpname, filename) - except OSError: - # We're probably on Windows, which doesn't support atomic - # renames, at least not through Python - # If the error is in fact due to a permissions problem, that - # same error is going to be raised from one of the following - # operations - os.remove(filename) - shutil.copy(tmpname, filename) - os.remove(tmpname) - - -class CommandLineInterface(object): - """Command-line interface. - - This class provides a simple command-line interface to the message - extraction and PO file generation functionality. - """ - - usage = '%%prog %s [options] %s' - version = '%%prog %s' % VERSION - commands = { - 'compile': 'compile message catalogs to MO files', - 'extract': 'extract messages from source files and generate a POT file', - 'init': 'create new message catalogs from a POT file', - 'update': 'update existing message catalogs from a POT file' - } - - def run(self, argv=sys.argv): - """Main entry point of the command-line interface. - - :param argv: list of arguments passed on the command-line - """ - self.parser = OptionParser(usage=self.usage % ('command', '[args]'), - version=self.version) - self.parser.disable_interspersed_args() - self.parser.print_help = self._help - self.parser.add_option('--list-locales', dest='list_locales', - action='store_true', - help="print all known locales and exit") - self.parser.add_option('-v', '--verbose', action='store_const', - dest='loglevel', const=logging.DEBUG, - help='print as much as possible') - self.parser.add_option('-q', '--quiet', action='store_const', - dest='loglevel', const=logging.ERROR, - help='print as little as possible') - self.parser.set_defaults(list_locales=False, loglevel=logging.INFO) - - options, args = self.parser.parse_args(argv[1:]) - - # Configure logging - self.log = logging.getLogger('babel') - self.log.setLevel(options.loglevel) - handler = logging.StreamHandler() - handler.setLevel(options.loglevel) - formatter = logging.Formatter('%(message)s') - handler.setFormatter(formatter) - self.log.addHandler(handler) - - if options.list_locales: - identifiers = localedata.list() - longest = max([len(identifier) for identifier in identifiers]) - format = u'%%-%ds %%s' % (longest + 1) - for identifier in localedata.list(): - locale = Locale.parse(identifier) - output = format % (identifier, locale.english_name) - print output.encode(sys.stdout.encoding or - getpreferredencoding() or - 'ascii', 'replace') - return 0 - - if not args: - self.parser.error('incorrect number of arguments') - - cmdname = args[0] - if cmdname not in self.commands: - self.parser.error('unknown command "%s"' % cmdname) - - return getattr(self, cmdname)(args[1:]) - - def _help(self): - print self.parser.format_help() - print "commands:" - longest = max([len(command) for command in self.commands]) - format = " %%-%ds %%s" % max(8, longest + 1) - commands = self.commands.items() - commands.sort() - for name, description in commands: - print format % (name, description) - - def compile(self, argv): - """Subcommand for compiling a message catalog to a MO file. - - :param argv: the command arguments - :since: version 0.9 - """ - parser = OptionParser(usage=self.usage % ('compile', ''), - description=self.commands['compile']) - parser.add_option('--domain', '-D', dest='domain', - help="domain of MO and PO files (default '%default')") - parser.add_option('--directory', '-d', dest='directory', - metavar='DIR', help='base directory of catalog files') - parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE', - help='locale of the catalog') - parser.add_option('--input-file', '-i', dest='input_file', - metavar='FILE', help='name of the input file') - parser.add_option('--output-file', '-o', dest='output_file', - metavar='FILE', - help="name of the output file (default " - "'<output_dir>/<locale>/LC_MESSAGES/" - "<domain>.mo')") - parser.add_option('--use-fuzzy', '-f', dest='use_fuzzy', - action='store_true', - help='also include fuzzy translations (default ' - '%default)') - parser.add_option('--statistics', dest='statistics', - action='store_true', - help='print statistics about translations') - - parser.set_defaults(domain='messages', use_fuzzy=False, - compile_all=False, statistics=False) - options, args = parser.parse_args(argv) - - po_files = [] - mo_files = [] - if not options.input_file: - if not options.directory: - parser.error('you must specify either the input file or the ' - 'base directory') - if options.locale: - po_files.append((options.locale, - os.path.join(options.directory, - options.locale, 'LC_MESSAGES', - options.domain + '.po'))) - mo_files.append(os.path.join(options.directory, options.locale, - 'LC_MESSAGES', - options.domain + '.mo')) - else: - for locale in os.listdir(options.directory): - po_file = os.path.join(options.directory, locale, - 'LC_MESSAGES', options.domain + '.po') - if os.path.exists(po_file): - po_files.append((locale, po_file)) - mo_files.append(os.path.join(options.directory, locale, - 'LC_MESSAGES', - options.domain + '.mo')) - else: - po_files.append((options.locale, options.input_file)) - if options.output_file: - mo_files.append(options.output_file) - else: - if not options.directory: - parser.error('you must specify either the input file or ' - 'the base directory') - mo_files.append(os.path.join(options.directory, options.locale, - 'LC_MESSAGES', - options.domain + '.mo')) - if not po_files: - parser.error('no message catalogs found') - - for idx, (locale, po_file) in enumerate(po_files): - mo_file = mo_files[idx] - infile = open(po_file, 'r') - try: - catalog = read_po(infile, locale) - finally: - infile.close() - - if options.statistics: - translated = 0 - for message in list(catalog)[1:]: - if message.string: - translated +=1 - percentage = 0 - if len(catalog): - percentage = translated * 100 // len(catalog) - self.log.info("%d of %d messages (%d%%) translated in %r", - translated, len(catalog), percentage, po_file) - - if catalog.fuzzy and not options.use_fuzzy: - self.log.warn('catalog %r is marked as fuzzy, skipping', - po_file) - continue - - for message, errors in catalog.check(): - for error in errors: - self.log.error('error: %s:%d: %s', po_file, message.lineno, - error) - - self.log.info('compiling catalog %r to %r', po_file, mo_file) - - outfile = open(mo_file, 'wb') - try: - write_mo(outfile, catalog, use_fuzzy=options.use_fuzzy) - finally: - outfile.close() - - def extract(self, argv): - """Subcommand for extracting messages from source files and generating - a POT file. - - :param argv: the command arguments - """ - parser = OptionParser(usage=self.usage % ('extract', 'dir1 <dir2> ...'), - description=self.commands['extract']) - parser.add_option('--charset', dest='charset', - help='charset to use in the output (default ' - '"%default")') - parser.add_option('-k', '--keyword', dest='keywords', action='append', - help='keywords to look for in addition to the ' - 'defaults. You can specify multiple -k flags on ' - 'the command line.') - parser.add_option('--no-default-keywords', dest='no_default_keywords', - action='store_true', - 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', - action='store_true', - help='do not include location comments with filename ' - 'and line number') - parser.add_option('--omit-header', dest='omit_header', - action='store_true', - help='do not include msgid "" entry in header') - parser.add_option('-o', '--output', dest='output', - help='path to the output POT file') - parser.add_option('-w', '--width', dest='width', type='int', - help="set output line width (default %default)") - parser.add_option('--no-wrap', dest='no_wrap', action = 'store_true', - help='do not break long message lines, longer than ' - 'the output line width, into several lines') - parser.add_option('--sort-output', dest='sort_output', - action='store_true', - help='generate sorted output (default False)') - parser.add_option('--sort-by-file', dest='sort_by_file', - action='store_true', - help='sort output by file location (default False)') - parser.add_option('--msgid-bugs-address', dest='msgid_bugs_address', - metavar='EMAIL@ADDRESS', - help='set report address for msgid') - parser.add_option('--copyright-holder', dest='copyright_holder', - help='set copyright holder in output') - parser.add_option('--add-comments', '-c', dest='comment_tags', - metavar='TAG', action='append', - help='place comment block with TAG (or those ' - 'preceding keyword lines) in output file. One ' - 'TAG per argument call') - parser.add_option('--strip-comment-tags', '-s', - dest='strip_comment_tags', action='store_true', - help='Strip the comment tags from the comments.') - - parser.set_defaults(charset='utf-8', keywords=[], - no_default_keywords=False, no_location=False, - omit_header = False, width=76, no_wrap=False, - sort_output=False, sort_by_file=False, - comment_tags=[], strip_comment_tags=False) - options, args = parser.parse_args(argv) - if not args: - parser.error('incorrect number of arguments') - - if options.output not in (None, '-'): - outfile = open(options.output, 'w') - else: - outfile = sys.stdout - - keywords = DEFAULT_KEYWORDS.copy() - if options.no_default_keywords: - if not options.keywords: - parser.error('you must specify new keywords if you disable the ' - 'default ones') - 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: - options.width = 76 - elif not options.width and options.no_wrap: - options.width = 0 - - if options.sort_output and options.sort_by_file: - parser.error("'--sort-output' and '--sort-by-file' are mutually " - "exclusive") - - try: - catalog = Catalog(msgid_bugs_address=options.msgid_bugs_address, - copyright_holder=options.copyright_holder, - charset=options.charset) - - for dirname in args: - if not os.path.isdir(dirname): - parser.error('%r is not a directory' % dirname) - - def callback(filename, method, options): - if method == 'ignore': - return - filepath = os.path.normpath(os.path.join(dirname, filename)) - optstr = '' - if options: - optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for - k, v in options.items()]) - self.log.info('extracting messages from %s%s', filepath, - optstr) - - extracted = extract_from_dir(dirname, method_map, options_map, - keywords, options.comment_tags, - callback=callback, - strip_comment_tags= - options.strip_comment_tags) - for filename, lineno, message, comments in extracted: - filepath = os.path.normpath(os.path.join(dirname, filename)) - catalog.add(message, None, [(filepath, lineno)], - auto_comments=comments) - - if options.output not in (None, '-'): - self.log.info('writing PO template file to %s' % options.output) - write_po(outfile, catalog, width=options.width, - no_location=options.no_location, - omit_header=options.omit_header, - sort_output=options.sort_output, - sort_by_file=options.sort_by_file) - finally: - if options.output: - outfile.close() - - def init(self, argv): - """Subcommand for creating new message catalogs from a template. - - :param argv: the command arguments - """ - parser = OptionParser(usage=self.usage % ('init', ''), - description=self.commands['init']) - parser.add_option('--domain', '-D', dest='domain', - help="domain of PO file (default '%default')") - parser.add_option('--input-file', '-i', dest='input_file', - metavar='FILE', help='name of the input file') - parser.add_option('--output-dir', '-d', dest='output_dir', - metavar='DIR', help='path to output directory') - parser.add_option('--output-file', '-o', dest='output_file', - metavar='FILE', - help="name of the output file (default " - "'<output_dir>/<locale>/LC_MESSAGES/" - "<domain>.po')") - parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE', - help='locale for the new localized catalog') - - parser.set_defaults(domain='messages') - options, args = parser.parse_args(argv) - - if not options.locale: - parser.error('you must provide a locale for the new catalog') - try: - locale = Locale.parse(options.locale) - except UnknownLocaleError, e: - parser.error(e) - - if not options.input_file: - parser.error('you must specify the input file') - - if not options.output_file and not options.output_dir: - parser.error('you must specify the output file or directory') - - if not options.output_file: - options.output_file = os.path.join(options.output_dir, - options.locale, 'LC_MESSAGES', - options.domain + '.po') - if not os.path.exists(os.path.dirname(options.output_file)): - os.makedirs(os.path.dirname(options.output_file)) - - infile = open(options.input_file, 'r') - try: - # Although reading from the catalog template, read_po must be fed - # the locale in order to correcly calculate plurals - catalog = read_po(infile, locale=options.locale) - finally: - infile.close() - - catalog.locale = locale - catalog.revision_date = datetime.now(LOCALTZ) - - self.log.info('creating catalog %r based on %r', options.output_file, - options.input_file) - - outfile = open(options.output_file, 'w') - try: - write_po(outfile, catalog) - finally: - outfile.close() - - def update(self, argv): - """Subcommand for updating existing message catalogs from a template. - - :param argv: the command arguments - :since: version 0.9 - """ - parser = OptionParser(usage=self.usage % ('update', ''), - description=self.commands['update']) - parser.add_option('--domain', '-D', dest='domain', - help="domain of PO file (default '%default')") - parser.add_option('--input-file', '-i', dest='input_file', - metavar='FILE', help='name of the input file') - parser.add_option('--output-dir', '-d', dest='output_dir', - metavar='DIR', help='path to output directory') - parser.add_option('--output-file', '-o', dest='output_file', - metavar='FILE', - help="name of the output file (default " - "'<output_dir>/<locale>/LC_MESSAGES/" - "<domain>.po')") - parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE', - help='locale of the translations catalog') - parser.add_option('--ignore-obsolete', dest='ignore_obsolete', - action='store_true', - help='do not include obsolete messages in the output ' - '(default %default)'), - parser.add_option('--no-fuzzy-matching', '-N', dest='no_fuzzy_matching', - action='store_true', - help='do not use fuzzy matching (default %default)'), - parser.add_option('--previous', dest='previous', action='store_true', - help='keep previous msgids of translated messages ' - '(default %default)'), - - parser.set_defaults(domain='messages', ignore_obsolete=False, - no_fuzzy_matching=False, previous=False) - options, args = parser.parse_args(argv) - - if not options.input_file: - parser.error('you must specify the input file') - if not options.output_file and not options.output_dir: - parser.error('you must specify the output file or directory') - if options.output_file and not options.locale: - parser.error('you must specify the loicale') - if options.no_fuzzy_matching and options.previous: - options.previous = False - - po_files = [] - if not options.output_file: - if options.locale: - po_files.append((options.locale, - os.path.join(options.output_dir, - options.locale, 'LC_MESSAGES', - options.domain + '.po'))) - else: - for locale in os.listdir(options.output_dir): - po_file = os.path.join(options.output_dir, locale, - 'LC_MESSAGES', - options.domain + '.po') - if os.path.exists(po_file): - po_files.append((locale, po_file)) - else: - po_files.append((options.locale, options.output_file)) - - domain = options.domain - if not domain: - domain = os.path.splitext(os.path.basename(options.input_file))[0] - - infile = open(options.input_file, 'U') - try: - template = read_po(infile) - finally: - infile.close() - - if not po_files: - parser.error('no message catalogs found') - - for locale, filename in po_files: - self.log.info('updating catalog %r based on %r', filename, - options.input_file) - infile = open(filename, 'U') - try: - catalog = read_po(infile, locale=locale, domain=domain) - finally: - infile.close() - - catalog.update(template, options.no_fuzzy_matching) - - tmpname = os.path.join(os.path.dirname(filename), - tempfile.gettempprefix() + - os.path.basename(filename)) - tmpfile = open(tmpname, 'w') - try: - try: - write_po(tmpfile, catalog, - ignore_obsolete=options.ignore_obsolete, - include_previous=options.previous) - finally: - tmpfile.close() - except: - os.remove(tmpname) - raise - - try: - os.rename(tmpname, filename) - except OSError: - # We're probably on Windows, which doesn't support atomic - # renames, at least not through Python - # If the error is in fact due to a permissions problem, that - # same error is going to be raised from one of the following - # operations - os.remove(filename) - shutil.copy(tmpname, filename) - os.remove(tmpname) - - -def main(): - return CommandLineInterface().run(sys.argv) - -def parse_mapping(fileobj, filename=None): - """Parse an extraction method mapping from a file-like object. - - >>> buf = StringIO(''' - ... [extractors] - ... custom = mypackage.module:myfunc - ... - ... # Python source files - ... [python: **.py] - ... - ... # Genshi templates - ... [genshi: **/templates/**.html] - ... include_attrs = - ... [genshi: **/templates/**.txt] - ... template_class = genshi.template:TextTemplate - ... encoding = latin-1 - ... - ... # Some custom extractor - ... [custom: **/custom/*.*] - ... ''') - - >>> method_map, options_map = parse_mapping(buf) - >>> len(method_map) - 4 - - >>> method_map[0] - ('**.py', 'python') - >>> options_map['**.py'] - {} - >>> method_map[1] - ('**/templates/**.html', 'genshi') - >>> options_map['**/templates/**.html']['include_attrs'] - '' - >>> method_map[2] - ('**/templates/**.txt', 'genshi') - >>> options_map['**/templates/**.txt']['template_class'] - 'genshi.template:TextTemplate' - >>> options_map['**/templates/**.txt']['encoding'] - 'latin-1' - - >>> method_map[3] - ('**/custom/*.*', 'mypackage.module:myfunc') - >>> options_map['**/custom/*.*'] - {} - - :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` - """ - extractors = {} - method_map = [] - options_map = {} - - parser = RawConfigParser() - parser._sections = odict(parser._sections) # We need ordered sections - parser.readfp(fileobj, filename) - for section in parser.sections(): - if section == 'extractors': - extractors = dict(parser.items(section)) - else: - method, pattern = [part.strip() for part in section.split(':', 1)] - method_map.append((pattern, method)) - options_map[pattern] = dict(parser.items(section)) - - if extractors: - for idx, (pattern, method) in enumerate(method_map): - if method in extractors: - method = extractors[method] - method_map[idx] = (pattern, method) - - return (method_map, options_map) - -def parse_keywords(strings=[]): - """Parse keywords specifications from the given list of strings. - - >>> kw = parse_keywords(['_', 'dgettext:2', 'dngettext:2,3']) - >>> for keyword, indices in sorted(kw.items()): - ... print (keyword, indices) - ('_', None) - ('dgettext', (2,)) - ('dngettext', (2, 3)) - """ - keywords = {} - for string in strings: - if ':' in string: - funcname, indices = string.split(':') - else: - funcname, indices = string, None - if funcname not in keywords: - if indices: - indices = tuple([(int(x)) for x in indices.split(',')]) - keywords[funcname] = indices - return keywords - - -if __name__ == '__main__': - main() |