3 Commits 5f3920b11a ... df3559d420

Author SHA1 Message Date
  The MMGen Project df3559d420 support negated command-line options 1 month ago
  The MMGen Project 34deadb0f5 test suite cfgfile: cleanups 1 month ago
  The MMGen Project 399f7d42a7 opts: minor cleanups 1 month ago
6 changed files with 117 additions and 55 deletions
  1. 1 1
      mmgen/data/version
  2. 28 11
      mmgen/opts.py
  3. 54 35
      test/cmdtest_d/ct_cfgfile.py
  4. 20 0
      test/cmdtest_d/ct_opts.py
  5. 11 7
      test/misc/cfg_main.py
  6. 3 1
      test/misc/opts_main.py

+ 1 - 1
mmgen/data/version

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

+ 28 - 11
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:
@@ -52,21 +62,28 @@ def process_uopts(opts_data, opts):
 				opt, parm = arg[2:].split('=', 1) if '=' in arg else (arg[2:], None)
 				if len(opt) < 2:
 					die('CmdlineOptError', f'--{opt}: option name must be at least two characters long')
-				if opt in opts or (opt := get_opt_by_substring(opt, opts)):
-					if opts[opt].has_parm:
+				if (
+						(_opt := opt) in opts
+						or (_opt := get_opt_by_substring(_opt, opts))):
+					if opts[_opt].has_parm:
 						if parm:
-							yield (opts[opt].name, parm)
+							yield (opts[_opt].name, parm)
 						else:
 							idx += 1
 							if idx == argv_len or (parm := sys.argv[idx]).startswith('-'):
-								die('CmdlineOptError', f'missing parameter for option --{opt}')
-							yield (opts[opt].name, parm)
+								die('CmdlineOptError', f'missing parameter for option --{_opt}')
+							yield (opts[_opt].name, parm)
 					else:
 						if parm:
-							die('CmdlineOptError', f'option --{opt} requires no parameter')
-						yield (opts[opt].name, True)
+							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:
-					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:], 2):
@@ -109,7 +126,7 @@ def process_uopts(opts_data, opts):
 
 cmd_opts_pat = re.compile(r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)')
 global_opts_pat = re.compile(r'^\t\t\t(.)(.) --([a-zA-Z0-9-]{2,64})(=| )(.+)')
-ao = namedtuple('opt', ['name', 'has_parm'])
+opt_tuple = namedtuple('cmdline_option', ['name', 'has_parm'])
 
 def parse_opts(opts_data, opt_filter, global_opts_data, global_opts_filter):
 
@@ -117,7 +134,7 @@ def parse_opts(opts_data, opt_filter, global_opts_data, global_opts_filter):
 		for line in opts_data['text']['options'].strip().splitlines():
 			m = cmd_opts_pat.match(line)
 			if m and (not opt_filter or m[1] in opt_filter):
-				ret = ao(m[2].replace('-', '_'), m[3] == '=')
+				ret = opt_tuple(m[2].replace('-', '_'), m[3] == '=')
 				yield (m[1], ret)
 				yield (m[2], ret)
 
@@ -125,7 +142,7 @@ def parse_opts(opts_data, opt_filter, global_opts_data, global_opts_filter):
 		for line in global_opts_data['text'].splitlines():
 			m = global_opts_pat.match(line)
 			if m and m[1] in global_opts_filter.coin and m[2] in global_opts_filter.cmd:
-				yield (m[3], ao(m[3].replace('-', '_'), m[4] == '='))
+				yield (m[3], opt_tuple(m[3].replace('-', '_'), m[4] == '='))
 
 	opts = tuple(parse_cmd_opts_text()) + tuple(parse_global_opts_text())
 

+ 54 - 35
test/cmdtest_d/ct_cfgfile.py

@@ -38,16 +38,26 @@ 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):
 		CmdTestBase.__init__(self, trunner, cfgs, spawn)
 		self.spawn_env['MMGEN_TEST_SUITE_CFGTEST'] = '1'
 
