From 6e728b7f4d4c158be0c6331a5fe4fd7c3873d90f Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 10 Oct 2024 11:28:51 +0000 Subject: [PATCH] opts: allow combined short option with parameter; add tests `foo -abparam`, equivalent to `foo -a -bparam`, is now allowed, in conformity with standard Unix option parsing convention. --- mmgen/data/version | 2 +- mmgen/opts.py | 8 +++---- test/cmdtest_py_d/ct_opts.py | 42 ++++++++++++++++++++++++++++-------- test/misc/opts_main.py | 2 ++ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index 4b6317a6..0fbe2a7c 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -15.1.dev3 +15.1.dev4 diff --git a/mmgen/opts.py b/mmgen/opts.py index f3c23e41..49ff448d 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -69,13 +69,11 @@ def process_uopts(opts_data, opts): opt, parm = arg[2:].split('=', 1) if '=' in arg else (arg[2:], None) die('CmdlineOptError', f'--{opt}: unrecognized option') elif arg[0] == '-' and len(arg) > 1: - for j, sopt in enumerate(arg[1:]): + for j, sopt in enumerate(arg[1:], 2): if sopt in opts: if opts[sopt].has_parm: - if j > 0: - die('CmdlineOptError', f'{arg}: short option with parameters cannot be combined') - if arg[2:]: - yield (opts[sopt].name, arg[2:]) + if arg[j:]: + yield (opts[sopt].name, arg[j:]) else: idx += 1 if idx == argv_len or (parm := sys.argv[idx]).startswith('-'): diff --git a/test/cmdtest_py_d/ct_opts.py b/test/cmdtest_py_d/ct_opts.py index 103a10e0..4f08030e 100755 --- a/test/cmdtest_py_d/ct_opts.py +++ b/test/cmdtest_py_d/ct_opts.py @@ -33,6 +33,14 @@ class CmdTestOpts(CmdTestBase): ('opt_good9', (41, 'good cmdline arg ‘-’', [])), ('opt_good10', (41, 'good cmdline arg ‘-’ with arg', [])), ('opt_good11', (41, 'good cmdline arg ‘-’ with option', [])), + ('opt_good12', (41, 'good cmdline opt (short opt with option)', [])), + ('opt_good13', (41, 'good cmdline opt (short opt with option)', [])), + ('opt_good14', (41, 'good cmdline opt (combined short opt with option)', [])), + ('opt_good15', (41, 'good cmdline opt (combined short opt with option)', [])), + ('opt_good16', (41, 'good cmdline opt (param with equals signs)', [])), + ('opt_good17', (41, 'good cmdline opt (param with equals signs)', [])), + ('opt_good18', (41, 'good cmdline opt (param with equals signs)', [])), + ('opt_good19', (41, 'good cmdline opt (param with equals signs)', [])), ('opt_bad_param', (41, 'bad global opt (--pager=1)', [])), ('opt_bad_infile', (41, 'bad infile parameter', [])), ('opt_bad_outdir', (41, 'bad outdir parameter', [])), @@ -40,8 +48,6 @@ class CmdTestOpts(CmdTestBase): ('opt_bad_autoset', (41, 'invalid autoset value', [])), ('opt_invalid_1', (41, 'invalid cmdline opt ‘--x’', [])), ('opt_invalid_2', (41, 'invalid cmdline opt ‘---’', [])), - ('opt_invalid_3', (41, 'invalid cmdline opt (combined short opt with param)', [])), - ('opt_invalid_4', (41, 'invalid cmdline opt (combined short opt with param)', [])), ('opt_invalid_5', (41, 'invalid cmdline opt (missing parameter)', [])), ('opt_invalid_6', (41, 'invalid cmdline opt (missing parameter)', [])), ('opt_invalid_7', (41, 'invalid cmdline opt (parameter not required)', [])), @@ -178,6 +184,30 @@ class CmdTestOpts(CmdTestBase): def opt_good11(self): return self.check_vals(['-q', '-', '-x'], (('arg1', '-'), ('arg2', '-x'))) + def opt_good12(self): + return self.check_vals(['-l128'], (('cfg.seed_len', '128'),)) + + def opt_good13(self): + return self.check_vals(['-l', '128'], (('cfg.seed_len', '128'),)) + + def opt_good14(self): + return self.check_vals(['-kl128'], (('cfg.keep_label', 'True'), ('cfg.seed_len', '128'))) + + def opt_good15(self): + return self.check_vals(['-kl', '128'], (('cfg.keep_label', 'True'), ('cfg.seed_len', '128'))) + + def opt_good16(self): + return self.check_vals(['--point=x=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),)) + + def opt_good17(self): + return self.check_vals(['--point', 'x=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),)) + + def opt_good18(self): + return self.check_vals(['-xx=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),)) + + def opt_good19(self): + return self.check_vals(['-x', 'x=1,y=2,z=3'], (('cfg.point', 'x=1,y=2,z=3'),)) + def opt_bad_param(self): return self.do_run(['--pager=1'], 'no parameter', 1) @@ -206,12 +236,6 @@ class CmdTestOpts(CmdTestBase): def opt_invalid_2(self): return self.opt_invalid(['---'], 'must be at least', 1) - def opt_invalid_3(self): - return self.opt_invalid(['-kl3'], 'short option with parameters', 1) - - def opt_invalid_4(self): - return self.opt_invalid(['-kl 3'], 'short option with parameters', 1) - def opt_invalid_5(self): return self.opt_invalid(['-l'], 'missing parameter', 1) @@ -222,7 +246,7 @@ class CmdTestOpts(CmdTestBase): return self.opt_invalid(['--quiet=1'], 'requires no parameter', 1) def opt_invalid_8(self): - return self.opt_invalid(['-x'], 'unrecognized option', 1) + return self.opt_invalid(['-w'], 'unrecognized option', 1) def opt_invalid_9(self): return self.opt_invalid(['--frobnicate'], 'unrecognized option', 1) diff --git a/test/misc/opts_main.py b/test/misc/opts_main.py index 9705c7ab..4dae8270 100755 --- a/test/misc/opts_main.py +++ b/test/misc/opts_main.py @@ -27,6 +27,7 @@ opts_data = { -q, --quiet Be quieter -t, --min-temp= t Minimum temperature (in degrees Celsius) -T, --max-temp= t Maximum temperature (in degrees Celsius) +-x, --point= P Point in Euclidean space -X, --cached-balances Use cached balances (Ethereum only) -v, --verbose Be more verbose sample help_note: {kgs} @@ -67,6 +68,7 @@ for k in ( 'max_temp', 'coin', 'pager', + 'point', ): msg('{:30} {}'.format( f'cfg.{k}:', getattr(cfg,k) ))