-
Notifications
You must be signed in to change notification settings - Fork 440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Re-harmonize argument parsing between distutils and optparse CLIs #389
Changes from all commits
414aec5
72e97c1
ad2057a
bb06614
ee8abd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,7 @@ | |
|
||
from babel import __version__ as VERSION | ||
from babel import Locale, localedata | ||
from babel._compat import StringIO, string_types | ||
from babel._compat import StringIO, string_types, text_type | ||
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 | ||
|
@@ -39,6 +39,48 @@ | |
from configparser import RawConfigParser | ||
|
||
|
||
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. | ||
|
@@ -47,8 +89,21 @@ class Command(_Command): | |
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 = {} | ||
|
||
#: Log object. To allow replacement in the script command line runner. | ||
log = distutils_log | ||
|
||
|
@@ -110,6 +165,7 @@ def initialize_options(self): | |
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') | ||
|
@@ -118,9 +174,7 @@ def finalize_options(self): | |
'or the base directory') | ||
|
||
def run(self): | ||
domains = self.domain.split() | ||
|
||
for domain in domains: | ||
for domain in self.domain: | ||
self._run_domain(domain) | ||
|
||
def _run_domain(self, domain): | ||
|
@@ -174,12 +228,12 @@ def _run_domain(self, domain): | |
if len(catalog): | ||
percentage = translated * 100 // len(catalog) | ||
self.log.info( | ||
'%d of %d messages (%d%%) translated in %r', | ||
'%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 %r is marked as fuzzy, skipping', po_file) | ||
self.log.info('catalog %s is marked as fuzzy, skipping', po_file) | ||
continue | ||
|
||
for message, errors in catalog.check(): | ||
|
@@ -188,7 +242,7 @@ def _run_domain(self, domain): | |
'error: %s:%d: %s', po_file, message.lineno, error | ||
) | ||
|
||
self.log.info('compiling catalog %r to %r', po_file, mo_file) | ||
self.log.info('compiling catalog %s to %s', po_file, mo_file) | ||
|
||
outfile = open(mo_file, 'wb') | ||
try: | ||
|
@@ -249,7 +303,7 @@ class extract_messages(Command): | |
('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', None, | ||
('strip-comments', 's', | ||
'strip the comment TAGs from the comments.'), | ||
('input-paths=', None, | ||
'files or directories that should be scanned for messages. Separate multiple ' | ||
|
@@ -263,6 +317,12 @@ class extract_messages(Command): | |
] | ||
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',), | ||
} | ||
|
||
def initialize_options(self): | ||
self.charset = 'utf-8' | ||
|
@@ -299,8 +359,7 @@ def finalize_options(self): | |
else: | ||
keywords = DEFAULT_KEYWORDS.copy() | ||
|
||
for kwarg in (self.keywords or ()): | ||
keywords.update(parse_keywords(kwarg.split())) | ||
keywords.update(parse_keywords(listify_value(self.keywords))) | ||
|
||
self.keywords = keywords | ||
|
||
|
@@ -325,11 +384,13 @@ def finalize_options(self): | |
if self.input_paths: | ||
if isinstance(self.input_paths, string_types): | ||
self.input_paths = re.split(',\s*', self.input_paths) | ||
else: | ||
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") | ||
|
@@ -338,11 +399,7 @@ def finalize_options(self): | |
if not os.path.exists(path): | ||
raise DistutilsOptionError("Input path: %s does not exist" % path) | ||
|
||
if self.add_comments: | ||
if isinstance(self.add_comments, string_types): | ||
self.add_comments = self.add_comments.split(',') | ||
else: | ||
self.add_comments = [] | ||
self.add_comments = listify_value(self.add_comments or (), ",") | ||
|
||
if self.distribution: | ||
if not self.project: | ||
|
@@ -531,7 +588,7 @@ def finalize_options(self): | |
|
||
def run(self): | ||
self.log.info( | ||
'creating catalog %r based on %r', self.output_file, self.input_file | ||
'creating catalog %s based on %s', self.output_file, self.input_file | ||
) | ||
|
||
infile = open(self.input_file, 'rb') | ||
|
@@ -662,7 +719,7 @@ def run(self): | |
raise DistutilsOptionError('no message catalogs found') | ||
|
||
for locale, filename in po_files: | ||
self.log.info('updating catalog %r based on %r', filename, self.input_file) | ||
self.log.info('updating catalog %s based on %s', filename, self.input_file) | ||
infile = open(filename, 'rb') | ||
try: | ||
catalog = read_po(infile, locale=locale, domain=domain) | ||
|
@@ -825,6 +882,7 @@ def _configure_command(self, cmdname, argv): | |
strs = ["--%s" % name] | ||
if short: | ||
strs.append("-%s" % short) | ||
strs.extend(cmdclass.option_aliases.get(name, ())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking if this, combined with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oop, good catch. The alias mapping should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (and as of ee8abd6, it is.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Err, oh. I don't know why I was looking at the old version. Shipit! |
||
if name == as_args: | ||
parser.usage += "<%s>" % name | ||
elif name in cmdclass.boolean_options: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will a single arg ever contain quotes/whitespace? Eg "foo "i am a.zip" bar"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sort of usage will never have been (how's that for a temporal form?) supported by Babel... At least not the distutils frontend. (Sneaky edit: distutils, not optparse)
Double edit: Keyword strings should never contain significant whitespace, so that shouldn't matter, actually. Directory/path args are separated by other characters.