-	def spawn_test(self, args=[], extra_desc='', pexpect_spawn=None, exit_val=None):
+	def read_from_cfgfile(self, loc):
+		return read_from_file(self.path(loc))
+
+	def write_to_cfgfile(self, loc, data, verbose=False):
+		write_to_file(self.path(loc), '\n'.join(data) + '\n')
+		if verbose:
+			imsg(yellow(f'Wrote cfg file: {data!r}'))
+
+	def spawn_test(self, opts=[], args=[], extra_desc='', pexpect_spawn=None, exit_val=None):
 		return self.spawn(
 			'test/misc/cfg.py',
-			[f'--data-dir={self.path("data_dir")}'] + args,
+			[f'--data-dir={self.path("data_dir")}'] + opts + args,
 			cmd_dir       = '.',
 			extra_desc    = extra_desc,
 			pexpect_spawn = pexpect_spawn,
@@ -71,16 +81,16 @@ class CmdTestCfgFile(CmdTestBase):
 		self.copy_sys_sample()
 		t = self.spawn_test()
 		t.read()
-		u = read_from_file(self.path('usr'))
-		S = read_from_file(self.path('sys'))
+		u = self.read_from_cfgfile('usr')
+		S = self.read_from_cfgfile('sys')
 		assert u[-1] == '\n', u
 		assert u.replace('\r\n', '\n') == S, 'u != S'
 		self.check_replaced_sample()
 		return t
 
 	def check_replaced_sample(self):
-		S = read_from_file(self.path('sys'))
-		s = read_from_file(self.path('sample'))
+		s = self.read_from_cfgfile('sample')
+		S = self.read_from_cfgfile('sys')
 		assert s[-1] == '\n', s
 		assert S.splitlines() == s.splitlines()[:-1], 'sys != sample[:-1]'
 
@@ -94,23 +104,22 @@ class CmdTestCfgFile(CmdTestBase):
 
 	def no_metadata_sample(self):
 		self.copy_sys_sample()
-		s = read_from_file(self.path('sys'))
+		S = self.read_from_cfgfile('sys')
 		e = CfgFileSampleUsr.out_of_date_fs.format(self.path('sample'))
-		return self.bad_sample(s, e)
+		return self.bad_sample(S, e)
 
 	def altered_sample(self):
-		s = '\n'.join(read_from_file(self.path('sample')).splitlines()[1:]) + '\n'
+		s = '\n'.join(self.read_from_cfgfile('sample').splitlines()[1:]) + '\n'
 		e = CfgFileSampleUsr.altered_by_user_fs.format(self.path('sample'))
 		return self.bad_sample(s, e)
 
 	def old_sample_common(self, old_set=False, args=[], pexpect_spawn=False):
-		s = read_from_file(self.path('sys'))
-		d = s.replace('monero_', 'zcash_').splitlines()
-		a1 = ['', '# Uncomment to make foo true:', '# foo true']
-		a2 = ['', '# Uncomment to make bar false:', '# bar false']
-		d = d + a1 + a2
-		chk = cfg_file_sample.cls_make_metadata(d)
-		write_to_file(self.path('sample'), '\n'.join(d+chk) + '\n')
+		d = (
+			self.read_from_cfgfile('sys').replace('monero_', 'zcash_').splitlines()
+			+ ['', '# Uncomment to make foo true:', '# foo true']
+			+ ['', '# Uncomment to make bar false:', '# bar false']
+		)
+		self.write_to_cfgfile('sample', d + cfg_file_sample.cls_make_metadata(d))
 
 		t = self.spawn_test(args=args, pexpect_spawn=pexpect_spawn, exit_val=1 if old_set else None)
 
@@ -148,22 +157,19 @@ class CmdTestCfgFile(CmdTestBase):
 		return t
 
 	def old_sample(self):
-		d = ['testnet true', 'rpc_password passwOrd']
-		write_to_file(self.path('usr'), '\n'.join(d) + '\n')
+		self.write_to_cfgfile('usr', ['testnet true', 'rpc_password passwOrd'])
 		return self.old_sample_common(args=['parse_test'])
 
 	def old_sample_bad_var(self):
-		d = ['foo true', 'bar false']
-		write_to_file(self.path('usr'), '\n'.join(d) + '\n')
+		self.write_to_cfgfile('usr', ['foo true', 'bar false'])
 		t = self.old_sample_common(
 			old_set       = True,
 			pexpect_spawn = not sys.platform == 'win32')
 		t.expect('unrecognized option')
 		return t
 
-	def _autoset_opts(self, args=[], text='rpc_backend aiohttp\n', exit_val=None):
-		write_to_file(self.path('usr'), text)
-		imsg(yellow(f'Wrote cfg file:\n  {text}'))
+	def _autoset_opts(self, args=[], text='rpc_backend aiohttp', exit_val=None):
+		self.write_to_cfgfile('usr', [text], verbose=True)
 		return self.spawn_test(args=args, exit_val=exit_val)
 
 	def autoset_opts(self):
@@ -178,7 +184,7 @@ class CmdTestCfgFile(CmdTestBase):
 		return t
 
 	def autoset_opts_bad(self):
-		return self._autoset_opts_bad('not unique substring', {'text':'rpc_backend foo\n'})
+		return self._autoset_opts_bad('not unique substring', {'text':'rpc_backend foo'})
 
 	def autoset_opts_bad_cmdline(self):
 		return self._autoset_opts_bad('not unique substring', {'args':['--rpc-backend=foo']})
@@ -193,8 +199,7 @@ class CmdTestCfgFile(CmdTestBase):
 			'btc_ignore_daemon_version true',
 			'eth_ignore_daemon_version true'
 		]
