Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/babel/messages/frontend.py
diff options
context:
space:
mode:
Diffstat (limited to 'babel/messages/frontend.py')
-rw-r--r--babel/messages/frontend.py1194
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()