diff --git a/mmgen/cfg.py b/mmgen/cfg.py index 8a42a4d0..c4215093 100755 --- a/mmgen/cfg.py +++ b/mmgen/cfg.py @@ -543,6 +543,11 @@ class Config(Lockable): # Check user-set opts without modifying them check_opts(self) + def _usage(self): + from .help import make_usage_str + print(make_usage_str(self, caller='user')) + sys.exit(1) # called only on bad invocation + def _set_cfg_from_env(self): for name,val in ((k,v) for k,v in os.environ.items() if k.startswith('MMGEN_')): if name == 'MMGEN_DEBUG_ALL': diff --git a/mmgen/fileutil.py b/mmgen/fileutil.py index be8beaa7..61ceb101 100755 --- a/mmgen/fileutil.py +++ b/mmgen/fileutil.py @@ -129,9 +129,9 @@ def get_seed_file(cfg,nargs,wallets=None,invoked_as=None): if len(wallets) + (wd_from_opt or bool(wf)) < nargs: if not wf: msg('No default wallet found, and no other seed source was specified') - cfg._opts.usage() + cfg._usage() elif len(wallets) > nargs: - cfg._opts.usage() + cfg._usage() elif len(wallets) == nargs and wf and invoked_as != 'gen': cfg._util.qmsg('Warning: overriding default wallet with user-supplied wallet') diff --git a/mmgen/help/__init__.py b/mmgen/help/__init__.py index 4c378df9..2996b7e7 100755 --- a/mmgen/help/__init__.py +++ b/mmgen/help/__init__.py @@ -20,8 +20,46 @@ help: help notes for MMGen suite commands """ +import sys + from ..cfg import gc +def version(cfg): + from ..util import fmt + print(fmt(f""" + {gc.prog_name.upper()} version {gc.version} + Part of {gc.proj_name} Wallet, an online/offline cryptocurrency wallet for the + command line. Copyright (C){gc.Cdates} {gc.author} {gc.email} + """, indent=' ').rstrip()) + sys.exit(0) + +def show_hash_presets(cfg): + fs = ' {:<6} {:<3} {:<2} {}' + from ..util import msg + from ..crypto import Crypto + msg(' Available parameters for scrypt.hash():') + msg(fs.format('Preset', 'N', 'r', 'p')) + for i in sorted(Crypto.hash_presets.keys()): + msg(fs.format(i, *Crypto.hash_presets[i])) + msg(' N = memory usage (power of two)\n p = iterations (rounds)') + sys.exit(0) + +def make_usage_str(cfg, caller): + indent, col1_w = { + 'help': (2, len(gc.prog_name) + 1), + 'user': (0, len('USAGE:')), + }[caller] + def gen(): + ulbl = 'USAGE:' + for line in [cfg._usage_data.strip()] if isinstance(cfg._usage_data, str) else cfg._usage_data: + yield f'{ulbl:{col1_w}} {gc.prog_name} {line}' + ulbl = '' + return ('\n' + (' ' * indent)).join(gen()) + +def usage(cfg): + print(make_usage_str(cfg, caller='user')) + sys.exit(0) + def help_notes_func(proto,cfg,k): def fee_spec_letters(use_quotes=False): diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 193267fb..614cf98b 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -134,7 +134,7 @@ addr_type = MMGenAddrType( errmsg = f'{cfg.type!r}: invalid parameter for --type option' ) if len(cfg._args) < 1: - cfg._opts.usage() + cfg._usage() if cfg.keygen_backend: from .keygen import check_backend diff --git a/mmgen/main_autosign.py b/mmgen/main_autosign.py index 7515e5f9..017b202a 100755 --- a/mmgen/main_autosign.py +++ b/mmgen/main_autosign.py @@ -194,7 +194,7 @@ cfg = Config( }, do_post_init = True ) -cmd = cfg._args[0] if len(cfg._args) == 1 else 'sign' if not cfg._args else cfg._opts.usage() +cmd = cfg._args[0] if len(cfg._args) == 1 else 'sign' if not cfg._args else cfg._usage() if cmd not in Autosign.cmds + Autosign.util_cmds: die(1,f'‘{cmd}’: unrecognized command') diff --git a/mmgen/main_msg.py b/mmgen/main_msg.py index d1cc0c0c..83b1ba27 100755 --- a/mmgen/main_msg.py +++ b/mmgen/main_msg.py @@ -207,7 +207,7 @@ cfg = Config( opts_data=opts_data, need_amt=False ) cmd_args = cfg._args if len(cmd_args) < 2: - cfg._opts.usage() + cfg._usage() op = cmd_args.pop(0) @@ -217,15 +217,15 @@ if cfg.msghash_type and op != 'create': async def main(): if op == 'create': if len(cmd_args) < 2: - cfg._opts.usage() + cfg._usage() MsgOps.create( cmd_args[0], ' '.join(cmd_args[1:]) ) elif op == 'sign': if len(cmd_args) < 1: - cfg._opts.usage() + cfg._usage() await MsgOps.sign( cmd_args[0], cmd_args[1:] ) elif op in ('verify','export'): if len(cmd_args) not in (1,2): - cfg._opts.usage() + cfg._usage() await getattr(MsgOps,op)( cmd_args[0], cmd_args[1] if len(cmd_args) == 2 else None ) else: die(1,f'{op!r}: unrecognized operation') diff --git a/mmgen/main_passgen.py b/mmgen/main_passgen.py index c609f211..945a4270 100755 --- a/mmgen/main_passgen.py +++ b/mmgen/main_passgen.py @@ -138,7 +138,7 @@ FMT CODES: cfg = Config(opts_data=opts_data) if len(cfg._args) < 2: - cfg._opts.usage() + cfg._usage() pw_idxs = AddrIdxList(fmt_str=cfg._args.pop()) diff --git a/mmgen/main_regtest.py b/mmgen/main_regtest.py index b39f4670..d870f223 100755 --- a/mmgen/main_regtest.py +++ b/mmgen/main_regtest.py @@ -77,7 +77,7 @@ def check_num_args(): die(1,m.format(args,'many','more',amax)) if not cmd_args: - cfg._opts.usage() + cfg._usage() elif cmd_args[0] not in MMGenRegtest.usr_cmds: die(1,f'{cmd_args[0]!r}: invalid command') elif cmd_args[0] not in ('cli','wallet_cli','balances'): diff --git a/mmgen/main_seedjoin.py b/mmgen/main_seedjoin.py index 55be82f1..b3b7d358 100755 --- a/mmgen/main_seedjoin.py +++ b/mmgen/main_seedjoin.py @@ -111,7 +111,7 @@ def print_shares_info(): cfg = Config(opts_data=opts_data) if len(cfg._args) + bool(cfg.hidden_incog_input_params) < 2: - cfg._opts.usage() + cfg._usage() if cfg.master_share: master_idx = MasterShareIdx(cfg.master_share) diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 52783ea5..c178eda1 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -359,7 +359,7 @@ if gc.prog_name.endswith('-tool'): sys.exit(0) if len(po.cmd_args) < 1: - cfg._opts.usage() + cfg._usage() cmd = po.cmd_args[0] diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index 0ca01da4..b3abd060 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -78,7 +78,7 @@ elif not cfg._args and cfg.autosign: infile = si.get_unsent() cfg._util.qmsg(f'Got signed transaction file ‘{infile}’') else: - cfg._opts.usage() + cfg._usage() if not cfg.status: from .ui import do_license_msg diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index cec5e6d7..1aff07b0 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -102,7 +102,7 @@ cfg = Config(opts_data=opts_data) infiles = cfg._args if not infiles: - cfg._opts.usage() + cfg._usage() from .fileutil import check_infile for i in infiles: diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index 422f5a7c..9ee5bf23 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -166,17 +166,17 @@ elif invoked_as == 'seedsplit': m2 = 'To generate a master share, omit the seed split specifier.' die(1,m1+' '+m2) elif not sss: - cfg._opts.usage() + cfg._usage() elif master_share: sss = SeedSplitSpecifier('1:2') else: - cfg._opts.usage() + cfg._usage() from .fileutil import check_infile,get_seed_file if cmd_args: if invoked_as == 'gen' or len(cmd_args) > 1: - cfg._opts.usage() + cfg._usage() check_infile(cmd_args[0]) sf = get_seed_file(cfg,nargs,invoked_as=invoked_as) diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index a57c47e2..cbe88a0e 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -128,7 +128,7 @@ if cmd_args and cfg.autosign and ( cmd_args.insert(1,None) if len(cmd_args) < 2: - cfg._opts.usage() + cfg._usage() op = cmd_args.pop(0) infile = cmd_args.pop(0) @@ -136,22 +136,22 @@ wallets = spec = None if op in ('relay', 'submit', 'resubmit', 'abort'): if len(cmd_args) != 0: - cfg._opts.usage() + cfg._usage() elif op in ('txview','txlist'): infile = [infile] + cmd_args elif op in ('create','sync','list','view','listview','dump','restore'): # kafile_arg_ops if len(cmd_args) > 1: - cfg._opts.usage() + cfg._usage() wallets = cmd_args.pop(0) if cmd_args else None elif op in ('new', 'transfer', 'sweep', 'sweep_all', 'label'): if len(cmd_args) != 1: - cfg._opts.usage() + cfg._usage() spec = cmd_args[0] elif op in ('export-outputs', 'export-outputs-sign', 'import-key-images'): if not cfg.autosign: # --autosign only for now - TODO die(f'--autosign must be used with command {op!r}') if len(cmd_args) > 1: - cfg._opts.usage() + cfg._usage() wallets = cmd_args.pop(0) if cmd_args else None else: die(1,f'{op!r}: unrecognized operation') diff --git a/mmgen/opts.py b/mmgen/opts.py index 0356dafa..ed5f0fba 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -66,6 +66,7 @@ long_opts_data = { --, --tw-name=NAME Specify alternate name for the BTC/LTC/BCH tracking wallet (default: ‘{tw_name}’) --, --skip-cfg-file Skip reading the configuration file +--, --usage Print usage information and exit --, --version Print version information and exit --, --bob Specify user “Bob” in MMGen regtest mode --, --alice Specify user “Alice” in MMGen regtest mode @@ -105,9 +106,6 @@ class UserOpts: od['text']['long_options'] = long_opts_data['text'] - # Make this available to usage() - self.usage_data = od['text'].get('usage2') or od['text']['usage'] - # po: (user_opts,cmd_args,opts,filtered_opts) po = parsed_opts or Opts.parse_opts(od,opt_filter=opt_filter) @@ -123,15 +121,16 @@ class UserOpts: cfg._parsed_opts = po cfg._use_env = True cfg._use_cfg_file = not 'skip_cfg_file' in uopts + # Make this available to usage() + cfg._usage_data = opts_data['text'].get('usage2') or opts_data['text']['usage'] if os.getenv('MMGEN_DEBUG_OPTS'): opt_preproc_debug(po) - if 'version' in uopts: - self.version() # exits - - if 'show_hash_presets' in uopts: - self.show_hash_presets() # exits + for funcname in ('usage', 'version', 'show_hash_presets'): + if funcname in uopts: + import importlib + getattr(importlib.import_module('mmgen.help'), funcname)(cfg) # exits if parse_only: return diff --git a/scripts/create-bip-hd-chain-params.py b/scripts/create-bip-hd-chain-params.py index 6fcff636..af4d5d44 100755 --- a/scripts/create-bip-hd-chain-params.py +++ b/scripts/create-bip-hd-chain-params.py @@ -100,7 +100,7 @@ def main(): global slip44_data, bip_utils_data if len(cfg._args) != 1: - cfg._opts.usage() + cfg._usage() with open(cfg._args[0]) as fh: slip44_data = json.loads(fh.read()) diff --git a/scripts/create-token.py b/scripts/create-token.py index 0bbc09f4..8c6680db 100755 --- a/scripts/create-token.py +++ b/scripts/create-token.py @@ -275,7 +275,7 @@ def main(): die(1,'--coin option must be ETH or ETC') if not len(cfg._args) == 1: - cfg._opts.usage() + cfg._usage() code = create_src( cfg, solidity_code_template, token_data ) diff --git a/scripts/tx-v2-to-v3.py b/scripts/tx-v2-to-v3.py index f900fc61..642c1b41 100755 --- a/scripts/tx-v2-to-v3.py +++ b/scripts/tx-v2-to-v3.py @@ -29,7 +29,7 @@ opts_data = { cfg = Config(opts_data=opts_data) if len(cfg._args) != 1: - cfg._opts.usage() + cfg._usage() tx = asyncio.run(CompletedTX(cfg._args[0],quiet_open=True)) tx.file.write(ask_tty=False,ask_overwrite=not cfg.quiet,ask_write=not cfg.quiet) diff --git a/test/cmdtest_py_d/ct_help.py b/test/cmdtest_py_d/ct_help.py index 69a181aa..a70d6401 100755 --- a/test/cmdtest_py_d/ct_help.py +++ b/test/cmdtest_py_d/ct_help.py @@ -25,7 +25,8 @@ class CmdTestHelp(CmdTestBase): networks = ('btc', 'ltc', 'bch', 'eth', 'xmr', 'doge') passthru_opts = ('daemon_data_dir','rpc_port','coin','testnet') cmd_group = ( - ('usage', (1,'usage message (via bad invocation)',[])), + ('usage1', (1,'usage message (via --usage)',[])), + ('usage2', (1,'usage message (via bad invocation)',[])), ('version', (1,'version message',[])), ('license', (1,'license message',[])), ('helpscreens', (1,'help screens', [])), @@ -37,7 +38,12 @@ class CmdTestHelp(CmdTestBase): ('tooltest_help', (1,"'tooltest.py' help screens",[])), ) - def usage(self): + def usage1(self): + t = self.spawn('mmgen-txsend', ['--usage'], no_passthru_opts=True) + t.expect('USAGE: mmgen-txsend') + return t + + def usage2(self): t = self.spawn('mmgen-walletgen', ['foo'], exit_val=1, no_passthru_opts=True) t.expect('USAGE: mmgen-walletgen') return t diff --git a/test/gentest.py b/test/gentest.py index 7ad6a16f..82e17971 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -479,7 +479,7 @@ def get_protos(proto,addr_type,toolname): def parse_args(): if len(cfg._args) != 2: - cfg._opts.usage() + cfg._usage() arg1,arg2 = cfg._args gen1,gen2,rounds = (0,0,0) diff --git a/test/include/coin_daemon_control.py b/test/include/coin_daemon_control.py index b09f7ec8..ee902e03 100755 --- a/test/include/coin_daemon_control.py +++ b/test/include/coin_daemon_control.py @@ -140,7 +140,7 @@ def main(): ids = cfg._args network_ids = CoinDaemon.get_network_ids(cfg) if not ids: - cfg._opts.usage() + cfg._usage() for i in ids: if i not in network_ids + list(xmr_wallet_network_ids): die(1,f'{i!r}: invalid network ID')