-		write_to_file(self.path('usr'), '\n'.join(d) + '\n')
-		imsg(yellow('Wrote cfg file:\n  {}'.format('\n  '.join(d))))
+		self.write_to_cfgfile('usr', d, verbose=True)
 
 		for coin, res1_chk, res2_chk, res2_chk_eq in (
 			('BTC', 'True',  '1.2345', True),
@@ -234,9 +239,8 @@ class CmdTestCfgFile(CmdTestBase):
 			assert modes_chk == modes, f'{modes_chk} != {modes}'
 			return t
 
-		txt = 'mnemonic_entry_modes mmgen:full bip39:short'
-		write_to_file(self.path('usr'), txt+'\n')
-		imsg(yellow(f'Wrote cfg file: {txt!r}'))
+		self.write_to_cfgfile('usr', ['mnemonic_entry_modes mmgen:full bip39:short'], verbose=True)
+
 		t = run("{'mmgen': 'full', 'bip39': 'short'}")
 		# check that set_dfl_entry_mode() set the mode correctly:
 		t.expect('mmgen: full')
@@ -262,17 +266,32 @@ class CmdTestCfgFile(CmdTestBase):
 				t.ok()
 			return t
 
-		txt = 'eth_mainnet_chain_names istanbul constantinople'
-		write_to_file(self.path('usr'), txt+'\n')
-		imsg(yellow(f'Wrote cfg file: {txt!r}'))
+		self.write_to_cfgfile('usr', ['eth_mainnet_chain_names istanbul constantinople'], verbose=True)
+
 		t = run("['istanbul', 'constantinople']", False)
 		t = run(None, True)
 
-		txt = 'eth_testnet_chain_names rinkeby'
-		write_to_file(self.path('usr'), txt+'\n')
-		imsg(yellow(f'Wrote cfg file: {txt!r}'))
+		self.write_to_cfgfile('usr', ['eth_testnet_chain_names rinkeby'], verbose=True)
+
 		t = run(None, False)
 		t = run("['rinkeby']", True)
 
 		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

+ 20 - 0
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)
 

+ 11 - 7
test/misc/cfg_main.py

@@ -8,6 +8,7 @@ from mmgen.util import msg
 cfg = Config(process_opts=True)
 
 cmd_args = cfg._args
+op, args = (cmd_args[0], cmd_args[1:]) if cmd_args else (None, None)
 
 from mmgen.cfgfile import mmgen_cfg_file
 
@@ -19,24 +20,27 @@ msg(f'Usr cfg file:    {os.path.relpath(cf_usr.fn)}')
 msg(f'Sys cfg file:    {os.path.relpath(cf_sys.fn)}')
 msg(f'Sample cfg file: {os.path.relpath(cf_sample.fn)}')
 
-if cmd_args:
-	if cmd_args[0] == 'parse_test':
+if op:
+	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()
 		msg('usr cfg: {}'.format(' '.join(f'{i.name}={i.value}' for i in pu)))
-	elif cmd_args[0] == 'coin_specific_vars':
-		for varname in cmd_args[1:]:
+	elif op == 'coin_specific_vars':
+		for varname in args:
 			msg('{}.{}: {}'.format(
 				type(cfg._proto).__name__,
 				varname,
 				getattr(cfg._proto, varname)
 			))
-	elif cmd_args[0] == 'autoset_opts':
+	elif op == 'autoset_opts':
 		assert cfg.rpc_backend == 'aiohttp', "cfg.rpc_backend != 'aiohttp'"
-	elif cmd_args[0] == 'autoset_opts_cmdline':
+	elif op == 'autoset_opts_cmdline':
 		assert cfg.rpc_backend == 'curl', "cfg.rpc_backend != 'curl'"
-	elif cmd_args[0] == 'mnemonic_entry_modes':
+	elif op == 'mnemonic_entry_modes':
 		from mmgen.mn_entry import mn_entry
 		msg('mnemonic_entry_modes: {}\nmmgen: {}\nbip39: {}'.format(
 			cfg.mnemonic_entry_modes,

+ 3 - 1
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('')