[+] add stop_at parameter to parse_args for subcommand routing, bump pr34 v0.1.5.69
1. add stop_at param to commands_typed/argparse.py parse_args(); 2. when a token from stop_at is found, parsing stops and remainder is returned inclusive; 3. enables main parser to stop before subcommand so -h passes through to subcommand; 4. add test_argparse.py with 10 tests: default, double-dash, stop_at, edge cases; 5. add test_argparse to pr34 test_names config; 6. bump pr34 to v0.1.5.69;
This commit is contained in:
parent
c491da0bb9
commit
2dac844087
@ -11,7 +11,20 @@ from typing import (
|
||||
def parse_args(
|
||||
parser: argparse.ArgumentParser,
|
||||
args: Optional[list[str]] = None,
|
||||
stop_at: Optional[list[str]] = None,
|
||||
) -> tuple[argparse.Namespace, list[str]]:
|
||||
"""Parse args with support for early termination.
|
||||
|
||||
stop_at: list of tokens that act like '--' when encountered.
|
||||
When any token from stop_at is found in args, parsing stops
|
||||
at that position: everything from that token onward (inclusive)
|
||||
goes into the returned remainder list, and the parser only
|
||||
sees args before it.
|
||||
|
||||
This allows e.g. a main parser with --log-level to stop at
|
||||
a subcommand name like 'cve', so that 'cve -h' is not consumed
|
||||
by the main parser.
|
||||
"""
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
@ -20,9 +33,12 @@ def parse_args(
|
||||
for i, o in enumerate(args):
|
||||
if o == '--':
|
||||
argv.extend(args[i + 1 :])
|
||||
|
||||
del args[i:]
|
||||
break
|
||||
|
||||
if stop_at is not None and o in stop_at:
|
||||
argv.extend(args[i:])
|
||||
del args[i:]
|
||||
break
|
||||
|
||||
return parser.parse_args(args), argv
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import argparse
|
||||
import unittest
|
||||
|
||||
from ..argparse import parse_args
|
||||
|
||||
|
||||
class TestParseArgsDefault(unittest.TestCase):
|
||||
def test_basic(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--foo', default='bar')
|
||||
opts, rest = parse_args(parser, ['--foo', 'baz'])
|
||||
self.assertEqual(opts.foo, 'baz')
|
||||
self.assertEqual(rest, [])
|
||||
|
||||
def test_double_dash(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--foo', default='bar')
|
||||
opts, rest = parse_args(parser, ['--foo', 'baz', '--', 'extra1', 'extra2'])
|
||||
self.assertEqual(opts.foo, 'baz')
|
||||
self.assertEqual(rest, ['extra1', 'extra2'])
|
||||
|
||||
def test_no_args(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--x', default='1')
|
||||
opts, rest = parse_args(parser, [])
|
||||
self.assertEqual(opts.x, '1')
|
||||
self.assertEqual(rest, [])
|
||||
|
||||
|
||||
class TestStopAt(unittest.TestCase):
|
||||
def test_stop_at_command(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--log-level', default='INFO')
|
||||
opts, rest = parse_args(
|
||||
parser, ['--log-level', 'DEBUG', 'cve', '-h'], stop_at=['cve', 'compile']
|
||||
)
|
||||
self.assertEqual(opts.log_level, 'DEBUG')
|
||||
self.assertEqual(rest, ['cve', '-h'])
|
||||
|
||||
def test_stop_at_preserves_all_after(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--verbose', action='store_true')
|
||||
opts, rest = parse_args(
|
||||
parser,
|
||||
['--verbose', 'download', '--progress', '-j', '4'],
|
||||
stop_at=['download', 'compile'],
|
||||
)
|
||||
self.assertTrue(opts.verbose)
|
||||
self.assertEqual(rest, ['download', '--progress', '-j', '4'])
|
||||
|
||||
def test_stop_at_first_arg(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--x', default='1')
|
||||
opts, rest = parse_args(parser, ['cve', 'sync', '--cache-dir', '/tmp'], stop_at=['cve'])
|
||||
self.assertEqual(opts.x, '1')
|
||||
self.assertEqual(rest, ['cve', 'sync', '--cache-dir', '/tmp'])
|
||||
|
||||
def test_stop_at_no_match(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--x', default='1')
|
||||
opts, rest = parse_args(parser, ['--x', '2'], stop_at=['cve'])
|
||||
self.assertEqual(opts.x, '2')
|
||||
self.assertEqual(rest, [])
|
||||
|
||||
def test_stop_at_with_double_dash(self) -> None:
|
||||
"""Double dash takes priority over stop_at."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--x', default='1')
|
||||
opts, rest = parse_args(parser, ['--x', '2', '--', 'cve', '-h'], stop_at=['cve'])
|
||||
self.assertEqual(opts.x, '2')
|
||||
self.assertEqual(rest, ['cve', '-h'])
|
||||
|
||||
def test_stop_at_help_not_consumed_by_main(self) -> None:
|
||||
"""The key use case: -h after command goes to subcommand, not main."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--log-level', default='INFO')
|
||||
opts, rest = parse_args(parser, ['cve', '-h'], stop_at=['cve'])
|
||||
self.assertEqual(rest, ['cve', '-h'])
|
||||
|
||||
def test_stop_at_empty_stop_list(self) -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--x', default='1')
|
||||
opts, rest = parse_args(parser, ['--x', '2'], stop_at=[])
|
||||
self.assertEqual(opts.x, '2')
|
||||
self.assertEqual(rest, [])
|
||||
@ -9,7 +9,7 @@ classifiers = [
|
||||
]
|
||||
|
||||
name = 'online.fxreader.pr34'
|
||||
version = '0.1.5.68'
|
||||
version = '0.1.5.69'
|
||||
dynamic = []
|
||||
|
||||
dependencies = [
|
||||
@ -86,6 +86,7 @@ modules = [
|
||||
search_paths = ['.']
|
||||
test_names = [
|
||||
'online.fxreader.pr34.commands_typed.tests',
|
||||
'online.fxreader.pr34.commands_typed.tests.test_argparse',
|
||||
]
|
||||
discovery_paths = ['.']
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user