cfg, opts: improve contextual options handling
This commit is contained in:
parent
8b6c24cc07
commit
dc028988cb
5 changed files with 95 additions and 77 deletions
|
|
@ -516,9 +516,6 @@ class Autosign:
|
|||
if any(k in cfg._uopts for k in ('help', 'longhelp')):
|
||||
return
|
||||
|
||||
if 'coin' in cfg._uopts:
|
||||
die(1, '--coin option not supported with this command. Use --coins instead')
|
||||
|
||||
self.coins = cfg.coins.upper().split(',') if cfg.coins else []
|
||||
|
||||
if cfg.xmrwallets and not 'XMR' in self.coins:
|
||||
|
|
|
|||
49
mmgen/cfg.py
49
mmgen/cfg.py
|
|
@ -56,29 +56,32 @@ class GlobalConstants(Lockable):
|
|||
btc_fork_rpc_coins = ('btc', 'bch', 'ltc')
|
||||
eth_fork_coins = ('eth', 'etc')
|
||||
|
||||
_cc = namedtuple('cmd_cap', ['proto', 'rpc', 'coin', 'caps', 'platforms'])
|
||||
# ‘use_coin_opt’ must be False if ‘coin_codes’ is set
|
||||
_cc = namedtuple('cmd_cap', ['proto', 'rpc', 'use_coin_opt', 'coin_codes', 'caps', 'platforms'])
|
||||
cmd_caps_data = {
|
||||
'addrgen': _cc(True, False, None, [], 'lmw'),
|
||||
'addrimport': _cc(True, True, 'R', ['tw'], 'lmw'),
|
||||
'autosign': _cc(True, True, 'r', ['rpc'], 'lm'),
|
||||
'keygen': _cc(True, False, None, [], 'lmw'),
|
||||
'msg': _cc(True, True, 'R', ['msg'], 'lmw'),
|
||||
'passchg': _cc(False, False, None, [], 'lmw'),
|
||||
'passgen': _cc(False, False, None, [], 'lmw'),
|
||||
'regtest': _cc(True, True, 'b', ['tw'], 'lmw'),
|
||||
'seedjoin': _cc(False, False, None, [], 'lmw'),
|
||||
'seedsplit': _cc(False, False, None, [], 'lmw'),
|
||||
'subwalletgen': _cc(False, False, None, [], 'lmw'),
|
||||
'tool': _cc(True, True, None, [], 'lmw'),
|
||||
'txbump': _cc(True, True, 'R', ['tw'], 'lmw'),
|
||||
'txcreate': _cc(True, True, 'R', ['tw'], 'lmw'),
|
||||
'txdo': _cc(True, True, 'R', ['tw'], 'lmw'),
|
||||
'txsend': _cc(True, True, 'R', ['tw'], 'lmw'),
|
||||
'txsign': _cc(True, True, 'R', ['tw'], 'lmw'),
|
||||
'walletchk': _cc(False, False, None, [], 'lmw'),
|
||||
'walletconv': _cc(False, False, None, [], 'lmw'),
|
||||
'walletgen': _cc(False, False, None, [], 'lmw'),
|
||||
'xmrwallet': _cc(True, True, 'xmr', ['rpc'], 'lmw'),
|
||||
'addrgen': _cc(True, False, True, None, [], 'lmw'),
|
||||
'addrimport': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'autosign': _cc(True, True, False, '-rRb', ['rpc'], 'lm'),
|
||||
'keygen': _cc(True, False, True, None, [], 'lmw'),
|
||||
'msg': _cc(True, True, True, None, ['msg'], 'lmw'),
|
||||
'passchg': _cc(False, False, False, None, [], 'lmw'),
|
||||
'passgen': _cc(False, False, False, None, [], 'lmw'),
|
||||
'regtest': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'seedjoin': _cc(False, False, False, None, [], 'lmw'),
|
||||
'seedsplit': _cc(False, False, False, None, [], 'lmw'),
|
||||
'subwalletgen': _cc(False, False, False, None, [], 'lmw'),
|
||||
'swaptxcreate': _cc(True, True, False, '-rRb', ['tw'], 'lmw'),
|
||||
'swaptxdo': _cc(True, True, False, '-rRb', ['tw'], 'lmw'),
|
||||
'tool': _cc(True, True, True, None, [], 'lmw'),
|
||||
'txbump': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'txcreate': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'txdo': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'txsend': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'txsign': _cc(True, True, True, None, ['tw'], 'lmw'),
|
||||
'walletchk': _cc(False, False, False, None, [], 'lmw'),
|
||||
'walletconv': _cc(False, False, False, None, [], 'lmw'),
|
||||
'walletgen': _cc(False, False, False, None, [], 'lmw'),
|
||||
'xmrwallet': _cc(True, True, False, '-r', ['rpc'], 'lmw'),
|
||||
}
|
||||
|
||||
prog_name = os.path.basename(sys.argv[0])
|
||||
|
|
@ -475,7 +478,7 @@ class Config(Lockable):
|
|||
'_data_dir_root_override',
|
||||
self._uopts.pop('data_dir', None))
|
||||
|
||||
if parse_only and not any(k in self._uopts for k in ['help', 'longhelp']):
|
||||
if parse_only and not any(k in self._uopts for k in ['help', 'longhelp', 'usage']):
|
||||
return
|
||||
|
||||
# Step 2: set cfg from user-supplied data, skipping auto opts; set type from corresponding
|
||||
|
|
|
|||
|
|
@ -140,14 +140,14 @@ class CmdHelp_v2(CmdHelp_v1):
|
|||
def gen_text(self, opts):
|
||||
from ..opts import cmd_opts_v2_help_pat
|
||||
skipping = False
|
||||
coin_filter_codes = opts.global_filter_codes.coin
|
||||
cmd_filter_codes = opts.opts_data['filter_codes']
|
||||
coin_codes = opts.global_filter_codes.coin
|
||||
cmd_codes = opts.opts_data['filter_codes']
|
||||
for line in opts.opts_data['text']['options'][1:].rstrip().splitlines():
|
||||
m = cmd_opts_v2_help_pat.match(line)
|
||||
if m[1] == '+':
|
||||
if not skipping:
|
||||
yield line[6:]
|
||||
elif m[1] in coin_filter_codes and m[2] in cmd_filter_codes:
|
||||
elif (coin_codes is None or m[1] in coin_codes) and m[2] in cmd_codes:
|
||||
yield '{} --{} {}'.format(
|
||||
(f'-{m[3]},', ' ')[m[3] == '-'],
|
||||
m[4],
|
||||
|
|
@ -165,14 +165,14 @@ class GlobalHelp(Help):
|
|||
def gen_text(self, opts):
|
||||
from ..opts import global_opts_help_pat
|
||||
skipping = False
|
||||
coin_filter_codes = opts.global_filter_codes.coin
|
||||
cmd_filter_codes = opts.global_filter_codes.cmd
|
||||
coin_codes = opts.global_filter_codes.coin
|
||||
cmd_codes = opts.global_filter_codes.cmd
|
||||
for line in opts.global_opts_data['text']['options'][1:].rstrip().splitlines():
|
||||
m = global_opts_help_pat.match(line)
|
||||
if m[1] == '+':
|
||||
if not skipping:
|
||||
yield line[4:]
|
||||
elif m[1] in coin_filter_codes and m[2] in cmd_filter_codes:
|
||||
elif (coin_codes is None or m[1] in coin_codes) and (cmd_codes is None or m[2] in cmd_codes):
|
||||
yield ' --{} {}'.format(m[3], m[5]) if m[3] else m[5]
|
||||
skipping = False
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -171,17 +171,22 @@ def parse_opts(cfg, opts_data, global_opts_data, global_filter_codes, need_proto
|
|||
|
||||
def parse_v2():
|
||||
cmd_filter_codes = opts_data['filter_codes']
|
||||
coin_codes = global_filter_codes.coin
|
||||
for line in opts_data['text']['options'].splitlines():
|
||||
m = cmd_opts_v2_pat.match(line)
|
||||
if m and m[1] in global_filter_codes.coin and m[2] in cmd_filter_codes:
|
||||
if m and (coin_codes is None or m[1] in coin_codes) and m[2] in cmd_filter_codes:
|
||||
ret = opt_tuple(m[4].replace('-', '_'), m[5] == '=')
|
||||
yield (m[3], ret)
|
||||
yield (m[4], ret)
|
||||
|
||||
def parse_global():
|
||||
coin_codes = global_filter_codes.coin
|
||||
cmd_codes = global_filter_codes.cmd
|
||||
for line in global_opts_data['text']['options'].splitlines():
|
||||
m = global_opts_pat.match(line)
|
||||
if m and m[1] in global_filter_codes.coin and m[2] in global_filter_codes.cmd:
|
||||
if m and (
|
||||
(coin_codes is None or m[1] in coin_codes) and
|
||||
(cmd_codes is None or m[2] in cmd_codes)):
|
||||
yield (m[3], opt_tuple(m[3].replace('-', '_'), m[4] == '='))
|
||||
|
||||
opts = tuple((parse_v2 if 'filter_codes' in opts_data else parse_v1)()) + tuple(parse_global())
|
||||
|
|
@ -294,7 +299,7 @@ class UserOpts(Opts):
|
|||
'options': """
|
||||
-- --accept-defaults Accept defaults at all prompts
|
||||
hp --cashaddr=0|1 Display addresses in cashaddr format (default: 1)
|
||||
-p --coin=c Choose coin unit. Default: BTC. Current choice: {cu_dfl}
|
||||
-c --coin=c Choose coin unit. Default: BTC. Current choice: {cu_dfl}
|
||||
er --token=t Specify an ERC20 token by address or symbol
|
||||
-- --color=0|1 Disable or enable color output (default: 1)
|
||||
-- --columns=N Force N columns of output with certain commands
|
||||
|
|
@ -351,6 +356,10 @@ class UserOpts(Opts):
|
|||
@staticmethod
|
||||
def get_global_filter_codes(need_proto):
|
||||
"""
|
||||
Enable options based on the value of --coin and name of executable
|
||||
|
||||
Both must produce a matching code list, or None, for the option to be enabled
|
||||
|
||||
Coin codes:
|
||||
'b' - Bitcoin or Bitcoin code fork supporting RPC
|
||||
'R' - Bitcoin or Ethereum code fork supporting RPC
|
||||
|
|
@ -360,26 +369,26 @@ class UserOpts(Opts):
|
|||
'-' - other coin
|
||||
Cmd codes:
|
||||
'p' - proto required
|
||||
'c' - proto required, --coin recognized
|
||||
'r' - RPC required
|
||||
'-' - no capabilities required
|
||||
"""
|
||||
ret = namedtuple('global_filter_codes', ['coin', 'cmd'])
|
||||
if caps := gc.cmd_caps:
|
||||
coin = caps.coin if caps.coin and len(caps.coin) > 1 else get_coin()
|
||||
coin = get_coin() if caps.use_coin_opt else None
|
||||
# a return value of None removes the filter, enabling all options for the given criterion
|
||||
return ret(
|
||||
coin = (
|
||||
('-', 'r', 'R', 'b', 'h') if coin == 'bch' else
|
||||
('-', 'r', 'R', 'b') if coin in gc.btc_fork_rpc_coins else
|
||||
('-', 'r', 'R', 'e') if coin in gc.eth_fork_coins else
|
||||
('-', 'r') if coin in gc.rpc_coins else
|
||||
('-')),
|
||||
coin = caps.coin_codes or (
|
||||
None if coin is None else
|
||||
['-', 'r', 'R', 'b', 'h'] if coin == 'bch' else
|
||||
['-', 'r', 'R', 'b'] if coin in gc.btc_fork_rpc_coins else
|
||||
['-', 'r', 'R', 'e'] if coin in gc.eth_fork_coins else
|
||||
['-', 'r'] if coin in gc.rpc_coins else
|
||||
['-']),
|
||||
cmd = (
|
||||
['-']
|
||||
+ (['r'] if caps.rpc else [])
|
||||
+ (['p'] if caps.proto else [])
|
||||
+ (['p', 'c'] if caps.proto and caps.use_coin_opt else ['p'] if caps.proto else [])
|
||||
))
|
||||
else:
|
||||
return ret(
|
||||
coin = ('-', 'r', 'R', 'b', 'h', 'e'),
|
||||
cmd = ('-', 'r', 'p')
|
||||
)
|
||||
else: # unmanaged command: enable everything
|
||||
return ret(None, None)
|
||||
|
|
|
|||
|
|
@ -41,26 +41,27 @@ class CmdTestHelp(CmdTestBase):
|
|||
)
|
||||
|
||||
def usage1(self):
|
||||
t = self.spawn('mmgen-walletgen', ['--usage'], no_passthru_opts=True)
|
||||
t.expect('USAGE: mmgen-walletgen')
|
||||
return t
|
||||
return self._usage('walletgen', ['--usage'], True, False, 0)
|
||||
|
||||
def usage2(self):
|
||||
cmd = 'xmrwallet' if self.coin == 'xmr' else 'txcreate'
|
||||
t = self.spawn(f'mmgen-{cmd}', ['--usage', f'--coin={self.coin}'], no_passthru_opts=True)
|
||||
t.expect(f'USAGE: mmgen-{cmd}')
|
||||
return t
|
||||
return self._usage('tool' if self.coin == 'xmr' else 'txcreate', ['--usage'], True, True, 0)
|
||||
|
||||
def usage3(self):
|
||||
t = self.spawn('mmgen-walletgen', ['foo'], exit_val=1, no_passthru_opts=True)
|
||||
t.expect('USAGE: mmgen-walletgen')
|
||||
return t
|
||||
return self._usage('walletgen', ['foo'], True, False, 1)
|
||||
|
||||
def usage4(self):
|
||||
cmd = 'xmrwallet' if self.coin == 'xmr' else 'addrgen'
|
||||
t = self.spawn(f'mmgen-{cmd}', [f'--coin={self.coin}'], exit_val=1, no_passthru_opts=True)
|
||||
t.expect(f'USAGE: mmgen-{cmd}')
|
||||
return t
|
||||
return self._usage('tool' if self.coin == 'xmr' else 'addrgen', [], True, True, 1)
|
||||
|
||||
def _usage(self, cmd_arg, args, no_passthru_opts, add_coin_opt, exit_val):
|
||||
if cmd := (None if self._gen_skiplist(cmd_arg) else cmd_arg):
|
||||
t = self.spawn(
|
||||
f'mmgen-{cmd}',
|
||||
([f'--coin={self.coin}'] if add_coin_opt else []) + args,
|
||||
exit_val = exit_val,
|
||||
no_passthru_opts = no_passthru_opts)
|
||||
t.expect(f'USAGE: mmgen-{cmd}')
|
||||
return t
|
||||
return 'skip'
|
||||
|
||||
def version(self):
|
||||
t = self.spawn('mmgen-tool', ['--version'], exit_val=0)
|
||||
|
|
@ -97,29 +98,37 @@ class CmdTestHelp(CmdTestBase):
|
|||
t.skip_ok = True
|
||||
return t
|
||||
|
||||
def _gen_skiplist(self, scripts):
|
||||
def gen(scripts):
|
||||
if isinstance(scripts, str):
|
||||
scripts = [scripts]
|
||||
for script in scripts:
|
||||
d = gc.cmd_caps_data[script]
|
||||
if sys.platform == 'win32' and 'w' not in d.platforms:
|
||||
yield script
|
||||
elif not (d.use_coin_opt or self.proto.coin.lower() == 'btc'):
|
||||
yield script
|
||||
else:
|
||||
for cap in d.caps:
|
||||
if cap not in self.proto.mmcaps:
|
||||
yield script
|
||||
break
|
||||
return set(gen(scripts))
|
||||
|
||||
def helpscreens(self, arg='--help', scripts=(), expect='USAGE:.*OPTIONS:', pager=True):
|
||||
|
||||
scripts = list(scripts or gc.cmd_caps_data)
|
||||
|
||||
def gen_skiplist():
|
||||
for script in scripts:
|
||||
d = gc.cmd_caps_data[script]
|
||||
for cap in d.caps:
|
||||
if cap not in self.proto.mmcaps:
|
||||
yield script
|
||||
break
|
||||
else:
|
||||
if sys.platform == 'win32' and 'w' not in d.platforms:
|
||||
yield script
|
||||
elif d.coin and len(d.coin) > 1 and self.proto.coin.lower() not in (d.coin, 'btc'):
|
||||
yield script
|
||||
cmdlist = sorted(set(scripts) - self._gen_skiplist(scripts))
|
||||
|
||||
for cmdname in sorted(set(scripts) - set(list(gen_skiplist()))):
|
||||
for cmdname in cmdlist:
|
||||
cmd_caps = gc.cmd_caps_data[cmdname]
|
||||
assert cmd_caps, cmdname
|
||||
t = self.spawn(
|
||||
f'mmgen-{cmdname}',
|
||||
[arg],
|
||||
extra_desc = f'(mmgen-{cmdname})',
|
||||
no_passthru_opts = not gc.cmd_caps_data[cmdname].proto)
|
||||
no_passthru_opts = not cmd_caps.use_coin_opt)
|
||||
t.expect(expect, regex=True)
|
||||
if pager and t.pexpect_spawn:
|
||||
time.sleep(0.2)
|
||||
|
|
@ -128,7 +137,7 @@ class CmdTestHelp(CmdTestBase):
|
|||
t.ok()
|
||||
t.skip_ok = True
|
||||
|
||||
return t
|
||||
return 'silent'
|
||||
|
||||
def longhelpscreens(self):
|
||||
return self.helpscreens(arg='--longhelp', expect='USAGE:.*GLOBAL OPTIONS:')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue