Browse Source

[opts]: separate code from text in opts_data

- When parsing opts, opts.init() now looks only at string values from
  opts_data.  Global variables are evaluated only when printing help text,
  after the variables are initialized
MMGen 6 years ago
parent
commit
017ecef3bc

+ 32 - 22
mmgen/main_addrgen.py

@@ -41,12 +41,15 @@ note_secp256k1 = """
 If available, the secp256k1 library will be used for address generation.
 """.strip()
 
-opts_data = lambda: {
+opts_data = {
 	'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),
-	'usage':'[opts] [seed source] <index list or range(s)>',
-	'options': """
+	'text': {
+		'desc': """
+                 Generate a range or list of {desc} from an {pnm} wallet,
+                 mnemonic, seed or brainwallet
+			  """.format(desc=gen_desc,pnm=g.proj_name),
+		'usage':'[opts] [seed source] <index list or range(s)>',
+		'options': """
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
 -A, --no-addresses    Print only secret keys, no addresses
@@ -77,14 +80,7 @@ opts_data = lambda: {
                       (default: {dmat})
 -v, --verbose         Produce more verbose output
 -x, --b16             Print secret keys in hexadecimal too
-""".format(
-	seed_lens=', '.join(map(str,g.seed_lens)),
-	pnm=g.proj_name,
-	kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-	kg=g.key_generator,
-	what=gen_what,g=g,
-	dmat="'{}' or '{}'".format(g.proto.dfl_mmtype,MAT.mmtypes[g.proto.dfl_mmtype]['name'])
-),
+""",
 	'notes': """
 
                            NOTES FOR THIS COMMAND
@@ -105,16 +101,30 @@ ADDRESS TYPES:
 {n_bw}
 
 FMT CODES:
+
   {n_fmt}
-""".format(
-		n_secp=note_secp256k1,
-		n_addrkey=note_addrkey,
-		n_pw=help_notes('passwd'),
-		n_bw=help_notes('brainwallet'),
-		n_fmt='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
-		n_at='\n  '.join(["'{}','{:<12} - {}".format(k,v['name']+"'",v['desc']) for k,v in list(MAT.mmtypes.items())]),
-		o=opts
-	)
+"""
+	},
+	'code': {
+		'options': lambda s: s.format(
+			seed_lens=', '.join(map(str,g.seed_lens)),
+			dmat="'{}' or '{}'".format(g.proto.dfl_mmtype,MAT.mmtypes[g.proto.dfl_mmtype]['name']),
+			kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
+			kg=g.key_generator,
+			pnm=g.proj_name,
+			what=gen_what,
+			g=g,
+		),
+		'notes': lambda s: s.format(
+			n_secp=note_secp256k1,
+			n_addrkey=note_addrkey,
+			n_pw=help_notes('passwd'),
+			n_bw=help_notes('brainwallet'),
+			n_fmt='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
+			n_at='\n  '.join(["'{}','{:<12} - {}".format(
+				k,v['name']+"'",v['desc']) for k,v in list(MAT.mmtypes.items())])
+		)
+	}
 }
 
 cmd_args = opts.init(opts_data,add_opts=['b16'],opt_filter=opt_filter)

+ 6 - 4
mmgen/main_addrimport.py

@@ -47,10 +47,11 @@ option, or a list of non-{pnm} addresses with the '--addrlist' option
 # In batch mode, daemon just rescans each address separately anyway, so make
 # --batch and --rescan incompatible.
 
-opts_data = lambda: {
-	'desc': """Import addresses into an {} tracking wallet""".format(g.proj_name),
-	'usage':'[opts] [mmgen address file]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': """Import addresses into an {} tracking wallet""".format(g.proj_name),
+		'usage':'[opts] [mmgen address file]',
+		'options': """
 -h, --help         Print this help message
 --, --longhelp     Print help message for long options (common options)
 -a, --address=a    Import the single coin address 'a'
@@ -67,6 +68,7 @@ already in the tracking wallet.
 
 The --batch and --rescan options cannot be used together.
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 6 - 4
mmgen/main_autosign.py

@@ -31,10 +31,11 @@ key_fn       = 'autosign.key'
 
 from mmgen.common import *
 prog_name = os.path.basename(sys.argv[0])
-opts_data = lambda: {
-	'desc': 'Auto-sign MMGen transactions',
-	'usage':'[opts] [command]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Auto-sign MMGen transactions',
+		'usage':'[opts] [command]',
+		'options': """
 -h, --help          Print this help message
 --, --longhelp      Print help message for long options (common options)
 -c, --coins=c       Coins to sign for (comma-separated list)
@@ -97,6 +98,7 @@ each signing session.
 
 This command is currently available only on Linux-based platforms.
 """.format(pnm=prog_name,wd=wallet_dir,td=tx_dir,kf=key_fn,mp=mountpoint)
+	}
 }
 
 cmd_args = opts.init(opts_data,add_opts=['mmgen_keys_from_file','in_fmt'])

+ 28 - 18
mmgen/main_passgen.py

@@ -33,12 +33,15 @@ dfl_len = {
 	'hex': PasswordList.pw_info['hex']['dfl_len']
 }
 
-opts_data = lambda: {
+opts_data = {
 	'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),
-	'usage':'[opts] [seed source] <ID string> <index list or range(s)>',
-	'options': """
+	'text': {
+		'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),
+		'usage':'[opts] [seed source] <ID string> <index list or range(s)>',
+		'options': """
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
 -b, --base32          Generate passwords in Base32 format instead of Base58
@@ -65,11 +68,7 @@ opts_data = lambda: {
                       (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
 -S, --stdout          Print passwords to stdout
 -v, --verbose         Produce more verbose output
-""".format(
-	seed_lens=', '.join(map(str,g.seed_lens)),
-	g=g,pnm=g.proj_name,d58=dfl_len['b58'],d32=dfl_len['b32'],dhex=dfl_len['hex'],
-	kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)])
-),
+""",
 	'notes': """
 
                            NOTES FOR THIS COMMAND
@@ -84,6 +83,7 @@ Changing either the password format (base32,base58) or length alters the seed
 and thus generates a completely new set of passwords.
 
 EXAMPLE:
+
   Generate ten base58 passwords of length {d58} for Alice's email account:
   {g.prog_name} alice@nowhere.com 1-10
 
@@ -104,15 +104,25 @@ EXAMPLE:
 {n_bw}
 
 FMT CODES:
+
   {n_fmt}
-""".format(
-		o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
-		ml=MMGenPWIDString.max_len,
-		fs="', '".join(MMGenPWIDString.forbidden),
-		n_pw=help_notes('passwd'),
-		n_bw=help_notes('brainwallet'),
-		n_fmt='\n  '.join(SeedSource.format_fmt_codes().splitlines())
-	)
+"""
+	},
+	'code': {
+		'options': lambda s: s.format(
+			seed_lens=', '.join(map(str,g.seed_lens)),
+			g=g,pnm=g.proj_name,d58=dfl_len['b58'],d32=dfl_len['b32'],dhex=dfl_len['hex'],
+			kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)])
+		),
+		'notes': lambda s: s.format(
+				o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
+				ml=MMGenPWIDString.max_len,
+				fs="', '".join(MMGenPWIDString.forbidden),
+				n_pw=help_notes('passwd'),
+				n_bw=help_notes('brainwallet'),
+				n_fmt='\n  '.join(SeedSource.format_fmt_codes().splitlines())
+		)
+	}
 }
 
 cmd_args = opts.init(opts_data,add_opts=['b16'])

+ 7 - 5
mmgen/main_regtest.py

@@ -23,11 +23,12 @@ mmgen-regtest: Coin daemon regression test mode setup and operations for the MMG
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': 'Coin daemon regression test mode setup and operations for the {} suite'.format(g.proj_name),
-	'usage':   '[opts] <command>',
-	'sets': ( ('yes', True, 'quiet', True), ),
-	'options': """
+opts_data = {
+	'sets': [('yes', True, 'quiet', True)],
+	'text': {
+		'desc': 'Coin daemon regression test mode setup and operations for the {} suite'.format(g.proj_name),
+		'usage':   '[opts] <command>',
+		'options': """
 -h, --help          Print this help message
 --, --longhelp      Print help message for long options (common options)
 -e, --empty         Don't fund Bob and Alice's wallets on setup
@@ -52,6 +53,7 @@ opts_data = lambda: {
   show_mempool    - show transaction IDs in mempool
   cli [arguments] - execute an RPC call with arguments
 	"""
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 15 - 6
mmgen/main_split.py

@@ -26,11 +26,14 @@ import time
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': """Split funds in a {pnm} wallet after a chain fork using a
-                timelocked transaction""".format(pnm=g.proj_name),
-	'usage':'[opts] [output addr1] [output addr2]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': """
+               Split funds in a {pnm} wallet after a chain fork using a
+               timelocked transaction
+		 """.format(pnm=g.proj_name),
+		'usage':'[opts] [output addr1] [output addr2]',
+		'options': """
 -h, --help           Print this help message
 --, --longhelp       Print help message for long options (common options)
 -f, --tx-fees=     f The transaction fees for each chain (comma-separated)
@@ -46,7 +49,7 @@ opts_data = lambda: {
 -R, --rpc-host2=   h Host the other coin daemon is running on (default: none)
 -L, --locktime=    t Lock time (block height or unix seconds)
                      (default: {bh})
-""".format(oc=g.proto.forks[-1][2].upper(),bh='current block height'),
+""",
 	'notes': """\n
 This command creates two transactions: one (with the timelock) to be broadcast
 on the long chain and one on the short chain after a replayable chain fork.
@@ -74,6 +77,12 @@ minority chain is ahead of the timelock.  If the reorg'd minority chain is
 behind the timelock, protection is contingent on getting the non-timelocked
 transaction reconfirmed before the timelock expires. Use at your own risk.
 """.format(pnm=g.proj_name)
+	},
+	'code': {
+		'options': lambda s: s.format(
+			oc=g.proto.forks[-1][2].upper(),
+			bh='current block height'),
+	}
 }
 
 cmd_args = opts.init(opts_data,add_opts=['tx_fee','tx_fee_adj','comment_file'])

+ 14 - 6
mmgen/main_tool.py

@@ -53,10 +53,11 @@ def make_cmd_help():
 
 	return '\n'.join(out)
 
-opts_data = lambda: {
-	'desc':    'Perform various {pnm}- and cryptocoin-related operations'.format(pnm=g.proj_name),
-	'usage':   '[opts] <command> <command args>',
-	'options': """
+opts_data = {
+	'text': {
+		'desc':    'Perform various {pnm}- and cryptocoin-related operations'.format(pnm=g.proj_name),
+		'usage':   '[opts] <command> <command args>',
+		'options': """
 -d, --outdir=       d Specify an alternate directory 'd' for output
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
@@ -70,14 +71,21 @@ opts_data = lambda: {
 -t, --type=t          Specify address type (valid options: 'legacy',
                       'compressed', 'segwit', 'bech32', 'zcash_z')
 -v, --verbose         Produce more verbose output
-""".format(g=g),
+""",
 	'notes': """
 
                                COMMANDS
 
 {ch}
 Type '{pn} help <command>' for help on a particular command
-""".format(pn=g.prog_name,ch=make_cmd_help())
+"""
+	},
+	'code': {
+		'options': lambda s: s.format(g=g),
+		'notes': lambda s: s.format(
+			ch=make_cmd_help(),
+			pn=g.prog_name)
+	}
 }
 
 cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','use_old_ed25519'])

+ 23 - 12
mmgen/main_txbump.py

@@ -23,11 +23,16 @@ mmgen-txbump: Increase the fee on a replaceable (replace-by-fee) MMGen
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'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), ),
-	'options': """
+opts_data = {
+	'sets': [('yes', True, 'quiet', True)],
+	'text': {
+		'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),
+		'options': """
 -h, --help             Print this help message
 --, --longhelp         Print help message for long options (common options)
 -b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
@@ -66,13 +71,19 @@ opts_data = lambda: {
 -y, --yes             Answer 'yes' to prompts, suppress non-essential output
 -z, --show-hash-presets Show information on available hash presets
 """,
-	'options_fmt_args': lambda: dict(
-		g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
-		fu=help_notes('rel_fee_desc'),fl=help_notes('fee_spec_letters'),
-		kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-		kg=g.key_generator,
-		cu=g.coin),
-	'notes': lambda: '\n' + help_notes('fee') + help_notes('txsign')
+	'notes': '\n{}{}'
+	},
+	'code': {
+		'options': lambda s: s.format(
+			g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
+			fu=help_notes('rel_fee_desc'),fl=help_notes('fee_spec_letters'),
+			kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
+			kg=g.key_generator,
+			cu=g.coin),
+		'notes': lambda s: s.format(
+			help_notes('fee'),
+			help_notes('txsign'))
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 18 - 10
mmgen/main_txcreate.py

@@ -23,11 +23,12 @@ mmgen-txcreate: Create a cryptocoin transaction with MMGen- and/or non-MMGen
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': 'Create a transaction with outputs to specified coin or {g.proj_name} addresses'.format(g=g),
-	'usage':   '[opts]  <addr,amt> ... [change addr] [addr file] ...',
-	'sets': ( ('yes', True, 'quiet', True), ),
-	'options': """
+opts_data = {
+	'sets': [('yes', True, 'quiet', True)],
+	'text': {
+		'desc': 'Create a transaction with outputs to specified coin or {g.proj_name} addresses'.format(g=g),
+		'usage':   '[opts]  <addr,amt> ... [change addr] [addr file] ...',
+		'options': """
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
 -a, --tx-fee-adj=  f  Adjust transaction fee by factor 'f' (see below)
@@ -54,11 +55,18 @@ opts_data = lambda: {
 -V, --vsize-adj=   f  Adjust transaction's estimated vsize by factor 'f'
 -y, --yes             Answer 'yes' to prompts, suppress non-essential output
 """,
-	'options_fmt_args': lambda: dict(
-							g=g,cu=g.coin,
-							fu=help_notes('rel_fee_desc'),
-							fl=help_notes('fee_spec_letters') ),
-	'notes': lambda: '\n' + help_notes('txcreate') + help_notes('fee')
+		'notes': '\n{}{}',
+	},
+	'code': {
+		'options': lambda s: s.format(
+			fu=help_notes('rel_fee_desc'),
+			fl=help_notes('fee_spec_letters'),
+			cu=g.coin,
+			g=g),
+		'notes': lambda s: s.format(
+			help_notes('txcreate'),
+			help_notes('fee'))
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 21 - 13
mmgen/main_txdo.py

@@ -22,11 +22,12 @@ mmgen-txdo: Create, sign and broadcast an online MMGen transaction
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': 'Create, sign and send an {g.proj_name} transaction'.format(g=g),
-	'usage':   '[opts]  <addr,amt> ... [change addr] [addr file] ... [seed source] ...',
-	'sets': ( ('yes', True, 'quiet', True), ),
-	'options': """
+opts_data = {
+	'sets': [('yes', True, 'quiet', True)],
+	'text': {
+		'desc': 'Create, sign and send an {g.proj_name} transaction'.format(g=g),
+		'usage':   '[opts]  <addr,amt> ... [change addr] [addr file] ... [seed source] ...',
+		'options': """
 -h, --help             Print this help message
 --, --longhelp         Print help message for long options (common options)
 -a, --tx-fee-adj=    f Adjust transaction fee by factor 'f' (see below)
@@ -75,14 +76,21 @@ opts_data = lambda: {
 -y, --yes              Answer 'yes' to prompts, suppress non-essential output
 -z, --show-hash-presets Show information on available hash presets
 """,
-	'options_fmt_args': lambda: dict(
-		g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),
-		kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-		fu=help_notes('rel_fee_desc'),
-		fl=help_notes('fee_spec_letters'),
-		kg=g.key_generator,
-		cu=g.coin),
-	'notes': lambda: '\n' + help_notes('txcreate') + help_notes('fee') + help_notes('txsign')
+		'notes': '\n{}{}{}',
+	},
+	'code': {
+		'options': lambda s: s.format(
+			g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),
+			kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
+			fu=help_notes('rel_fee_desc'),
+			fl=help_notes('fee_spec_letters'),
+			kg=g.key_generator,
+			cu=g.coin),
+		'notes': lambda s: s.format(
+			help_notes('txcreate'),
+			help_notes('fee'),
+			help_notes('txsign'))
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 7 - 5
mmgen/main_txsend.py

@@ -22,11 +22,12 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc':    'Send a cryptocoin transaction signed by {pnm}-txsign'.format(pnm=g.proj_name.lower()),
-	'usage':   '[opts] <signed transaction file>',
-	'sets': ( ('yes', True, 'quiet', True), ),
-	'options': """
+opts_data = {
+	'sets': [('yes', True, 'quiet', True)],
+	'text': {
+		'desc':    'Send a signed {pnm} cryptocoin transaction'.format(pnm=g.proj_name),
+		'usage':   '[opts] <signed transaction file>',
+		'options': """
 -h, --help      Print this help message
 --, --longhelp  Print help message for long options (common options)
 -d, --outdir= d Specify an alternate directory 'd' for output
@@ -34,6 +35,7 @@ opts_data = lambda: {
 -s, --status    Get status of a sent transaction
 -y, --yes       Answer 'yes' to prompts, suppress non-essential output
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 17 - 12
mmgen/main_txsign.py

@@ -23,11 +23,12 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
 from mmgen.common import *
 
 # -w, --use-wallet-dat (keys from running coin daemon) removed: use walletdump rpc instead
-opts_data = lambda: {
-	'desc':    'Sign cryptocoin transactions generated by {pnl}-txcreate'.format(pnl=g.proj_name.lower()),
-	'usage':   '[opts] <transaction file>... [seed source]...',
-	'sets': ( ('yes', True, 'quiet', True), ),
-	'options': """
+opts_data = {
+	'sets': [('yes', True, 'quiet', True)],
+	'text': {
+		'desc':    'Sign cryptocoin transactions generated by {pnl}-txcreate'.format(pnl=g.proj_name.lower()),
+		'usage':   '[opts] <transaction file>... [seed source]...',
+		'options': """
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
 -b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
@@ -62,13 +63,17 @@ opts_data = lambda: {
 -V, --vsize-adj=   f  Adjust transaction's estimated vsize by factor 'f'
 -y, --yes             Answer 'yes' to prompts, suppress non-essential output
 """,
-	'options_fmt_args': lambda: dict(
-		g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
-		kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
-		kg=g.key_generator,
-		cu=g.coin
-		),
-	'notes': lambda: '\n' + help_notes('txsign')
+	'notes': '\n{}'
+	},
+	'code': {
+		'options': lambda s: s.format(
+			g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
+			kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
+			kg=g.key_generator,
+			cu=g.coin),
+		'notes': lambda s: s.format(
+			help_notes('txsign'))
+	}
 }
 
 infiles = opts.init(opts_data,add_opts=['b16'])

+ 21 - 16
mmgen/main_wallet.py

@@ -55,12 +55,11 @@ elif invoked_as == 'passchg':
 else:
 	die(1,"'{}': unrecognized invocation".format(g.prog_name))
 
-opts_data = lambda: {
-# 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),
-	'usage': usage,
-	'options': """
+opts_data = {
+	'text': {
+		'desc': desc.format(pnm=g.proj_name),
+		'usage': usage,
+		'options': """
 -h, --help            Print this help message
 --, --longhelp        Print help message for long options (common options)
 -d, --outdir=      d  Output files to directory 'd' instead of working dir
@@ -90,22 +89,28 @@ opts_data = lambda: {
                       (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
 -S, --stdout          Write wallet data to stdout instead of file
 -v, --verbose         Produce more verbose output
-""".format(
-		g=g,
-		iaction=capfirst(iaction),
-		oaction=capfirst(oaction),
-	),
+""",
 	'notes': """
 
 {n_pw}{n_bw}
 
 FMT CODES:
+
   {f}
-""".format(
-	f='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
-	n_pw=help_notes('passwd'),
-	n_bw=('','\n\n' + help_notes('brainwallet'))[bw_note]
-	)
+"""
+	},
+	'code': {
+		'options': lambda s: s.format(
+			iaction=capfirst(iaction),
+			oaction=capfirst(oaction),
+			g=g,
+		),
+		'notes': lambda s: s.format(
+			f='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
+			n_pw=help_notes('passwd'),
+			n_bw=('','\n\n' + help_notes('brainwallet'))[bw_note]
+		)
+	}
 }
 
 cmd_args = opts.init(opts_data,opt_filter=opt_filter)

+ 29 - 28
mmgen/opts.py

@@ -181,10 +181,16 @@ Are you sure you want to continue?
 	if not keypress_confirm(m,default_yes=True):
 		sys.exit(0)
 
-def get_common_opts_data():
-	# most, but not all, of these set the corresponding global var
+def common_opts_code(s):
 	from mmgen.protocol import CoinProtocol
-	return """
+	return s.format(
+		pnm=g.proj_name,pn=g.proto.name,dn=g.proto.daemon_name,
+		cu_dfl=g.coin,
+		cu_all=' '.join(CoinProtocol.coins) )
+
+common_opts_data = {
+	# most, but not all, of these set the corresponding global var
+	'text': """
 --, --accept-defaults     Accept defaults at all prompts
 --, --coin=c              Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
 --, --token=t             Specify an ERC20 token by address or symbol
@@ -203,29 +209,25 @@ def get_common_opts_data():
 --, --version             Print version information and exit
 --, --bob                 Switch to user "Bob" in MMGen regtest setup
 --, --alice               Switch to user "Alice" in MMGen regtest setup
-	""".format( pnm=g.proj_name,pn=g.proto.name,dn=g.proto.daemon_name,
-				cu_dfl=g.coin,
-				cu_all=' '.join(CoinProtocol.coins))
-
-def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
+	""",
+	'code': common_opts_code
+}
 
-	from mmgen.protocol import CoinProtocol,BitcoinProtocol,init_genonly_altcoins
-	g.proto = BitcoinProtocol # this must be initialized to something before opts_f is called
+def init(opts_data,add_opts=[],opt_filter=None,parse_only=False):
 
-	opts_data = opts_f()
-	opts_data['long_options'] = get_common_opts_data()
+	opts_data['text']['long_options'] = common_opts_data['text']
 
-	uopts,args,short_opts,long_opts,skipped_opts,do_help = \
-		mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter,skip_help=True)
+	uopts,args,short_opts,long_opts,skipped_opts = \
+		mmgen.share.Opts.parse_opts(opts_data,opt_filter=opt_filter,parse_only=parse_only)
 
 	if parse_only:
-		return uopts,args,short_opts,long_opts,skipped_opts,do_help
+		return uopts,args,short_opts,long_opts,skipped_opts
 
 	if g.debug_opts: opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args)
 
 	# Save this for usage()
 	global usage_txt
-	usage_txt = opts_data['usage']
+	usage_txt = opts_data['text']['usage']
 
 	# 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)
@@ -264,6 +266,7 @@ def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
 
 	if g.regtest: g.testnet = True # These are equivalent for now
 
+	from mmgen.protocol import init_genonly_altcoins,CoinProtocol
 	altcoin_trust_level = init_genonly_altcoins(opt.coin)
 
 	# g.testnet is set, so we can set g.proto
@@ -272,7 +275,7 @@ def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
 	# global sets proto
 	if g.daemon_data_dir: g.proto.daemon_data_dir = g.daemon_data_dir
 
-#	g.proto is set, so we can set g.data_dir
+	# g.proto is set, so we can set g.data_dir
 	g.data_dir = os.path.normpath(os.path.join(g.data_dir_root,g.proto.data_subdir))
 
 	# If user opt is set, convert its type based on value in mmgen.globalvars (g)
@@ -295,14 +298,15 @@ def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
 
 	opt_postproc_initializations()
 
-	if do_help: # print help screen only after global vars are initialized
-		opts_data = opts_f()
-		opts_data['long_options'] = get_common_opts_data()
+	if opts_data['do_help']: # print help screen only after global vars are initialized
+		if not 'code' in opts_data:
+			opts_data['code'] = {}
+		opts_data['code']['long_options'] = common_opts_data['code']
 		if g.debug_utf8:
 			for k in opts_data:
 				if type(opts_data[k]) == str:
 					opts_data[k] += '-α'
-		mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter) # exits
+		mmgen.share.Opts.print_help(opts_data,opt_filter) # exits
 
 	if g.bob or g.alice:
 		g.testnet = True
@@ -329,18 +333,15 @@ def init(opts_f,add_opts=[],opt_filter=None,parse_only=False):
 		my_raw_input('Hit ENTER to continue: ')
 
 	if g.debug and g.prog_name != 'test.py':
-		opt.verbose,opt.quiet = True,None
+		opt.verbose,opt.quiet = (True,None)
 	if g.debug_opts: opt_postproc_debug()
 
-	# 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]
-
 	g.altcoin_data_dir = os.path.join(g.data_dir_root,'altcoins')
 	warn_altcoins(altcoin_trust_level)
 
+	# We don't need this data anymore
+	del mmgen.share.Opts, opts_data
+
 	return args
 
 def opt_is_tx_fee(val,desc):

+ 45 - 32
mmgen/share/Opts.py

@@ -27,47 +27,57 @@ def usage(opts_data):
 	print('USAGE: {} {}'.format(opts_data['prog_name'], opts_data['usage']))
 	sys.exit(2)
 
-def print_help_and_exit(opts_data,longhelp=False):
+def print_help(opts_data,opt_filter):
+	t = opts_data['text']
+	c = opts_data['code']
+
+	# header
 	pn = opts_data['prog_name']
-	pn_len = str(len(pn)+2)
-	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)
-	o = opts_data[('options','long_options')[longhelp]].strip()
-	if 'options_fmt_args' in opts_data:
-		o = o.format(**opts_data['options_fmt_args']())
-	hdr = ('OPTIONS:','  LONG OPTIONS:')[longhelp]
-	ls = ('  ','')[longhelp]
-	es = ('','    ')[longhelp]
-	out += '{ls}{}\n{ls}{es}{}'.format(hdr,('\n'+ls).join(o.splitlines()),ls=ls,es=es)
-	if 'notes' in opts_data and not longhelp:
-		n = opts_data['notes']
-		if isinstance(n, collections.Callable): n = n()
-		out += '\n  ' + '\n  '.join(n.rstrip().splitlines())
+	out  = '  {:<{p}} {}\n'.format(pn.upper()+':',t['desc'].strip(),p=len(pn)+1)
+	out += '  {:<{p}} {} {}\n'.format('USAGE:',pn,t['usage'].strip(),p=len(pn)+1)
+
+	# options
+	if opts_data['do_help'] == 'longhelp':
+		hdr,ls,es = ('  LONG OPTIONS:','','    ')
+		text = t['long_options'].strip()
+		code = c['long_options'] if 'long_options' in c else None
+	else:
+		hdr,ls,es = ('OPTIONS:','  ','')
+		text = t['options']
+		code = c['options'] if 'options' in c else None
+
+	ftext = code(text) if code else text
+	out += '{ls}{}\n{ls}{es}{}'.format(hdr,('\n'+ls).join(ftext.splitlines()),ls=ls,es=es)
+
+	# notes
+	if opts_data['do_help'] == 'help' and 'notes' in t:
+		ftext = c['notes'](t['notes']) if 'notes' in c else t['notes']
+		out += '\n  ' + '\n  '.join(ftext.rstrip().splitlines())
+
 	print(out)
 	sys.exit(0)
 
-def process_opts(argv,opts_data,short_opts,long_opts,skip_help=False):
+def process_opts(opts_data,short_opts,long_opts):
 
 	import os
 	opts_data['prog_name'] = os.path.basename(sys.argv[0])
 	long_opts  = [i.replace('_','-') for i in long_opts]
 
 	so_str = short_opts.replace('-:','').replace('-','')
-	try: cl_opts,args = getopt.getopt(argv[1:], so_str, long_opts)
+	try: cl_opts,args = getopt.getopt(sys.argv[1:], so_str, long_opts)
 	except getopt.GetoptError as err:
 		print(str(err))
 		sys.exit(2)
 
 	sopts_list = ':_'.join(['_'.join(list(i)) for i in short_opts.split(':')]).split('_')
-	opts,skipped_help = {},False
+	opts = {}
+	opts_data['do_help'] = False
 
 	for opt,arg in cl_opts:
 		if opt in ('-h','--help'):
-			if not skip_help: print_help_and_exit(opts_data)
-			skipped_help = True
+			opts_data['do_help'] = 'help'
 		elif opt == '--longhelp':
-			if not skip_help: print_help_and_exit(opts_data,longhelp=True)
-			skipped_help = True
+			opts_data['do_help'] = 'longhelp'
 		elif opt[:2] == '--' and opt[2:] in long_opts:
 			opts[opt[2:].replace('-','_')] = True
 		elif opt[:2] == '--' and opt[2:]+'=' in long_opts:
@@ -93,17 +103,18 @@ def process_opts(argv,opts_data,short_opts,long_opts,skip_help=False):
 					else:
 						opts[o_out] = v_out
 
-	return opts,args,skipped_help
+	return opts,args
 
-def parse_opts(argv,opts_data,opt_filter=None,skip_help=False):
+def parse_opts(opts_data,opt_filter=None,parse_only=False):
 
 	import re
 	pat = r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)'
 	od_all = []
 
-	for k in ['options'] + ([],['long_options'])['long_options' in opts_data]:
+	for k in ('options','long_options'):
+		if k not in opts_data['text']: continue
 		od,skip = [],True
-		for l in opts_data[k].strip().splitlines():
+		for l in opts_data['text'][k].strip().splitlines():
 			m = re.match(pat,l)
 			if m:
 				skip = bool(opt_filter) and m.group(1) not in opt_filter
@@ -112,15 +123,17 @@ def parse_opts(argv,opts_data,opt_filter=None,skip_help=False):
 			else:
 				if not skip: od[-1][3] += '\n' + l
 
-		opts_data[k] = '\n'.join(
-			['{:<3} --{} {}'.format(
-				('-'+d[0]+',','')[d[0]=='-'],d[1],d[3]) for d in od if d[6] == False]
-		)
+		if not parse_only:
+			opts_data['text'][k] = '\n'.join(
+				['{:<3} --{} {}'.format(
+					('-'+d[0]+',','')[d[0]=='-'],d[1],d[3]) for d in od if d[6] == False]
+			)
 		od_all += od
+
 	short_opts    = ''.join([d[0]+d[4] for d in od_all if d[6] == False])
 	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,skipped_help = process_opts(argv,opts_data,short_opts,long_opts,skip_help=skip_help)
+	opts,args = process_opts(opts_data,short_opts,long_opts)
 
-	return opts,args,short_opts,long_opts,skipped_opts,skipped_help
+	return opts,args,short_opts,long_opts,skipped_opts

+ 6 - 4
scripts/compute-file-chksum.py

@@ -6,13 +6,15 @@ sys.path = [repo_root] + sys.path
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': 'Compute checksum for a MMGen data file',
-	'usage':'[opts] infile',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Compute checksum for a MMGen data file',
+		'usage':'[opts] infile',
+		'options': """
 -h, --help               Print this help message.
 -i, --include-first-line Include the first line of the file (you probably don't want this)
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 14 - 5
scripts/create-token.py

@@ -27,10 +27,11 @@ name   = 'MMGen Token'
 symbol = 'MMT'
 solc_version_pat = r'0.5.[123]'
 
-opts_data = lambda: {
-	'desc': 'Create an ERC20 token contract',
-	'usage':'[opts] <owner address>',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Create an ERC20 token contract',
+		'usage':'[opts] <owner address>',
+		'options': """
 -h, --help       Print this help message
 -o, --outdir=  d Specify output directory for *.bin files
 -d, --decimals=d Number of decimals for the token (default: {d})
@@ -39,7 +40,15 @@ opts_data = lambda: {
 -s, --symbol=  s Token symbol (default: {s})
 -S, --stdout     Output data in JSON format to stdout instead of files
 -v, --verbose    Produce more verbose output
-""".format(d=decimals,n=name,s=symbol,t=supply)
+"""
+	},
+	'code': {
+		'options': lambda s: s.format(
+			d=decimals,
+			n=name,
+			s=symbol,
+			t=supply)
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 6 - 4
scripts/tx-btc2bch.py

@@ -22,14 +22,16 @@ tx-btc2bch: Convert MMGen transaction files from BTC to BCH format
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': """Convert {pnm} transaction files from BTC to BCH format""".format(pnm=g.proj_name),
-	'usage':'[opts] [mmgen transaction file]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': """Convert {pnm} transaction files from BTC to BCH format""".format(pnm=g.proj_name),
+		'usage':'[opts] [mmgen transaction file]',
+		'options': """
 -h, --help         Print this help message
 --, --longhelp     Print help message for long options (common options)
 -v, --verbose      Produce more verbose output
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 6 - 4
scripts/tx-v2-to-v3.py

@@ -9,15 +9,17 @@ sys.path = [repo_root] + sys.path
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc':    "Convert MMGen transaction file from v2 format to v3 format",
-	'usage':   "<tx file>",
-	'options': """
+opts_data = {
+	'text': {
+		'desc':    "Convert MMGen transaction file from v2 format to v3 format",
+		'usage':   "<tx file>",
+		'options': """
 -h, --help     Print this help message
 -d, --outdir=d Output files to directory 'd' instead of working dir
 -q, --quiet    Write (and overwrite) files without prompting
 -S, --stdout   Write data to STDOUT instead of file
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 6 - 5
scripts/uninstall-mmgen.py

@@ -40,15 +40,16 @@ modpath_save = sys.modules['mmgen.main'].__spec__.origin
 
 from mmgen.common import *
 
-opts_data = lambda: {
-	'prog_name': sys.argv[0].split('/')[-1],
-	'desc': 'Remove MMGen from your system',
-	'usage': '[opts]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Remove MMGen from your system',
+		'usage': '[opts]',
+		'options': """
 -h, --help        Print this help message
 -l, --list-paths  List the directories and files that would be deleted
 -n, --no-prompt   Don't prompt before deleting
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 14 - 5
test/gentest.py

@@ -31,10 +31,11 @@ from mmgen.common import *
 from mmgen.obj import MMGenAddrType
 
 rounds = 100
-opts_data = lambda: {
-	'desc': 'Test address generation in various ways',
-	'usage':'[options] [spec] [rounds | dump file]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Test address generation in various ways',
+		'usage':'[options] [spec] [rounds | dump file]',
+		'options': """
 -h, --help       Print this help message
 -a, --all        Test all supported coins for external generator 'ext'
 -k, --use-internal-keccak-module Force use of the internal keccak module
@@ -68,7 +69,15 @@ EXAMPLES:
     + pycoin     (for supported coins)   https://github.com/richardkiss/pycoin
     + keyconv    (for all other coins)   https://github.com/exploitagency/vanitygen-plus
                  ('keyconv' generates uncompressed addresses only)
-""".format(prog='gentest.py',pnm=g.proj_name,snum=rounds,dn=g.proto.daemon_name)
+"""
+	},
+	'code': {
+		'notes': lambda s: s.format(
+			prog='gentest.py',
+			pnm=g.proj_name,
+			snum=rounds,
+			dn=g.proto.daemon_name)
+	}
 }
 
 sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]

+ 7 - 5
test/objtest.py

@@ -30,11 +30,12 @@ from mmgen.common import *
 from mmgen.obj import *
 from mmgen.seed import *
 
-opts_data = lambda: {
-	'desc': 'Test MMGen data objects',
-	'sets': ( ('super_silent', True, 'silent', True), ),
-	'usage':'[options] [object]',
-	'options': """
+opts_data = {
+	'sets': [('super_silent', True, 'silent', True)],
+	'text': {
+		'desc': 'Test MMGen data objects',
+		'usage':'[options] [object]',
+		'options': """
 -h, --help         Print this help message
 --, --longhelp     Print help message for long options (common options)
 -q, --quiet        Produce quieter output
@@ -42,6 +43,7 @@ opts_data = lambda: {
 -S, --super-silent Silence all output except for errors
 -v, --verbose      Produce more verbose output
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 6 - 4
test/scrambletest.py

@@ -30,10 +30,11 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
 from mmgen.common import *
 from test.common import init_coverage
 
-opts_data = lambda: {
-	'desc': 'Test seed scrambling and addrlist data generation for all supported altcoins',
-	'usage':'[options] [command]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Test seed scrambling and addrlist data generation for all supported altcoins',
+		'usage':'[options] [command]',
+		'options': """
 -h, --help          Print this help message
 --, --longhelp      Print help message for long options (common options)
 -C, --coverage      Produce code coverage info using trace module
@@ -46,6 +47,7 @@ opts_data = lambda: {
 
 If no command is given, the whole suite of tests is run.
 """
+	}
 }
 
 cmd_args = opts.init(opts_data)

+ 13 - 6
test/test.py

@@ -84,10 +84,11 @@ from test.test_py_d.common import *
 g.quiet = False # if 'quiet' was set in config file, disable here
 os.environ['MMGEN_QUIET'] = '0' # for this script and spawned scripts
 
-opts_data = lambda: {
-	'desc': 'Test suite for the MMGen suite',
-	'usage':'[options] [command(s) or metacommand(s)]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': 'Test suite for the MMGen suite',
+		'usage':'[options] [command(s) or metacommand(s)]',
+		'options': """
 -h, --help           Print this help message
 --, --longhelp       Print help message for long options (common options)
 -B, --bech32         Generate and use Bech32 addresses
@@ -122,11 +123,17 @@ opts_data = lambda: {
 -X, --exit-after=C   Exit after command 'C'
 -y, --segwit         Generate and use Segwit addresses
 -Y, --segwit-random  Generate and use a random mix of Segwit and Legacy addrs
-""".format(tbc='scripts/traceback_run.py',lf=log_file),
-	'notes': """
+""",
+		'notes': """
 
 If no command is given, the whole test suite is run.
 """
+	},
+	'code': {
+		'options': lambda s: s.format(
+			tbc='scripts/traceback_run.py',
+			lf=log_file),
+	}
 }
 
 data_dir = os.path.join('test','data_dir' + ('','-α')[bool(os.getenv('MMGEN_DEBUG_UTF8'))])

+ 6 - 4
test/tooltest.py

@@ -31,10 +31,11 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
 from mmgen.common import *
 from test.common import *
 
-opts_data = lambda: {
-	'desc': "Test suite for the 'mmgen-tool' utility",
-	'usage':'[options] [command]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': "Test suite for the 'mmgen-tool' utility",
+		'usage':'[options] [command]',
+		'options': """
 -h, --help          Print this help message
 -C, --coverage      Produce code coverage info using trace module
 -d, --debug         Produce debugging output (stderr from spawned script)
@@ -50,6 +51,7 @@ opts_data = lambda: {
 
 If no command is given, the whole suite of tests is run.
 """
+	}
 }
 
 sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]

+ 6 - 4
test/tooltest2.py

@@ -40,10 +40,11 @@ from mmgen.seed import is_mnemonic
 
 def is_str(s): return type(s) == str
 
-opts_data = lambda: {
-	'desc': "Simple test suite for the 'mmgen-tool' utility",
-	'usage':'[options] [command]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': "Simple test suite for the 'mmgen-tool' utility",
+		'usage':'[options] [command]',
+		'options': """
 -h, --help           Print this help message
 -C, --coverage       Produce code coverage info using trace module
 -d, --die-on-missing Abort if no test data found for given command
@@ -63,6 +64,7 @@ opts_data = lambda: {
 
 If no command is given, the whole suite of tests is run.
 """
+	}
 }
 
 sample_text_hexdump = (

+ 6 - 4
test/unit_tests.py

@@ -30,10 +30,11 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
 # Import these _after_ prepending repo_root to sys.path
 from mmgen.common import *
 
-opts_data = lambda: {
-	'desc': "Unit tests for the MMGen suite",
-	'usage':'[options] [tests]',
-	'options': """
+opts_data = {
+	'text': {
+		'desc': "Unit tests for the MMGen suite",
+		'usage':'[options] [tests]',
+		'options': """
 -h, --help       Print this help message
 -l, --list       List available tests
 -n, --names      Print command names instead of descriptions
@@ -43,6 +44,7 @@ opts_data = lambda: {
 	'notes': """
 If no test is specified, all available tests are run
 	"""
+	}
 }
 
 sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]