support negated command-line options

- all options without parameters may be negated by prefixing the option name
  with ‘no-’
- if the option name itself begins with ‘no-’, then the option is negated
  by removing the ‘no-’ prefix
- negation may be used to override options set in the cfg file
- command-line options may also be overridden, with the last-listed option
  taking precedence
- as with ordinary options, substring matching is supported

Examples:

    OPTION        NEGATED OPTION
    --no-license  --license
    --no-license  --lic
    --quiet       --no-quiet
    --quiet       --no-q

Testing:

    $ test/cmdtest.py opts cfgfile
This commit is contained in:
The MMGen Project 2024-10-20 10:16:24 +00:00
commit df3559d420
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
6 changed files with 63 additions and 3 deletions

View file

@ -1 +1 @@
15.1.dev6
15.1.dev7

View file

@ -25,6 +25,16 @@ from collections import namedtuple
from .cfg import gc
def negated_opts(opts, data={}):
if data:
return data
else:
data.update(dict(
((k[3:] if k.startswith('no-') else f'no-{k}'), v)
for k, v in opts.items()
if len(k) > 1 and not v.has_parm))
return data
def get_opt_by_substring(opt, opts):
matches = [o for o in opts if o.startswith(opt)]
if len(matches) == 1:
@ -67,6 +77,12 @@ def process_uopts(opts_data, opts):
if parm:
die('CmdlineOptError', f'option --{_opt} requires no parameter')
yield (opts[_opt].name, True)
elif (
(_opt := opt) in negated_opts(opts)
or (_opt := get_opt_by_substring(_opt, negated_opts(opts)))):
if parm:
die('CmdlineOptError', f'option --{_opt} requires no parameter')
yield (negated_opts(opts)[_opt].name, False)
else:
die('CmdlineOptError', f'--{opt}: unrecognized option')
elif arg[0] == '-' and len(arg) > 1:

View file

@ -38,6 +38,8 @@ class CmdTestCfgFile(CmdTestBase):
('coin_specific_vars', (40, 'setting coin-specific vars', [])),
('chain_names', (40, 'setting chain names', [])),
('mnemonic_entry_modes', (40, 'setting mnemonic entry modes', [])),
('opt_override1', (40, 'cfg file opts not overridden', [])),
('opt_override2', (40, 'negative cmdline opts overriding cfg file opts', [])),
)
def __init__(self, trunner, cfgs, spawn):
@ -276,3 +278,20 @@ class CmdTestCfgFile(CmdTestBase):
t.skip_ok = True
return t
def opt_override1(self):
self.write_to_cfgfile('usr', ['no_license true', 'scroll true'])
t = self.spawn_test(
args = ['print_cfg', 'scroll', 'no_license'])
t.expect('scroll: True')
t.expect('no_license: True')
return t
def opt_override2(self):
self.write_to_cfgfile('usr', ['no_license true', 'scroll true'])
t = self.spawn_test(
args = ['print_cfg', 'scroll', 'no_license'],
opts = ['--no-scrol', '--lic'])
t.expect('scroll: False')
t.expect('no_license: False')
return t

View file

@ -41,6 +41,11 @@ class CmdTestOpts(CmdTestBase):
('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_good20', (41, 'good cmdline opt (opt + negated opt)', [])),
('opt_good21', (41, 'good cmdline opt (negated negative opt)', [])),
('opt_good22', (41, 'good cmdline opt (opt + negated opt [substring])', [])),
('opt_good23', (41, 'good cmdline opt (negated negative opt [substring])', [])),
('opt_good24', (41, 'good cmdline opt (negated opt + opt [substring])', [])),
('opt_bad_param', (41, 'bad global opt (--pager=1)', [])),
('opt_bad_infile', (41, 'bad infile parameter', [])),
('opt_bad_outdir', (41, 'bad outdir parameter', [])),
@ -208,6 +213,21 @@ class CmdTestOpts(CmdTestBase):
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_good20(self):
return self.check_vals(['--pager', '--no-pager'], (('cfg.pager', 'False'),))
def opt_good21(self):
return self.check_vals(['--foobleize'], (('cfg.no_foobleize', 'False'),))
def opt_good22(self):
return self.check_vals(['--quiet', '--no-q'], (('cfg.quiet', 'False'),))
def opt_good23(self):
return self.check_vals(['--foobl'], (('cfg.no_foobleize', 'False'),))
def opt_good24(self):
return self.check_vals(['--no-pag', '--pag'], (('cfg.pager', 'True'),))
def opt_bad_param(self):
return self.do_run(['--pager=1'], 'no parameter', 1)

View file

@ -21,7 +21,10 @@ msg(f'Sys cfg file: {os.path.relpath(cf_sys.fn)}')
msg(f'Sample cfg file: {os.path.relpath(cf_sample.fn)}')
if op:
if op == 'parse_test':
if op == 'print_cfg':
for name in args:
msg('{} {}'.format(name+':', getattr(cfg, name)))
elif op == 'parse_test':
ps = cf_sample.get_lines()
msg(f'parsed chunks: {len(ps)}')
pu = cf_usr.get_lines()

View file

@ -15,6 +15,7 @@ opts_data = {
-d, --outdir= d Use outdir 'd'
-C, --print-checksum Print a checksum
-E, --fee-estimate-mode=M Specify the network fee estimate mode.
-F, --no-foobleize Do not foobleize the output, even on user request
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
'f' at offset 'o' (comma-separated)
-k, --keep-label Reuse label of input wallet for output wallet
@ -68,7 +69,8 @@ for k in (
'max_temp',
'coin',
'pager',
'point'):
'point',
'no_foobleize'):
msg('{:30} {}'.format(f'cfg.{k}:', getattr(cfg, k)))
msg('')