Spaces:
Running
Running
text-generation-webui
/
installer_files
/env
/lib
/python3.11
/site-packages
/babel
/messages
/frontend.py
# -*- coding: utf-8 -*- | |
""" | |
babel.messages.frontend | |
~~~~~~~~~~~~~~~~~~~~~~~ | |
Frontends for the message extraction functionality. | |
:copyright: (c) 2013-2021 by the Babel Team. | |
:license: BSD, see LICENSE for more details. | |
""" | |
from __future__ import print_function | |
import logging | |
import optparse | |
import os | |
import re | |
import shutil | |
import sys | |
import tempfile | |
from collections import OrderedDict | |
from datetime import datetime | |
from locale import getpreferredencoding | |
from babel import __version__ as VERSION | |
from babel import Locale, localedata | |
from babel._compat import StringIO, string_types, text_type, PY2 | |
from babel.core import UnknownLocaleError | |
from babel.messages.catalog import Catalog | |
from babel.messages.extract import DEFAULT_KEYWORDS, DEFAULT_MAPPING, check_and_call_extract_file, extract_from_dir | |
from babel.messages.mofile import write_mo | |
from babel.messages.pofile import read_po, write_po | |
from babel.util import LOCALTZ | |
from distutils import log as distutils_log | |
from distutils.cmd import Command as _Command | |
from distutils.errors import DistutilsOptionError, DistutilsSetupError | |
try: | |
from ConfigParser import RawConfigParser | |
except ImportError: | |
from configparser import RawConfigParser | |
po_file_read_mode = ('rU' if PY2 else 'r') | |
def listify_value(arg, split=None): | |
""" | |
Make a list out of an argument. | |
Values from `distutils` argument parsing are always single strings; | |
values from `optparse` parsing may be lists of strings that may need | |
to be further split. | |
No matter the input, this function returns a flat list of whitespace-trimmed | |
strings, with `None` values filtered out. | |
>>> listify_value("foo bar") | |
['foo', 'bar'] | |
>>> listify_value(["foo bar"]) | |
['foo', 'bar'] | |
>>> listify_value([["foo"], "bar"]) | |
['foo', 'bar'] | |
>>> listify_value([["foo"], ["bar", None, "foo"]]) | |
['foo', 'bar', 'foo'] | |
>>> listify_value("foo, bar, quux", ",") | |
['foo', 'bar', 'quux'] | |
:param arg: A string or a list of strings | |
:param split: The argument to pass to `str.split()`. | |
:return: | |
""" | |
out = [] | |
if not isinstance(arg, (list, tuple)): | |
arg = [arg] | |
for val in arg: | |
if val is None: | |
continue | |
if isinstance(val, (list, tuple)): | |
out.extend(listify_value(val, split=split)) | |
continue | |
out.extend(s.strip() for s in text_type(val).split(split)) | |
assert all(isinstance(val, string_types) for val in out) | |
return out | |
class Command(_Command): | |
# This class is a small shim between Distutils commands and | |
# optparse option parsing in the frontend command line. | |
#: Option name to be input as `args` on the script command line. | |
as_args = None | |
#: Options which allow multiple values. | |
#: This is used by the `optparse` transmogrification code. | |
multiple_value_options = () | |
#: Options which are booleans. | |
#: This is used by the `optparse` transmogrification code. | |
# (This is actually used by distutils code too, but is never | |
# declared in the base class.) | |
boolean_options = () | |
#: Option aliases, to retain standalone command compatibility. | |
#: Distutils does not support option aliases, but optparse does. | |
#: This maps the distutils argument name to an iterable of aliases | |
#: that are usable with optparse. | |
option_aliases = {} | |
#: Choices for options that needed to be restricted to specific | |
#: list of choices. | |
option_choices = {} | |
#: Log object. To allow replacement in the script command line runner. | |
log = distutils_log | |
def __init__(self, dist=None): | |
# A less strict version of distutils' `__init__`. | |
self.distribution = dist | |
self.initialize_options() | |
self._dry_run = None | |
self.verbose = False | |
self.force = None | |
self.help = 0 | |
self.finalized = 0 | |
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} | |
) | |
.. versionadded:: 0.9 | |
""" | |
description = 'compile message catalogs to binary MO files' | |
user_options = [ | |
('domain=', 'D', | |
"domains of PO files (space separated list, 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>.mo')"), | |
('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): | |
self.domain = listify_value(self.domain) | |
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 output file ' | |
'or the base directory') | |
def run(self): | |
n_errors = 0 | |
for domain in self.domain: | |
for catalog, errors in self._run_domain(domain).items(): | |
n_errors += len(errors) | |
if n_errors: | |
self.log.error('%d errors encountered.' % n_errors) | |
return (1 if n_errors else 0) | |
def _run_domain(self, domain): | |
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', | |
domain + '.po'))) | |
mo_files.append(os.path.join(self.directory, self.locale, | |
'LC_MESSAGES', | |
domain + '.mo')) | |
else: | |
for locale in os.listdir(self.directory): | |
po_file = os.path.join(self.directory, locale, | |
'LC_MESSAGES', 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', | |
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', | |
domain + '.mo')) | |
if not po_files: | |
raise DistutilsOptionError('no message catalogs found') | |
catalogs_and_errors = {} | |
for idx, (locale, po_file) in enumerate(po_files): | |
mo_file = mo_files[idx] | |
with open(po_file, 'rb') as infile: | |
catalog = read_po(infile, locale) | |
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) | |
self.log.info( | |
'%d of %d messages (%d%%) translated in %s', | |
translated, len(catalog), percentage, po_file | |
) | |
if catalog.fuzzy and not self.use_fuzzy: | |
self.log.info('catalog %s is marked as fuzzy, skipping', po_file) | |
continue | |
catalogs_and_errors[catalog] = catalog_errors = list(catalog.check()) | |
for message, errors in catalog_errors: | |
for error in errors: | |
self.log.error( | |
'error: %s:%d: %s', po_file, message.lineno, error | |
) | |
self.log.info('compiling catalog %s to %s', po_file, mo_file) | |
with open(mo_file, 'wb') as outfile: | |
write_mo(outfile, catalog, use_fuzzy=self.use_fuzzy) | |
return catalogs_and_errors | |
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} | |
) | |
""" | |
description = 'extract localizable strings from the project code' | |
user_options = [ | |
('charset=', None, | |
'charset to use in the output file (default "utf-8")'), | |
('keywords=', 'k', | |
'space-separated list of keywords to look for in addition to the ' | |
'defaults (may be repeated multiple times)'), | |
('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'), | |
('add-location=', None, | |
'location lines format. If it is not given or "full", it generates ' | |
'the lines with both file name and line number. If it is "file", ' | |
'the line number part is omitted. If it is "never", it completely ' | |
'suppresses the lines (same as --no-location).'), | |
('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'), | |
('project=', None, | |
'set project name in output'), | |
('version=', None, | |
'set project version in output'), | |
('add-comments=', 'c', | |
'place comment block with TAG (or those preceding keyword lines) in ' | |
'output file. Separate multiple TAGs with commas(,)'), # TODO: Support repetition of this argument | |
('strip-comments', 's', | |
'strip the comment TAGs from the comments.'), | |
('input-paths=', None, | |
'files or directories that should be scanned for messages. Separate multiple ' | |
'files or directories with commas(,)'), # TODO: Support repetition of this argument | |
('input-dirs=', None, # TODO (3.x): Remove me. | |
'alias for input-paths (does allow files as well as directories).'), | |
] | |
boolean_options = [ | |
'no-default-keywords', 'no-location', 'omit-header', 'no-wrap', | |
'sort-output', 'sort-by-file', 'strip-comments' | |
] | |
as_args = 'input-paths' | |
multiple_value_options = ('add-comments', 'keywords') | |
option_aliases = { | |
'keywords': ('--keyword',), | |
'mapping-file': ('--mapping',), | |
'output-file': ('--output',), | |
'strip-comments': ('--strip-comment-tags',), | |
} | |
option_choices = { | |
'add-location': ('full', 'file', 'never',), | |
} | |
def initialize_options(self): | |
self.charset = 'utf-8' | |
self.keywords = None | |
self.no_default_keywords = False | |
self.mapping_file = None | |
self.no_location = False | |
self.add_location = None | |
self.omit_header = False | |
self.output_file = None | |
self.input_dirs = None | |
self.input_paths = None | |
self.width = None | |
self.no_wrap = False | |
self.sort_output = False | |
self.sort_by_file = False | |
self.msgid_bugs_address = None | |
self.copyright_holder = None | |
self.project = None | |
self.version = None | |
self.add_comments = None | |
self.strip_comments = False | |
self.include_lineno = True | |
def finalize_options(self): | |
if self.input_dirs: | |
if not self.input_paths: | |
self.input_paths = self.input_dirs | |
else: | |
raise DistutilsOptionError( | |
'input-dirs and input-paths are mutually exclusive' | |
) | |
if self.no_default_keywords: | |
keywords = {} | |
else: | |
keywords = DEFAULT_KEYWORDS.copy() | |
keywords.update(parse_keywords(listify_value(self.keywords))) | |
self.keywords = keywords | |
if not self.keywords: | |
raise DistutilsOptionError('you must specify new keywords if you ' | |
'disable the default ones') | |
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 not self.no_wrap and not self.width: | |
self.width = 76 | |
elif self.width is not None: | |
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 self.input_paths: | |
if isinstance(self.input_paths, string_types): | |
self.input_paths = re.split(r',\s*', self.input_paths) | |
elif self.distribution is not None: | |
self.input_paths = dict.fromkeys([ | |
k.split('.', 1)[0] | |
for k in (self.distribution.packages or ()) | |
]).keys() | |
else: | |
self.input_paths = [] | |
if not self.input_paths: | |
raise DistutilsOptionError("no input files or directories specified") | |
for path in self.input_paths: | |
if not os.path.exists(path): | |
raise DistutilsOptionError("Input path: %s does not exist" % path) | |
self.add_comments = listify_value(self.add_comments or (), ",") | |
if self.distribution: | |
if not self.project: | |
self.project = self.distribution.get_name() | |
if not self.version: | |
self.version = self.distribution.get_version() | |
if self.add_location == 'never': | |
self.no_location = True | |
elif self.add_location == 'file': | |
self.include_lineno = False | |
def run(self): | |
mappings = self._get_mappings() | |
with open(self.output_file, 'wb') as outfile: | |
catalog = Catalog(project=self.project, | |
version=self.version, | |
msgid_bugs_address=self.msgid_bugs_address, | |
copyright_holder=self.copyright_holder, | |
charset=self.charset) | |
for path, method_map, options_map in mappings: | |
def callback(filename, method, options): | |
if method == 'ignore': | |
return | |
# If we explicitly provide a full filepath, just use that. | |
# Otherwise, path will be the directory path and filename | |
# is the relative path from that dir to the file. | |
# So we can join those to get the full filepath. | |
if os.path.isfile(path): | |
filepath = path | |
else: | |
filepath = os.path.normpath(os.path.join(path, 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) | |
if os.path.isfile(path): | |
current_dir = os.getcwd() | |
extracted = check_and_call_extract_file( | |
path, method_map, options_map, | |
callback, self.keywords, self.add_comments, | |
self.strip_comments, current_dir | |
) | |
else: | |
extracted = extract_from_dir( | |
path, 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, context in extracted: | |
if os.path.isfile(path): | |
filepath = filename # already normalized | |
else: | |
filepath = os.path.normpath(os.path.join(path, filename)) | |
catalog.add(message, None, [(filepath, lineno)], | |
auto_comments=comments, context=context) | |
self.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, | |
include_lineno=self.include_lineno) | |
def _get_mappings(self): | |
mappings = [] | |
if self.mapping_file: | |
with open(self.mapping_file, po_file_read_mode) as fileobj: | |
method_map, options_map = parse_mapping(fileobj) | |
for path in self.input_paths: | |
mappings.append((path, method_map, options_map)) | |
elif getattr(self.distribution, 'message_extractors', None): | |
message_extractors = self.distribution.message_extractors | |
for path, mapping in message_extractors.items(): | |
if isinstance(mapping, string_types): | |
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.append((path, method_map, options_map)) | |
else: | |
for path in self.input_paths: | |
mappings.append((path, 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 | |
""" | |
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} | |
) | |
""" | |
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'), | |
('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'), | |
] | |
boolean_options = ['no-wrap'] | |
def initialize_options(self): | |
self.output_dir = None | |
self.output_file = None | |
self.input_file = None | |
self.locale = None | |
self.domain = 'messages' | |
self.no_wrap = False | |
self.width = None | |
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 as 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)) | |
if self.no_wrap and self.width: | |
raise DistutilsOptionError("'--no-wrap' and '--width' are mutually " | |
"exclusive") | |
if not self.no_wrap and not self.width: | |
self.width = 76 | |
elif self.width is not None: | |
self.width = int(self.width) | |
def run(self): | |
self.log.info( | |
'creating catalog %s based on %s', self.output_file, self.input_file | |
) | |
with open(self.input_file, 'rb') as infile: | |
# Although reading from the catalog template, read_po must be fed | |
# the locale in order to correctly calculate plurals | |
catalog = read_po(infile, locale=self.locale) | |
catalog.locale = self._locale | |
catalog.revision_date = datetime.now(LOCALTZ) | |
catalog.fuzzy = False | |
with open(self.output_file, 'wb') as outfile: | |
write_po(outfile, catalog, width=self.width) | |
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} | |
) | |
.. versionadded:: 0.9 | |
""" | |
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')"), | |
('omit-header', None, | |
"do not include msgid "" entry in header"), | |
('locale=', 'l', | |
'locale of the catalog to compile'), | |
('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'), | |
('ignore-obsolete=', None, | |
'whether to omit obsolete messages from the output'), | |
('no-fuzzy-matching', 'N', | |
'do not use fuzzy matching'), | |
('update-header-comment', None, | |
'update target header comment'), | |
('previous', None, | |
'keep previous msgids of translated messages'), | |
] | |
boolean_options = [ | |
'omit-header', 'no-wrap', 'ignore-obsolete', 'no-fuzzy-matching', | |
'previous', 'update-header-comment', | |
] | |
def initialize_options(self): | |
self.domain = 'messages' | |
self.input_file = None | |
self.output_dir = None | |
self.output_file = None | |
self.omit_header = False | |
self.locale = None | |
self.width = None | |
self.no_wrap = False | |
self.ignore_obsolete = False | |
self.no_fuzzy_matching = False | |
self.update_header_comment = 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_wrap and self.width: | |
raise DistutilsOptionError("'--no-wrap' and '--width' are mutually " | |
"exclusive") | |
if not self.no_wrap and not self.width: | |
self.width = 76 | |
elif self.width is not None: | |
self.width = int(self.width) | |
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)) | |
if not po_files: | |
raise DistutilsOptionError('no message catalogs found') | |
domain = self.domain | |
if not domain: | |
domain = os.path.splitext(os.path.basename(self.input_file))[0] | |
with open(self.input_file, 'rb') as infile: | |
template = read_po(infile) | |
for locale, filename in po_files: | |
self.log.info('updating catalog %s based on %s', filename, self.input_file) | |
with open(filename, 'rb') as infile: | |
catalog = read_po(infile, locale=locale, domain=domain) | |
catalog.update( | |
template, self.no_fuzzy_matching, | |
update_header_comment=self.update_header_comment | |
) | |
tmpname = os.path.join(os.path.dirname(filename), | |
tempfile.gettempprefix() + | |
os.path.basename(filename)) | |
try: | |
with open(tmpname, 'wb') as tmpfile: | |
write_po(tmpfile, catalog, | |
omit_header=self.omit_header, | |
ignore_obsolete=self.ignore_obsolete, | |
include_previous=self.previous, width=self.width) | |
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' | |
} | |
command_classes = { | |
'compile': compile_catalog, | |
'extract': extract_messages, | |
'init': init_catalog, | |
'update': update_catalog, | |
} | |
log = None # Replaced on instance level | |
def run(self, argv=None): | |
"""Main entry point of the command-line interface. | |
:param argv: list of arguments passed on the command-line | |
""" | |
if argv is None: | |
argv = sys.argv | |
self.parser = optparse.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:]) | |
self._configure_logging(options.loglevel) | |
if options.list_locales: | |
identifiers = localedata.locale_identifiers() | |
longest = max([len(identifier) for identifier in identifiers]) | |
identifiers.sort() | |
format = u'%%-%ds %%s' % (longest + 1) | |
for identifier in identifiers: | |
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('no valid command or option passed. ' | |
'Try the -h/--help option for more information.') | |
cmdname = args[0] | |
if cmdname not in self.commands: | |
self.parser.error('unknown command "%s"' % cmdname) | |
cmdinst = self._configure_command(cmdname, args[1:]) | |
return cmdinst.run() | |
def _configure_logging(self, loglevel): | |
self.log = logging.getLogger('babel') | |
self.log.setLevel(loglevel) | |
# Don't add a new handler for every instance initialization (#227), this | |
# would cause duplicated output when the CommandLineInterface as an | |
# normal Python class. | |
if self.log.handlers: | |
handler = self.log.handlers[0] | |
else: | |
handler = logging.StreamHandler() | |
self.log.addHandler(handler) | |
handler.setLevel(loglevel) | |
formatter = logging.Formatter('%(message)s') | |
handler.setFormatter(formatter) | |
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 = sorted(self.commands.items()) | |
for name, description in commands: | |
print(format % (name, description)) | |
def _configure_command(self, cmdname, argv): | |
""" | |
:type cmdname: str | |
:type argv: list[str] | |
""" | |
cmdclass = self.command_classes[cmdname] | |
cmdinst = cmdclass() | |
if self.log: | |
cmdinst.log = self.log # Use our logger, not distutils'. | |
assert isinstance(cmdinst, Command) | |
cmdinst.initialize_options() | |
parser = optparse.OptionParser( | |
usage=self.usage % (cmdname, ''), | |
description=self.commands[cmdname] | |
) | |
as_args = getattr(cmdclass, "as_args", ()) | |
for long, short, help in cmdclass.user_options: | |
name = long.strip("=") | |
default = getattr(cmdinst, name.replace('-', '_')) | |
strs = ["--%s" % name] | |
if short: | |
strs.append("-%s" % short) | |
strs.extend(cmdclass.option_aliases.get(name, ())) | |
choices = cmdclass.option_choices.get(name, None) | |
if name == as_args: | |
parser.usage += "<%s>" % name | |
elif name in cmdclass.boolean_options: | |
parser.add_option(*strs, action="store_true", help=help) | |
elif name in cmdclass.multiple_value_options: | |
parser.add_option(*strs, action="append", help=help, choices=choices) | |
else: | |
parser.add_option(*strs, help=help, default=default, choices=choices) | |
options, args = parser.parse_args(argv) | |
if as_args: | |
setattr(options, as_args.replace('-', '_'), args) | |
for key, value in vars(options).items(): | |
setattr(cmdinst, key, value) | |
try: | |
cmdinst.ensure_finalized() | |
except DistutilsOptionError as err: | |
parser.error(str(err)) | |
return cmdinst | |
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 | |
:see: `extract_from_directory` | |
""" | |
extractors = {} | |
method_map = [] | |
options_map = {} | |
parser = RawConfigParser() | |
parser._sections = OrderedDict(parser._sections) # We need ordered sections | |
if PY2: | |
parser.readfp(fileobj, filename) | |
else: | |
parser.read_file(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 = sorted(parse_keywords(['_', 'dgettext:2', 'dngettext:2,3', 'pgettext:1c,2']).items()) | |
>>> for keyword, indices in kw: | |
... print((keyword, indices)) | |
('_', None) | |
('dgettext', (2,)) | |
('dngettext', (2, 3)) | |
('pgettext', ((1, 'c'), 2)) | |
""" | |
keywords = {} | |
for string in strings: | |
if ':' in string: | |
funcname, indices = string.split(':') | |
else: | |
funcname, indices = string, None | |
if funcname not in keywords: | |
if indices: | |
inds = [] | |
for x in indices.split(','): | |
if x[-1] == 'c': | |
inds.append((int(x[:-1]), 'c')) | |
else: | |
inds.append(int(x)) | |
indices = tuple(inds) | |
keywords[funcname] = indices | |
return keywords | |
if __name__ == '__main__': | |
main() | |