From df3559d4200e3ec6fc761e9cb8681f1fcf00f346 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 20 Oct 2024 10:16:24 +0000 Subject: [PATCH] support negated command-line options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- mmgen/data/version | 2 +- mmgen/opts.py | 16 ++++++++++++++++ test/cmdtest_d/ct_cfgfile.py | 19 +++++++++++++++++++ test/cmdtest_d/ct_opts.py | 20 ++++++++++++++++++++ test/misc/cfg_main.py | 5 ++++- test/misc/opts_main.py | 4 +++- 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index f08075ae..f3f95d67 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -15.1.dev6 +15.1.dev7 diff --git a/mmgen/opts.py b/mmgen/opts.py index 69e18b84..99261f2b 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -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: diff --git a/test/cmdtest_d/ct_cfgfile.py b/test/cmdtest_d/ct_cfgfile.py index 0d7e0b71..0b1338c3 100755 --- a/test/cmdtest_d/ct_cfgfile.py +++ b/test/cmdtest_d/ct_cfgfile.py @@ -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 diff --git a/test/cmdtest_d/ct_opts.py b/test/cmdtest_d/ct_opts.py index 6a66486c..27dc267e 100755 --- a/test/cmdtest_d/ct_opts.py +++ b/test/cmdtest_d/ct_opts.py @@ -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) diff --git a/test/misc/cfg_main.py b/test/misc/cfg_main.py index cfe3843c..eb6dbd7d 100755 --- a/test/misc/cfg_main.py +++ b/test/misc/cfg_main.py @@ -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() diff --git a/test/misc/opts_main.py b/test/misc/opts_main.py index bb3b0f4f..091e8df1 100755 --- a/test/misc/opts_main.py +++ b/test/misc/opts_main.py @@ -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('')