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.
This commit is contained in:
The MMGen Project 2024-10-10 11:28:51 +00:00
commit 6e728b7f4d
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 39 additions and 15 deletions

View file

@ -1 +1 @@
15.1.dev3
15.1.dev4

View file

@ -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('-'):

View file

@ -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)

View file

@ -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) ))