Skip to content

Commit bec1151

Browse files
committed
Merge pull request #386 from akx/fix-384
Fix CLI command usage regressions
2 parents 74f2313 + 4795c86 commit bec1151

2 files changed

Lines changed: 101 additions & 19 deletions

File tree

babel/messages/frontend.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class extract_messages(Command):
218218
'charset to use in the output file (default "utf-8")'),
219219
('keywords=', 'k',
220220
'space-separated list of keywords to look for in addition to the '
221-
'defaults'),
221+
'defaults (may be repeated multiple times)'),
222222
('no-default-keywords', None,
223223
'do not include the default keywords'),
224224
('mapping-file=', 'F',
@@ -248,12 +248,12 @@ class extract_messages(Command):
248248
'set project version in output'),
249249
('add-comments=', 'c',
250250
'place comment block with TAG (or those preceding keyword lines) in '
251-
'output file. Separate multiple TAGs with commas(,)'),
251+
'output file. Separate multiple TAGs with commas(,)'), # TODO: Support repetition of this argument
252252
('strip-comments', None,
253253
'strip the comment TAGs from the comments.'),
254254
('input-paths=', None,
255255
'files or directories that should be scanned for messages. Separate multiple '
256-
'files or directories with commas(,)'),
256+
'files or directories with commas(,)'), # TODO: Support repetition of this argument
257257
('input-dirs=', None, # TODO (3.x): Remove me.
258258
'alias for input-paths (does allow files as well as directories).'),
259259
]
@@ -262,12 +262,11 @@ class extract_messages(Command):
262262
'sort-output', 'sort-by-file', 'strip-comments'
263263
]
264264
as_args = 'input-paths'
265-
multiple_value_options = ('add-comments',)
265+
multiple_value_options = ('add-comments', 'keywords')
266266

267267
def initialize_options(self):
268268
self.charset = 'utf-8'
269-
self.keywords = ''
270-
self._keywords = DEFAULT_KEYWORDS.copy()
269+
self.keywords = None
271270
self.no_default_keywords = False
272271
self.mapping_file = None
273272
self.no_location = False
@@ -295,13 +294,19 @@ def finalize_options(self):
295294
'input-dirs and input-paths are mutually exclusive'
296295
)
297296

298-
if self.no_default_keywords and not self.keywords:
297+
if self.no_default_keywords:
298+
keywords = {}
299+
else:
300+
keywords = DEFAULT_KEYWORDS.copy()
301+
302+
for kwarg in (self.keywords or ()):
303+
keywords.update(parse_keywords(kwarg.split()))
304+
305+
self.keywords = keywords
306+
307+
if not self.keywords:
299308
raise DistutilsOptionError('you must specify new keywords if you '
300309
'disable the default ones')
301-
if self.no_default_keywords:
302-
self._keywords = {}
303-
if self.keywords:
304-
self._keywords.update(parse_keywords(self.keywords.split()))
305310

306311
if not self.output_file:
307312
raise DistutilsOptionError('no output file specified')
@@ -378,13 +383,13 @@ def callback(filename, method, options):
378383
current_dir = os.getcwd()
379384
extracted = check_and_call_extract_file(
380385
path, method_map, options_map,
381-
callback, self._keywords, self.add_comments,
386+
callback, self.keywords, self.add_comments,
382387
self.strip_comments, current_dir
383388
)
384389
else:
385390
extracted = extract_from_dir(
386391
path, method_map, options_map,
387-
keywords=self._keywords,
392+
keywords=self.keywords,
388393
comment_tags=self.add_comments,
389394
callback=callback,
390395
strip_comment_tags=self.strip_comments
@@ -592,7 +597,7 @@ class update_catalog(Command):
592597
('previous', None,
593598
'keep previous msgids of translated messages')
594599
]
595-
boolean_options = ['ignore_obsolete', 'no_fuzzy_matching', 'previous', 'update_header_comment']
600+
boolean_options = ['no-wrap', 'ignore-obsolete', 'no-fuzzy-matching', 'previous', 'update-header-comment']
596601

597602
def initialize_options(self):
598603
self.domain = 'messages'
@@ -720,6 +725,8 @@ class CommandLineInterface(object):
720725
'update': update_catalog,
721726
}
722727

