cliutil: Add ExtendAction.
This is a user affordance we've wanted for a while, and query-report *really* wants it.
This commit is contained in:
parent
6c89febb90
commit
95f7524b00
4 changed files with 85 additions and 12 deletions
|
@ -46,6 +46,7 @@ from typing import (
|
||||||
IO,
|
IO,
|
||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
|
List,
|
||||||
NamedTuple,
|
NamedTuple,
|
||||||
NoReturn,
|
NoReturn,
|
||||||
Optional,
|
Optional,
|
||||||
|
@ -195,6 +196,42 @@ class ExitCode(enum.IntEnum):
|
||||||
BeancountErrors = 63
|
BeancountErrors = 63
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendAction(argparse.Action):
|
||||||
|
"""argparse action to let a user build a list from a string
|
||||||
|
|
||||||
|
This is a fancier version of argparse's built-in ``action='append'``.
|
||||||
|
The user's input is turned into a list of strings, split by a regexp
|
||||||
|
pattern you provide. Typical usage looks like::
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
'--option', ...,
|
||||||
|
action=ExtendAction,
|
||||||
|
const=regexp_pattern, # default is r'\s*,\s*'
|
||||||
|
...,
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
DEFAULT_PATTERN = r'\s*,\s*'
|
||||||
|
|
||||||
|
def __call__(self,
|
||||||
|
parser: argparse.ArgumentParser,
|
||||||
|
namespace: argparse.Namespace,
|
||||||
|
values: Union[Sequence[Any], str, None]=None,
|
||||||
|
option_string: Optional[str]=None,
|
||||||
|
) -> None:
|
||||||
|
pattern: str = self.const or self.DEFAULT_PATTERN
|
||||||
|
value: Optional[List[str]] = getattr(namespace, self.dest, None)
|
||||||
|
if value is None:
|
||||||
|
value = []
|
||||||
|
setattr(namespace, self.dest, value)
|
||||||
|
if values is None:
|
||||||
|
values = []
|
||||||
|
elif isinstance(values, str):
|
||||||
|
values = [values]
|
||||||
|
for s in values:
|
||||||
|
value.extend(re.split(pattern, s))
|
||||||
|
|
||||||
|
|
||||||
class InfoAction(argparse.Action):
|
class InfoAction(argparse.Action):
|
||||||
def __call__(self,
|
def __call__(self,
|
||||||
parser: argparse.ArgumentParser,
|
parser: argparse.ArgumentParser,
|
||||||
|
|
|
@ -338,10 +338,10 @@ The default is one month after the start date.
|
||||||
'--account', '-a',
|
'--account', '-a',
|
||||||
dest='accounts',
|
dest='accounts',
|
||||||
metavar='ACCOUNT',
|
metavar='ACCOUNT',
|
||||||
action='append',
|
action=cliutil.ExtendAction,
|
||||||
help="""Reconcile this account. You can specify this option
|
help="""Reconcile this account. You can specify multiple
|
||||||
multiple times. You can specify a part of the account hierarchy, or an account
|
comma-separated accounts or classifications, and/or specify this option
|
||||||
classification from metadata. Default adapts to your search criteria.
|
multiple times. Default adapts to your search criteria.
|
||||||
""")
|
""")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--id-metadata-key', '-i',
|
'--id-metadata-key', '-i',
|
||||||
|
|
|
@ -739,8 +739,9 @@ date was also not specified.
|
||||||
'--account', '-a',
|
'--account', '-a',
|
||||||
dest='accounts',
|
dest='accounts',
|
||||||
metavar='ACCOUNT',
|
metavar='ACCOUNT',
|
||||||
action='append',
|
action=cliutil.ExtendAction,
|
||||||
help="""Show this account in the report. You can specify this option
|
help="""Show this account in the report. You can specify multiple
|
||||||
|
comma-separated accounts or classifications, and/or specify this option
|
||||||
multiple times. You can specify a part of the account hierarchy, or an account
|
multiple times. You can specify a part of the account hierarchy, or an account
|
||||||
classification from metadata. If not specified, the default set adapts to your
|
classification from metadata. If not specified, the default set adapts to your
|
||||||
search criteria.
|
search criteria.
|
||||||
|
@ -749,19 +750,21 @@ search criteria.
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--show-totals', '-S',
|
'--show-totals', '-S',
|
||||||
metavar='ACCOUNT',
|
metavar='ACCOUNT',
|
||||||
action='append',
|
action=cliutil.ExtendAction,
|
||||||
help="""When entries for this account appear in the report, include
|
help="""When entries for this account appear in the report, include
|
||||||
account balance(s) as well. You can specify this option multiple times. Pass in
|
account balance(s) as well. You can specify multiple comma-separated parts of
|
||||||
a part of the account hierarchy. The default is all accounts.
|
the account hierarchy, and/or specify this option multiple times.
|
||||||
|
The default is all accounts.
|
||||||
""")
|
""")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--add-totals', '-T',
|
'--add-totals', '-T',
|
||||||
metavar='ACCOUNT',
|
metavar='ACCOUNT',
|
||||||
action='append',
|
action=cliutil.ExtendAction,
|
||||||
help="""When an account could be included in the report but does not
|
help="""When an account could be included in the report but does not
|
||||||
have any entries in the date range, include a header and account balance(s) for
|
have any entries in the date range, include a header and account balance(s) for
|
||||||
it. You can specify this option multiple times. Pass in a part of the account
|
it. You can specify multiple comma-separated parts of the account hierarchy,
|
||||||
hierarchy. The default set adapts to your search criteria.
|
and/or specify this option multiple times. The default set adapts to your
|
||||||
|
search criteria.
|
||||||
""")
|
""")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--sheet-size', '--size',
|
'--sheet-size', '--size',
|
||||||
|
|
|
@ -298,3 +298,36 @@ def test_enum_arg_choices_str_defaults(arg_enum):
|
||||||
def test_enum_arg_choices_str_args(arg_enum):
|
def test_enum_arg_choices_str_args(arg_enum):
|
||||||
sep = '/'
|
sep = '/'
|
||||||
assert arg_enum.choices_str(sep, '{}') == sep.join(c.value for c in ArgChoices)
|
assert arg_enum.choices_str(sep, '{}') == sep.join(c.value for c in ArgChoices)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('values,sep', testutil.combine_values(
|
||||||
|
[['foo'], ['bar', 'baz'], ['qu', 'quu', 'quux']],
|
||||||
|
[',', ', ', ' ,', ' , '],
|
||||||
|
))
|
||||||
|
def test_extend_action_once(values, sep):
|
||||||
|
action = cliutil.ExtendAction(['-t'], 'result')
|
||||||
|
args = argparse.Namespace()
|
||||||
|
action(None, args, sep.join(values), '-t')
|
||||||
|
assert args.result == values
|
||||||
|
|
||||||
|
def test_extend_action_multi():
|
||||||
|
action = cliutil.ExtendAction(['-t'], 'result')
|
||||||
|
args = argparse.Namespace()
|
||||||
|
action(None, args, 'foo,bar', '-t')
|
||||||
|
action(None, args, 'baz, quux', '-t')
|
||||||
|
assert args.result == ['foo', 'bar', 'baz', 'quux']
|
||||||
|
|
||||||
|
def test_extend_action_from_default():
|
||||||
|
action = cliutil.ExtendAction(['-t'], 'result')
|
||||||
|
args = argparse.Namespace(result=['foo'])
|
||||||
|
action(None, args, 'bar , baz', '-t')
|
||||||
|
assert args.result == ['foo', 'bar', 'baz']
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('pattern,expected', [
|
||||||
|
(',', ['foo', ' bar']),
|
||||||
|
(r'\s+', ['foo,', 'bar']),
|
||||||
|
])
|
||||||
|
def test_extend_action_custom_pattern(pattern, expected):
|
||||||
|
action = cliutil.ExtendAction(['-t'], 'result', const=pattern)
|
||||||
|
args = argparse.Namespace()
|
||||||
|
action(None, args, 'foo, bar', '-t')
|
||||||
|
assert args.result == expected
|
||||||
|
|
Loading…
Reference in a new issue