diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 42a04372..f89125ab 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -41,8 +41,7 @@ note_secp256k1 = """ If available, the secp256k1 library will be used for address generation. """.strip() - -opts_data = { +def opts_data(): return { 'sets': [('print_checksum',True,'quiet',True)], 'desc': """Generate a range or list of {desc} from an {pnm} wallet, mnemonic, seed or brainwallet""".format(desc=gen_desc,pnm=g.proj_name), diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index f7909b84..e5e90ca3 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -29,7 +29,7 @@ from mmgen.obj import TwLabel # In batch mode, bitcoind just rescans each address separately anyway, so make # --batch and --rescan incompatible. -opts_data = { +def opts_data(): return { 'desc': """Import addresses (both {pnm} and non-{pnm}) into an {pnm} tracking wallet""".format(pnm=g.proj_name), 'usage':'[opts] [mmgen address file]', diff --git a/mmgen/main_passgen.py b/mmgen/main_passgen.py index 1776ff5b..c0e6a8bd 100755 --- a/mmgen/main_passgen.py +++ b/mmgen/main_passgen.py @@ -32,7 +32,7 @@ dfl_len = { 'b32': PasswordList.pw_info['b32']['dfl_len'] } -opts_data = { +def opts_data(): return { 'sets': [('print_checksum',True,'quiet',True)], 'desc': """Generate a range or list of passwords from an {pnm} wallet, mnemonic, seed or brainwallet for the given ID string""".format(pnm=g.proj_name), diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index c8f908fd..8f847c8b 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -24,7 +24,7 @@ mmgen-tool: Perform various MMGen- and Bitcoin-related operations. from mmgen.common import * import mmgen.tool as tool -opts_data = { +def opts_data(): return { 'desc': 'Perform various {pnm}- and Bitcoin-related operations'.format(pnm=g.proj_name), 'usage': '[opts] ', 'options': """ diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index 7e69a071..6517b1b7 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -24,7 +24,7 @@ mmgen-txbump: Increase the fee on a replaceable (replace-by-fee) MMGen from mmgen.txcreate import * from mmgen.txsign import * -opts_data = { +def opts_data(): return { 'desc': 'Increase the fee on a replaceable (RBF) {g.proj_name} transaction, creating a new transaction, and optionally sign and send the new transaction'.format(g=g), 'usage': '[opts] <{g.proj_name} TX file> [seed source] ...'.format(g=g), 'sets': ( ('yes', True, 'quiet', True), ), @@ -70,7 +70,7 @@ opts_data = { kg=g.key_generator, cu=g.coin ), - 'notes': '\n' + fee_notes + txsign_notes + 'notes': '\n' + fee_notes.format(g.coin) + txsign_notes } cmd_args = opts.init(opts_data) diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 46b136e9..b7530a4e 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -23,7 +23,7 @@ mmgen-txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen from mmgen.txcreate import * -opts_data = { +def opts_data(): return { 'desc': 'Create a transaction with outputs to specified Bitcoin or {g.proj_name} addresses'.format(g=g), 'usage': '[opts] ... [change addr] [addr file] ...', 'sets': ( ('yes', True, 'quiet', True), ), @@ -46,7 +46,7 @@ opts_data = { -v, --verbose Produce more verbose output -y, --yes Answer 'yes' to prompts, suppress non-essential output """.format(g=g,cu=g.coin), - 'notes': '\n' + txcreate_notes + fee_notes + 'notes': '\n' + txcreate_notes + fee_notes.format(g.coin) } cmd_args = opts.init(opts_data) diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index 5bce88e9..81c994a1 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -23,7 +23,7 @@ mmgen-txdo: Create, sign and broadcast an online MMGen transaction from mmgen.txcreate import * from mmgen.txsign import * -opts_data = { +def opts_data(): return { 'desc': 'Create, sign and send an {g.proj_name} transaction'.format(g=g), 'usage': '[opts] ... [change addr] [addr file] ... [seed source] ...', 'sets': ( ('yes', True, 'quiet', True), ), @@ -74,7 +74,7 @@ opts_data = { kg=g.key_generator, cu=g.coin ), - 'notes': '\n' + txcreate_notes + fee_notes + txsign_notes + 'notes': '\n' + txcreate_notes + fee_notes.format(g.coin) + txsign_notes } cmd_args = opts.init(opts_data) diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index e36b06e3..b1ee2d6a 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -23,7 +23,7 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network from mmgen.common import * from mmgen.tx import * -opts_data = { +def opts_data(): return { 'desc': 'Send a Bitcoin transaction signed by {pnm}-txsign'.format( pnm=g.proj_name.lower()), 'usage': '[opts] ', diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index e3c3a6b0..ad2aad37 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -23,7 +23,7 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate' from mmgen.txsign import * # -w, --use-wallet-dat (keys from running bitcoind) removed: use bitcoin-cli walletdump instead -opts_data = { +def opts_data(): return { 'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()), 'usage': '[opts] ... [seed source]...', 'sets': ( ('yes', True, 'quiet', True), ), diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index 8970e81b..2731ca90 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -55,7 +55,7 @@ elif invoked_as == 'passchg': else: die(1,"'%s': unrecognized invocation" % g.prog_name) -opts_data = { +def opts_data(): return { # Can't use: share/Opts doesn't know anything about fmt codes # 'sets': [('hidden_incog_output_params',bool,'out_fmt','hi')], 'desc': desc.format(pnm=g.proj_name), diff --git a/mmgen/opts.py b/mmgen/opts.py index 24e5348e..aca8fd74 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -166,7 +166,10 @@ def override_from_env(): gname = name[idx:].lower() setattr(g,gname,set_for_type(val,getattr(g,gname),name,invert_bool)) -def init(opts_data,add_opts=[],opt_filter=None): +def init(opts_f,add_opts=[],opt_filter=None): + + opts_data = opts_f() + opts_data['long_options'] = common_opts_data version_info = """ {pgnm_uc} version {g.version} @@ -174,10 +177,8 @@ def init(opts_data,add_opts=[],opt_filter=None): Copyright (C) {g.Cdates} {g.author} {g.email} """.format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip() - opts_data['long_options'] = common_opts_data - - uopts,args,short_opts,long_opts,skipped_opts = \ - mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter) + uopts,args,short_opts,long_opts,skipped_opts,do_help = \ + mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter,defer_help=True) if g.debug: opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args) @@ -185,11 +186,6 @@ def init(opts_data,add_opts=[],opt_filter=None): global usage_txt usage_txt = opts_data['usage'] - # We don't need this data anymore - del mmgen.share.Opts - for k in ('prog_name','desc','usage','options','notes'): - if k in opts_data: del opts_data[k] - # Transfer uopts into opt, setting program's opts + required opts to None if not set by user for o in tuple([s.rstrip('=') for s in long_opts] + add_opts + skipped_opts) + \ g.required_opts + g.common_opts: @@ -243,6 +239,17 @@ def init(opts_data,add_opts=[],opt_filter=None): opt_postproc_initializations() + if do_help: # print help screen only after global vars are initialized + opts_data = opts_f() + opts_data['long_options'] = common_opts_data + mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter) + + # We don't need this data anymore + del mmgen.share.Opts + del opts_f + for k in ('prog_name','desc','usage','options','notes'): + if k in opts_data: del opts_data[k] + if g.debug: opt_postproc_debug() return args diff --git a/mmgen/rpc.py b/mmgen/rpc.py index c30e4a5d..6080f064 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -75,7 +75,7 @@ class BitcoinRPCConnection(object): if cf['on_fail'] in ('return','silent'): return 'rpcfail',args else: - die(*args[1:]) + die(args[1],yellow(args[2])) dmsg('=== request() debug ===') dmsg(' RPC POST data ==> %s\n' % p) @@ -86,10 +86,10 @@ class BitcoinRPCConnection(object): return (float,str)[g.bitcoind_version>=120000](obj) return json.JSONEncoder.default(self, obj) -# Can't do UTF-8 labels yet: httplib only ascii? -# if type(p) != list and p['method'] == 'importaddress': -# dump = json.dumps(p,cls=MyJSONEncoder,ensure_ascii=False) -# print(dump) + # TODO: UTF-8 labels + # if type(p) != list and p['method'] == 'importaddress': + # dump = json.dumps(p,cls=MyJSONEncoder,ensure_ascii=False) + # print(dump) dmsg(' RPC AUTHORIZATION data ==> [Basic {}]\n'.format(base64.b64encode(self.auth_str))) try: @@ -98,9 +98,14 @@ class BitcoinRPCConnection(object): 'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str)) }) except Exception as e: - return die_maybe(None,2,'{}\nUnable to connect to bitcoind at {}:{}'.format(e,self.host,self.port)) + m = '{}\nUnable to connect to bitcoind at {}:{}' + return die_maybe(None,2,m.format(e,self.host,self.port)) - r = hc.getresponse() # returns HTTPResponse instance + try: + r = hc.getresponse() # returns HTTPResponse instance + except Exception: + m = 'Unable to connect to bitcoind at {}:{} (but port is bound?)' + return die_maybe(None,2,m.format(self.host,self.port)) dmsg(' RPC GETRESPONSE data ==> %s\n' % r.__dict__) diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index 431d5772..ff9245a2 100755 --- a/mmgen/share/Opts.py +++ b/mmgen/share/Opts.py @@ -27,19 +27,22 @@ def usage(opts_data): print 'USAGE: %s %s' % (opts_data['prog_name'], opts_data['usage']) sys.exit(2) -def print_help(opts_data,longhelp=False): +def print_help_and_exit(opts_data,longhelp=False): pn = opts_data['prog_name'] pn_len = str(len(pn)+2) - print (' %-'+pn_len+'s %s') % (pn.upper()+':', opts_data['desc'].strip()) - print (' %-'+pn_len+'s %s %s')%('USAGE:', pn, opts_data['usage'].strip()) + out = ' {:<{p}} {}\n'.format(pn.upper()+':',opts_data['desc'].strip(),p=pn_len) + out += ' {:<{p}} {} {}\n'.format('USAGE:',pn,opts_data['usage'].strip(),p=pn_len) od_opts = opts_data[('options','long_options')[longhelp]].strip().splitlines() - sep,m,ls = (('\n ',' OPTIONS:',''),('\n',' LONG OPTIONS:',' '))[longhelp] - print m + sep + ls + sep.join(od_opts) + hdr = ('OPTIONS:',' LONG OPTIONS:')[longhelp] + ls = (' ','')[longhelp] + es = ('',' ')[longhelp] + out += '{ls}{}\n{ls}{es}{}\n'.format(hdr,('\n'+ls).join(od_opts),ls=ls,es=es) if 'notes' in opts_data and not longhelp: - print ' ' + '\n '.join(opts_data['notes'][1:-1].splitlines()) + out += ' ' + '\n '.join(opts_data['notes'][1:-1].splitlines()) + print(out) + sys.exit(0) - -def process_opts(argv,opts_data,short_opts,long_opts): +def process_opts(argv,opts_data,short_opts,long_opts,defer_help=False): import os opts_data['prog_name'] = os.path.basename(sys.argv[0]) @@ -51,11 +54,15 @@ def process_opts(argv,opts_data,short_opts,long_opts): print str(err); sys.exit(2) sopts_list = ':_'.join(['_'.join(list(i)) for i in short_opts.split(':')]).split('_') - opts = {} + opts,do_help = {},False - for opt, arg in cl_opts: - if opt in ('-h','--help'): print_help(opts_data); sys.exit(0) - elif opt == '--longhelp': print_help(opts_data,longhelp=True); sys.exit(0) + for opt,arg in cl_opts: + if opt in ('-h','--help'): + if not defer_help: print_help_and_exit(opts_data) + do_help = True + elif opt == '--longhelp': + if not defer_help: print_help_and_exit(opts_data,longhelp=True) + do_help = True elif opt[:2] == '--' and opt[2:] in long_opts: opts[opt[2:].replace('-','_')] = True elif opt[:2] == '--' and opt[2:]+'=' in long_opts: @@ -82,10 +89,10 @@ def process_opts(argv,opts_data,short_opts,long_opts): else: opts[o_out] = v_out - return opts,args + return opts,args,do_help -def parse_opts(argv,opts_data,opt_filter=None): +def parse_opts(argv,opts_data,opt_filter=None,defer_help=False): import re pat = r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)' @@ -111,6 +118,7 @@ def parse_opts(argv,opts_data,opt_filter=None): long_opts = [d[1].replace('-','_')+d[5] for d in od_all if d[6] == False] skipped_opts = [d[1].replace('-','_') for d in od_all if d[6] == True] - opts,args = process_opts(argv,opts_data,short_opts,long_opts) + opts,args,do_help = process_opts(argv,opts_data,short_opts,long_opts,defer_help=defer_help) - return opts,args,short_opts,long_opts,skipped_opts + ret = opts,args,short_opts,long_opts,skipped_opts + return ret + (do_help,) if defer_help else ret diff --git a/mmgen/txcreate.py b/mmgen/txcreate.py index ec924472..efecd39b 100755 --- a/mmgen/txcreate.py +++ b/mmgen/txcreate.py @@ -54,7 +54,7 @@ FEE SPECIFICATION: Transaction fees, both on the command line and at the interactive prompt, may be specified as either absolute {} amounts, using a plain decimal number, or as satoshis per byte, using an integer followed by the letter 's'. -""".format(g.coin) +""" # formatted later, after g.coin is initialized wmsg = { 'addr_in_addrfile_only': """ diff --git a/test/gentest.py b/test/gentest.py index 95b7fd97..5334f36c 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -32,7 +32,7 @@ from mmgen.common import * from mmgen.bitcoin import hex2wif rounds = 100 -opts_data = { +def opts_data(): return { 'desc': "Test address generation in various ways", 'usage':'[options] [spec] [rounds | dump file]', 'options': """ diff --git a/test/mmgen_pexpect.py b/test/mmgen_pexpect.py index fc7fa74d..980a1450 100755 --- a/test/mmgen_pexpect.py +++ b/test/mmgen_pexpect.py @@ -88,10 +88,9 @@ class MMGenPexpect(object): NL = '\n' data_dir = os.path.join('test','data_dir') - add_spawn_args = ' '.join(['{} {}'.format( - '--'+k.replace('_','-'), + add_spawn_args = ' '.join(['{} {}'.format('--'+k.replace('_','-'), getattr(opt,k) if getattr(opt,k) != True else '' - ) for k in ('testnet','rpc_host','rpc_port','regtest') if getattr(opt,k)]).split() + ) for k in ('testnet','rpc_host','rpc_port','regtest','coin') if getattr(opt,k)]).split() add_spawn_args += ['--data-dir',data_dir] def __init__(self,name,mmgen_cmd,cmd_args,desc,no_output=False): diff --git a/test/test.py b/test/test.py index 6444523b..8dbf7d5e 100755 --- a/test/test.py +++ b/test/test.py @@ -100,7 +100,7 @@ if not any(e in ('--skip-deps','--resume','-S','-r') for e in sys.argv+shortopts except: pass os.symlink(dd,data_dir) -opts_data = { +def opts_data(): return { 'desc': 'Test suite for the MMGen suite', 'usage':'[options] [command(s) or metacommand(s)]', 'options': """ @@ -1035,7 +1035,7 @@ class MMGenTestSuite(object): def helpscreens(self,name,arg='--help'): scripts = ( - 'walletgen','walletconv','walletchk','txcreate','txsend','txsign', + 'walletgen','walletconv','walletchk','txcreate','txsign','txsend','txdo','txbump', 'addrgen','addrimport','keygen','passchg','tool','passgen') for s in scripts: t = MMGenExpect(name,('mmgen-'+s),[arg],extra_desc='(mmgen-%s)'%s,no_output=True) diff --git a/test/tooltest.py b/test/tooltest.py index 76a1bbc5..e111dd6a 100755 --- a/test/tooltest.py +++ b/test/tooltest.py @@ -115,7 +115,7 @@ cfg = { 'addrfile_chk': '6FEF 6FB9 7B13 5D91', } -opts_data = { +def opts_data(): return { 'desc': "Test suite for the 'mmgen-tool' utility", 'usage':'[options] [command]', 'options': """