728+
log = None # Replaced on instance level
729+
723730
def run(self, argv=None):
724731
"""Main entry point of the command-line interface.
725732
@@ -768,7 +775,8 @@ def run(self, argv=None):
768775
if cmdname not in self.commands:
769776
self.parser.error('unknown command "%s"' % cmdname)
770777

771-
return self._dispatch(cmdname, args[1:])
778+
cmdinst = self._configure_command(cmdname, args[1:])
779+
return cmdinst.run()
772780

773781
def _configure_logging(self, loglevel):
774782
self.log = logging.getLogger('babel')
@@ -794,14 +802,15 @@ def _help(self):
794802
for name, description in commands:
795803
print(format % (name, description))
796804

797-
def _dispatch(self, cmdname, argv):
805+
def _configure_command(self, cmdname, argv):
798806
"""
799807
:type cmdname: str
800808
:type argv: list[str]
801809
"""
802810
cmdclass = self.command_classes[cmdname]
803811
cmdinst = cmdclass()
804-
cmdinst.log = self.log # Use our logger, not distutils'.
812+
if self.log:
813+
cmdinst.log = self.log # Use our logger, not distutils'.
805814
assert isinstance(cmdinst, Command)
806815
cmdinst.initialize_options()
807816

@@ -837,7 +846,7 @@ def _dispatch(self, cmdname, argv):
837846
except DistutilsOptionError as err:
838847
parser.error(str(err))
839848

840-
cmdinst.run()
849+
return cmdinst
841850

842851

843852
def main():

tests/messages/test_frontend.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# This software consists of voluntary contributions made by many
1111
# individuals. For the exact contribution history, see the revision
1212
# history and logs, available at http://babel.edgewall.org/log/.
13-
13+
import shlex
1414
from datetime import datetime
1515
from distutils.dist import Distribution
1616
from distutils.errors import DistutilsOptionError
@@ -22,9 +22,12 @@
2222
import time
2323
import unittest
2424

25+
import pytest
26+
2527
from babel import __version__ as VERSION
2628
from babel.dates import format_datetime
2729
from babel.messages import frontend, Catalog
30+
from babel.messages.frontend import CommandLineInterface, extract_messages, update_catalog
2831
from babel.util import LOCALTZ
2932
from babel.messages.pofile import read_po, write_po
3033
from babel._compat import StringIO
@@ -1227,3 +1230,73 @@ def test_parse_keywords():
12271230
'dngettext': (2, 3),
12281231
'pgettext': ((1, 'c'), 2),
12291232
}
1233+
1234+
1235+
def configure_cli_command(cmdline):
1236+
"""
1237+
Helper to configure a command class, but not run it just yet.
1238+
1239+
:param cmdline: The command line (sans the executable name)
1240+
:return: Command instance
1241+
"""
1242+
args = shlex.split(cmdline)
1243+
cli = CommandLineInterface()
1244+
cmdinst = cli._configure_command(cmdname=args[0], argv=args[1:])
1245+
return cmdinst
1246+
1247+
1248+
@pytest.mark.parametrize("split", (False, True))
1249+
def test_extract_keyword_args_384(split):
1250+
# This is a regression test for https://github.com/python-babel/babel/issues/384
1251+
1252+
kwarg_specs = [
1253+
"gettext_noop",
1254+
"gettext_lazy",
1255+
"ngettext_lazy:1,2",
1256+
"ugettext_noop",
1257+
"ugettext_lazy",
1258+
"ungettext_lazy:1,2",
1259+
"pgettext_lazy:1c,2",
1260+
"npgettext_lazy:1c,2,3",
1261+
]
1262+
1263+
if split: # Generate a command line with multiple -ks
1264+
kwarg_text = " ".join("-k %s" % kwarg_spec for kwarg_spec in kwarg_specs)
1265+
else: # Generate a single space-separated -k
1266+
kwarg_text = "-k \"%s\"" % " ".join(kwarg_specs)
1267+
1268+
# (Both of those invocation styles should be equivalent, so there is no parametrization from here on out)
1269+
1270+
cmdinst = configure_cli_command(
1271+
"extract -F babel-django.cfg --add-comments Translators: -o django232.pot %s ." % kwarg_text
1272+
)
1273+
assert isinstance(cmdinst, extract_messages)
1274+
assert set(cmdinst.keywords.keys()) == set((
1275+
'_',
1276+
'dgettext',
1277+
'dngettext',
1278+
'gettext',
1279+
'gettext_lazy',
1280+
'gettext_noop',
1281+
'N_',
1282+
'ngettext',
1283+
'ngettext_lazy',
1284+
'npgettext',
1285+
'npgettext_lazy',
1286+
'pgettext',
1287+
'pgettext_lazy',
1288+
'ugettext',
1289+
'ugettext_lazy',
1290+
'ugettext_noop',
1291+
'ungettext',
1292+
'ungettext_lazy',
1293+
))
1294+
1295+
1296+
def test_update_catalog_boolean_args():
1297+
cmdinst = configure_cli_command("update --no-wrap -N --ignore-obsolete --previous -i foo -o foo -l en")
1298+
assert isinstance(cmdinst, update_catalog)
1299+
assert cmdinst.no_wrap is True
1300+
assert cmdinst.no_fuzzy_matching is True
1301+
assert cmdinst.ignore_obsolete is True
1302+
assert cmdinst.previous is False # Mutually exclusive with no_fuzzy_matching

0 commit comments

Comments
 (0)