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…
	
	Add table
		
		Reference in a new issue