mmgen-tool: improve usage screens for individual commands
Example:
$ mmgen-tool help listaddresses
Testing/demo:
$ test/test.py -e tool_cmd_usage
This commit is contained in:
parent
642b45b2e3
commit
d07d665f35
9 changed files with 220 additions and 75 deletions
|
|
@ -1 +1 @@
|
|||
13.2.dev12
|
||||
13.2.dev13
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ opts_data = {
|
|||
-k, --use-internal-keccak-module Force use of the internal keccak module
|
||||
-K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
for {coin_id}: {kgs}
|
||||
-l, --list List available commands
|
||||
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
||||
for password hashing (default: '{g.dfl_hash_preset}')
|
||||
-P, --passwd-file= f Get passphrase from file 'f'.
|
||||
|
|
@ -194,11 +195,10 @@ def create_call_sig(cmd,cls,as_string=False):
|
|||
get_type_from_ann = lambda x: 'str or STDIN' if ann[x] == 'sstr' else ann[x].__name__
|
||||
return ' '.join(
|
||||
[f'{a} [{get_type_from_ann(a)}]' for a in args[:nargs]] +
|
||||
['{a} [{b}={c!r}{d}]'.format(
|
||||
['{a} [{b}={c!r}]'.format(
|
||||
a = a,
|
||||
b = dfl_types[n].__name__,
|
||||
c = dfls[n],
|
||||
d = (' ' + ann[a] if a in ann and isinstance(ann[a],str) else ''))
|
||||
c = dfls[n] )
|
||||
for n,a in enumerate(args[nargs:])] )
|
||||
else:
|
||||
get_type_from_ann = lambda x: 'str' if ann[x] == 'sstr' else ann[x].__name__
|
||||
|
|
@ -206,10 +206,11 @@ def create_call_sig(cmd,cls,as_string=False):
|
|||
[(a,get_type_from_ann(a)) for a in args[:nargs]], # c_args
|
||||
dict([(a,dfls[n]) for n,a in enumerate(args[nargs:])]), # c_kwargs
|
||||
dict([(a,dfl_types[n]) for n,a in enumerate(args[nargs:])]), # c_kwargs_types
|
||||
('STDIN_OK' if nargs and ann[args[0]] == 'sstr' else flag) ) # flag
|
||||
('STDIN_OK' if nargs and ann[args[0]] == 'sstr' else flag), # flag
|
||||
ann ) # ann
|
||||
|
||||
def process_args(cmd,cmd_args,cls):
|
||||
c_args,c_kwargs,c_kwargs_types,flag = create_call_sig(cmd,cls)
|
||||
c_args,c_kwargs,c_kwargs_types,flag,ann = create_call_sig(cmd,cls)
|
||||
have_stdin_input = False
|
||||
|
||||
def usage_die(s):
|
||||
|
|
@ -339,6 +340,18 @@ if g.prog_name == 'mmgen-tool' and not opt._lock:
|
|||
|
||||
po = opts.init( opts_data, parse_only=True )
|
||||
|
||||
if po.user_opts.get('list'):
|
||||
def gen():
|
||||
for mod,cmdlist in mods.items():
|
||||
if mod == 'help':
|
||||
continue
|
||||
yield capfirst( get_mod_cls(mod).__doc__.lstrip().split('\n')[0] ) + ':'
|
||||
for cmd in cmdlist:
|
||||
yield ' ' + cmd
|
||||
yield ''
|
||||
Msg('\n'.join(gen()).rstrip())
|
||||
sys.exit(0)
|
||||
|
||||
if len(po.cmd_args) < 1:
|
||||
opts.usage()
|
||||
|
||||
|
|
|
|||
|
|
@ -78,8 +78,10 @@ class tool_cmd(tool_cmd_base):
|
|||
'dfls': ( False, False, 'addr', 'mtime' ),
|
||||
'annots': {
|
||||
'mmgen_tx_file(s)': str,
|
||||
'sort': options_annot_str(['addr','raw']),
|
||||
'filesort': options_annot_str(['mtime','ctime','atime']),
|
||||
'pager': 'send output to pager',
|
||||
'terse': 'produce compact tabular output',
|
||||
'sort': 'sort order for transaction inputs and outputs ' + options_annot_str(['addr','raw']),
|
||||
'filesort': 'file sort order ' + options_annot_str(['mtime','ctime','atime']),
|
||||
}
|
||||
},
|
||||
*infiles,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ from ..crypto import get_random,aesctr_iv_len
|
|||
class tool_cmd(tool_cmd_base):
|
||||
"file utilities"
|
||||
|
||||
def find_incog_data(self,filename:str,incog_id:str,keep_searching=False):
|
||||
def find_incog_data(self,
|
||||
filename: str,
|
||||
incog_id: str,
|
||||
keep_searching: 'continue search after finding data (ID collisions can yield false positives)' = False):
|
||||
"Use an Incog ID to find hidden incognito wallet data"
|
||||
|
||||
from hashlib import sha256
|
||||
|
|
@ -67,6 +70,24 @@ class tool_cmd(tool_cmd_base):
|
|||
def rand2file(self,outfile:str,nbytes:str,threads=4,silent=False):
|
||||
"""
|
||||
write ‘nbytes’ bytes of random data to specified file (dd-style byte specifiers supported)
|
||||
|
||||
Valid specifiers:
|
||||
|
||||
c = 1
|
||||
w = 2
|
||||
b = 512
|
||||
kB = 1000
|
||||
K = 1024
|
||||
MB = 1000000
|
||||
M = 1048576
|
||||
GB = 1000000000
|
||||
G = 1073741824
|
||||
TB = 1000000000000
|
||||
T = 1099511627776
|
||||
PB = 1000000000000000
|
||||
P = 1125899906842624
|
||||
EB = 1000000000000000000
|
||||
E = 1152921504606846976
|
||||
"""
|
||||
from threading import Thread
|
||||
from queue import Queue
|
||||
|
|
|
|||
|
|
@ -25,23 +25,20 @@ import mmgen.main_tool as main_tool
|
|||
|
||||
def main_help():
|
||||
|
||||
from ..util import pretty_format
|
||||
from ..util import pretty_format,capfirst
|
||||
|
||||
def do():
|
||||
for clsname,cmdlist in main_tool.mods.items():
|
||||
cls = main_tool.get_mod_cls(clsname)
|
||||
cls_doc = cls.__doc__.strip().split('\n')
|
||||
for l in cls_doc:
|
||||
if l is cls_doc[0]:
|
||||
l += ':'
|
||||
l = l.replace('\t','',1)
|
||||
if l:
|
||||
l = l.replace('\t',' ')
|
||||
yield l[0].upper() + l[1:]
|
||||
else:
|
||||
yield ''
|
||||
cls_docstr = cls.__doc__.strip()
|
||||
yield capfirst(cls_docstr.split('\n')[0].strip()) + ':'
|
||||
yield ''
|
||||
|
||||
if '\n' in cls_docstr:
|
||||
for line in cls_docstr.split('\n')[2:]:
|
||||
yield ' ' + line.lstrip('\t')
|
||||
yield ''
|
||||
|
||||
max_w = max(map(len,cmdlist))
|
||||
|
||||
for cmdname in cmdlist:
|
||||
|
|
@ -64,7 +61,7 @@ def gen_tool_usage():
|
|||
from ..util import capfirst
|
||||
|
||||
m1 = """
|
||||
USAGE INFORMATION FOR MMGEN-TOOL COMMANDS:
|
||||
GENERAL USAGE INFORMATION FOR MMGEN-TOOL COMMANDS
|
||||
|
||||
Arguments with only type specified in square brackets are required
|
||||
|
||||
|
|
@ -140,15 +137,49 @@ def gen_tool_cmd_usage(mod,cmdname):
|
|||
|
||||
cls = main_tool.get_mod_cls(mod)
|
||||
docstr = getattr(cls,cmdname).__doc__.strip()
|
||||
args,kwargs,kwargs_types,flag = main_tool.create_call_sig(cmdname,cls)
|
||||
args,kwargs,kwargs_types,flag,ann = main_tool.create_call_sig(cmdname,cls)
|
||||
ARGS = 'ARG' if len(args) == 1 else 'ARGS' if args else ''
|
||||
KWARGS = 'KEYWORD ARG' if len(kwargs) == 1 else 'KEYWORD ARGS' if kwargs else ''
|
||||
|
||||
yield '{a}\n\nUSAGE: {b} {c} {d}{e}'.format(
|
||||
a = capfirst( docstr.split('\n')[0].strip() ),
|
||||
yield capfirst( docstr.split('\n')[0].strip() )
|
||||
yield ''
|
||||
yield 'USAGE: {b} [OPTS] {c}{d}{e}'.format(
|
||||
b = g.prog_name,
|
||||
c = cmdname,
|
||||
d = main_tool.create_call_sig(cmdname,cls,as_string=True),
|
||||
e = '\n\n' + fmt('\n'.join(docstr.split('\n')[1:]),strip_char='\t').rstrip()
|
||||
if '\n' in docstr else '' )
|
||||
d = f' {ARGS}' if ARGS else '',
|
||||
e = f' [{KWARGS}]' if KWARGS else '' )
|
||||
|
||||
if args:
|
||||
max_w = max(len(k[0]) for k in args)
|
||||
yield ''
|
||||
yield f'Required {ARGS} (type shown in square brackets):'
|
||||
yield ''
|
||||
for argname,argtype in args:
|
||||
have_sstr = ann.get(argname) == 'sstr'
|
||||
yield ' {a:{w}} [{b}]{c}{d}'.format(
|
||||
a = argname,
|
||||
b = argtype,
|
||||
c = " (use '-' to read from STDIN)" if have_sstr else '',
|
||||
d = ' ' + ann[argname] if isinstance(ann.get(argname),str) and not have_sstr else '',
|
||||
w = max_w )
|
||||
|
||||
if kwargs:
|
||||
max_w = max(len(k) for k in kwargs)
|
||||
max_w2 = max(len(kwargs_types[k].__name__) + len(repr(kwargs[k])) for k in kwargs) + 3
|
||||
yield ''
|
||||
yield f'Optional {KWARGS} (type and default value shown in square brackets):'
|
||||
yield ''
|
||||
for argname in kwargs:
|
||||
yield ' {a:{w}} {b:{w2}} {c}'.format(
|
||||
a = argname,
|
||||
b = '[{}={}]'.format( kwargs_types[argname].__name__, repr(kwargs[argname]) ),
|
||||
c = capfirst(ann[argname]) if isinstance(ann.get(argname),str) else '',
|
||||
w = max_w,
|
||||
w2 = max_w2 ).rstrip()
|
||||
|
||||
if '\n' in docstr:
|
||||
for line in docstr.split('\n')[1:]:
|
||||
yield line.lstrip('\t')
|
||||
|
||||
def usage(cmdname=None,exit_val=1):
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ mnemonic_fmts = {
|
|||
'bip39': mft( 'bip39', None, bip39 ),
|
||||
'xmrseed': mft( 'xmrseed', None, xmrseed ),
|
||||
}
|
||||
mn_opts_disp = options_annot_str(mnemonic_fmts)
|
||||
mn_opts_disp = 'seed phrase format ' + options_annot_str(mnemonic_fmts)
|
||||
|
||||
class tool_cmd(tool_cmd_base):
|
||||
"""
|
||||
|
|
@ -106,7 +106,10 @@ class tool_cmd(tool_cmd_base):
|
|||
f = mnemonic_fmts[fmt]
|
||||
return f.conv_cls(fmt).tohex( seed_mnemonic.split(), f.pad )
|
||||
|
||||
def mn2hex_interactive( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, mn_len=24, print_mn=False ):
|
||||
def mn2hex_interactive( self,
|
||||
fmt: mn_opts_disp = dfl_mnemonic_fmt,
|
||||
mn_len: 'length of seed phrase in words' = 24,
|
||||
print_mn: 'print the seed phrase after entry' = False ):
|
||||
"convert an interactively supplied mnemonic seed phrase to a hexadecimal string"
|
||||
from ..mn_entry import mn_entry
|
||||
mn = mn_entry(fmt).get_mnemonic_from_user(25 if fmt == 'xmrseed' else mn_len,validate=False)
|
||||
|
|
@ -119,7 +122,10 @@ class tool_cmd(tool_cmd_base):
|
|||
"show stats for a mnemonic wordlist"
|
||||
return mnemonic_fmts[fmt].conv_cls(fmt).check_wordlist()
|
||||
|
||||
def mn_printlist( self, fmt:mn_opts_disp = dfl_mnemonic_fmt, enum=False, pager=False ):
|
||||
def mn_printlist(self,
|
||||
fmt: mn_opts_disp = dfl_mnemonic_fmt,
|
||||
enum: 'enumerate the list' = False,
|
||||
pager: 'send output to pager' = False ):
|
||||
"print a mnemonic wordlist"
|
||||
ret = mnemonic_fmts[fmt].conv_cls(fmt).get_wordlist()
|
||||
if enum:
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ tool/rpc.py: JSON/RPC routines for the 'mmgen-tool' utility
|
|||
|
||||
from .common import tool_cmd_base,options_annot_str
|
||||
from ..tw.common import TwCommon
|
||||
from ..tw.txhistory import TwTxHistory
|
||||
|
||||
class tool_cmd(tool_cmd_base):
|
||||
"tracking-wallet commands using the JSON-RPC interface"
|
||||
|
|
@ -35,16 +36,19 @@ class tool_cmd(tool_cmd_base):
|
|||
r = await rpc_init( self.proto, ignore_daemon_version=True )
|
||||
return f'{r.daemon.coind_name} version {r.daemon_version} ({r.daemon_version_str})'
|
||||
|
||||
async def getbalance(self,minconf=1,quiet=False,pager=False):
|
||||
async def getbalance(self,
|
||||
minconf: 'minimum number of confirmations' = 1,
|
||||
quiet: 'produce quieter output' = False,
|
||||
pager: 'send output to pager' = False ):
|
||||
"list confirmed/unconfirmed, spendable/unspendable balances in tracking wallet"
|
||||
from ..tw.bal import TwGetBalance
|
||||
return (await TwGetBalance(self.proto,minconf,quiet)).format()
|
||||
|
||||
async def listaddress(self,
|
||||
mmgen_addr:str,
|
||||
minconf = 1,
|
||||
showbtcaddr = True,
|
||||
age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs' ):
|
||||
minconf: 'minimum number of confirmations' = 1,
|
||||
showbtcaddr: 'display coin address in addition to MMGen ID' = True,
|
||||
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs' ):
|
||||
"list the specified MMGen address in the tracking wallet and its balance"
|
||||
|
||||
return await self.listaddresses(
|
||||
|
|
@ -54,15 +58,15 @@ class tool_cmd(tool_cmd_base):
|
|||
age_fmt = age_fmt )
|
||||
|
||||
async def listaddresses(self,
|
||||
mmgen_addrs:'(range or list)' = '',
|
||||
minconf = 1,
|
||||
showempty = False,
|
||||
pager = False,
|
||||
showbtcaddrs = True,
|
||||
all_labels = False,
|
||||
sort: options_annot_str(['reverse','age']) = '',
|
||||
age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs' ):
|
||||
"list MMGen addresses and their balances"
|
||||
mmgen_addrs: 'hyphenated range or comma-separated list of addresses' = '',
|
||||
minconf: 'minimum number of confirmations' = 1,
|
||||
pager: 'send output to pager' = False,
|
||||
showbtcaddr: 'display coin addresses in addition to MMGen IDs' = True,
|
||||
showempty: 'show addresses with no balances' = True,
|
||||
all_labels: 'show all addresses with labels' = False,
|
||||
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs',
|
||||
sort: 'address sort order ' + options_annot_str(['reverse','age']) = '' ):
|
||||
"list MMGen addresses in the tracking wallet and their balances"
|
||||
|
||||
show_age = bool(age_fmt)
|
||||
|
||||
|
|
@ -111,14 +115,14 @@ class tool_cmd(tool_cmd_base):
|
|||
return await obj.format_squeezed()
|
||||
|
||||
async def twview(self,
|
||||
pager = False,
|
||||
reverse = False,
|
||||
wide = False,
|
||||
minconf = 1,
|
||||
sort = 'age',
|
||||
age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs',
|
||||
interactive = False,
|
||||
show_mmid = True ):
|
||||
pager: 'send output to pager' = False,
|
||||
reverse: 'reverse order of unspent outputs' = False,
|
||||
wide: 'display data in wide tabular format' = False,
|
||||
minconf: 'minimum number of confirmations' = 1,
|
||||
sort: 'unspent output sort order ' + options_annot_str(TwCommon.sort_funcs) = 'age',
|
||||
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs',
|
||||
interactive: 'enable interactive operation' = False,
|
||||
show_mmid: 'show MMGen IDs along with coin addresses' = True ):
|
||||
"view tracking wallet unspent outputs"
|
||||
|
||||
from ..tw.unspent import TwUnspentOutputs
|
||||
|
|
@ -129,16 +133,15 @@ class tool_cmd(tool_cmd_base):
|
|||
return ret
|
||||
|
||||
async def txhist(self,
|
||||
pager = False,
|
||||
reverse = False,
|
||||
detail = False,
|
||||
sinceblock = 0,
|
||||
sort = 'age',
|
||||
age_fmt: options_annot_str(TwCommon.age_fmts) = 'confs',
|
||||
interactive = False ):
|
||||
pager: 'send output to pager' = False,
|
||||
reverse: 'reverse order of transactions' = False,
|
||||
detail: 'produce detailed, non-tabular output' = False,
|
||||
sinceblock: 'display transactions starting from this block' = 0,
|
||||
sort: 'transaction sort order ' + options_annot_str(TwTxHistory.sort_funcs) = 'age',
|
||||
age_fmt: 'format for the Age/Date column ' + options_annot_str(TwCommon.age_fmts) = 'confs',
|
||||
interactive: 'enable interactive operation' = False ):
|
||||
"view transaction history of tracking wallet"
|
||||
|
||||
from ..tw.txhistory import TwTxHistory
|
||||
obj = await TwTxHistory(self.proto,sinceblock=sinceblock)
|
||||
return await self.twops(
|
||||
obj,pager,reverse,detail,sort,age_fmt,interactive,show_mmid=None)
|
||||
|
|
|
|||
|
|
@ -25,21 +25,64 @@ from .common import tool_cmd_base
|
|||
class tool_cmd(tool_cmd_base):
|
||||
"general string conversion and hashing utilities"
|
||||
|
||||
# mmgen.util.bytespec_map
|
||||
def bytespec(self,dd_style_byte_specifier:str):
|
||||
"convert a byte specifier such as '1GB' into an integer"
|
||||
"""
|
||||
convert a byte specifier such as ‘4GB’ into an integer
|
||||
|
||||
Valid specifiers:
|
||||
|
||||
c = 1
|
||||
w = 2
|
||||
b = 512
|
||||
kB = 1000
|
||||
K = 1024
|
||||
MB = 1000000
|
||||
M = 1048576
|
||||
GB = 1000000000
|
||||
G = 1073741824
|
||||
TB = 1000000000000
|
||||
T = 1099511627776
|
||||
PB = 1000000000000000
|
||||
P = 1125899906842624
|
||||
EB = 1000000000000000000
|
||||
E = 1152921504606846976
|
||||
"""
|
||||
from ..util import parse_bytespec
|
||||
return parse_bytespec(dd_style_byte_specifier)
|
||||
|
||||
# mmgen.util.bytespec_map
|
||||
def to_bytespec(self,
|
||||
n: int,
|
||||
dd_style_byte_specifier: str,
|
||||
fmt = '0.2',
|
||||
print_sym = True ):
|
||||
"convert an integer to a byte specifier such as '1GB'"
|
||||
fmt: 'width and precision of output' = '0.2',
|
||||
print_sym: 'print the specifier after the numerical value' = True ):
|
||||
"""
|
||||
convert an integer to a byte specifier such as ‘4GB’
|
||||
|
||||
Supported specifiers:
|
||||
|
||||
c = 1
|
||||
w = 2
|
||||
b = 512
|
||||
kB = 1000
|
||||
K = 1024
|
||||
MB = 1000000
|
||||
M = 1048576
|
||||
GB = 1000000000
|
||||
G = 1073741824
|
||||
TB = 1000000000000
|
||||
T = 1099511627776
|
||||
PB = 1000000000000000
|
||||
P = 1125899906842624
|
||||
EB = 1000000000000000000
|
||||
E = 1152921504606846976
|
||||
"""
|
||||
from ..util import int2bytespec
|
||||
return int2bytespec( n, dd_style_byte_specifier, fmt, print_sym )
|
||||
|
||||
def randhex(self,nbytes=32):
|
||||
def randhex(self,
|
||||
nbytes: 'number of bytes to output' = 32 ):
|
||||
"print 'n' bytes (default 32) of random data in hex format"
|
||||
from ..crypto import get_random
|
||||
return get_random( nbytes ).hex()
|
||||
|
|
@ -58,7 +101,10 @@ class tool_cmd(tool_cmd_base):
|
|||
"convert a hexadecimal string to bytes (warning: outputs binary data)"
|
||||
return bytes.fromhex(hexstr)
|
||||
|
||||
def hexdump(self,infile:str,cols=8,line_nums='hex'):
|
||||
def hexdump(self,
|
||||
infile: str,
|
||||
cols: 'number of columns in output' = 8,
|
||||
line_nums: "format for line numbers (valid choices: 'hex','dec')" = 'hex'):
|
||||
"create hexdump of data from file (use '-' for stdin)"
|
||||
from ..fileutil import get_data_from_file
|
||||
from ..util import pretty_hexdump
|
||||
|
|
@ -81,7 +127,11 @@ class tool_cmd(tool_cmd_base):
|
|||
from ..proto.common import hash160
|
||||
return hash160( bytes.fromhex(hexstr) ).hex()
|
||||
|
||||
def hash256(self,data:str,file_input=False,hex_input=False): # TODO: handle stdin
|
||||
# TODO: handle stdin
|
||||
def hash256(self,
|
||||
data: str,
|
||||
file_input: 'first arg is the name of a file containing the data' = False,
|
||||
hex_input: 'first arg is a hexadecimal string' = False ):
|
||||
"compute sha256(sha256(data)) (double sha256)"
|
||||
from hashlib import sha256
|
||||
if file_input:
|
||||
|
|
@ -113,30 +163,32 @@ class tool_cmd(tool_cmd_base):
|
|||
return make_chksum_8(
|
||||
get_data_from_file( infile, dash=True, quiet=True, binary=True ))
|
||||
|
||||
def randb58(self,nbytes=32,pad=0):
|
||||
def randb58(self,
|
||||
nbytes: 'number of bytes to output' = 32,
|
||||
pad: 'pad output to this width' = 0 ):
|
||||
"generate random data (default: 32 bytes) and convert it to base 58"
|
||||
from ..crypto import get_random
|
||||
from ..baseconv import baseconv
|
||||
return baseconv('b58').frombytes( get_random(nbytes), pad=pad, tostr=True )
|
||||
|
||||
def bytestob58(self,infile:str,pad=0):
|
||||
def bytestob58(self,infile:str,pad: 'pad output to this width' = 0):
|
||||
"convert bytes to base 58 (supply data via STDIN)"
|
||||
from ..fileutil import get_data_from_file
|
||||
from ..baseconv import baseconv
|
||||
data = get_data_from_file( infile, dash=True, quiet=True, binary=True )
|
||||
return baseconv('b58').frombytes( data, pad=pad, tostr=True )
|
||||
|
||||
def b58tobytes(self,b58_str:'sstr',pad=0):
|
||||
def b58tobytes(self,b58_str:'sstr',pad: 'pad output to this width' = 0):
|
||||
"convert a base 58 string to bytes (warning: outputs binary data)"
|
||||
from ..baseconv import baseconv
|
||||
return baseconv('b58').tobytes( b58_str, pad=pad )
|
||||
|
||||
def hextob58(self,hexstr:'sstr',pad=0):
|
||||
def hextob58(self,hexstr:'sstr',pad: 'pad output to this width' = 0):
|
||||
"convert a hexadecimal string to base 58"
|
||||
from ..baseconv import baseconv
|
||||
return baseconv('b58').fromhex( hexstr, pad=pad, tostr=True )
|
||||
|
||||
def b58tohex(self,b58_str:'sstr',pad=0):
|
||||
def b58tohex(self,b58_str:'sstr',pad: 'pad output to this width' = 0):
|
||||
"convert a base 58 string to hexadecimal"
|
||||
from ..baseconv import baseconv
|
||||
return baseconv('b58').tohex( b58_str, pad=pad )
|
||||
|
|
@ -151,24 +203,27 @@ class tool_cmd(tool_cmd_base):
|
|||
from ..proto.common import b58chk_decode
|
||||
return b58chk_decode(b58chk_str).hex()
|
||||
|
||||
def hextob32(self,hexstr:'sstr',pad=0):
|
||||
def hextob32(self,hexstr:'sstr',pad: 'pad output to this width' = 0):
|
||||
"convert a hexadecimal string to an MMGen-flavor base 32 string"
|
||||
from ..baseconv import baseconv
|
||||
return baseconv('b32').fromhex( hexstr, pad, tostr=True )
|
||||
|
||||
def b32tohex(self,b32_str:'sstr',pad=0):
|
||||
def b32tohex(self,b32_str:'sstr',pad: 'pad output to this width' = 0):
|
||||
"convert an MMGen-flavor base 32 string to hexadecimal"
|
||||
from ..baseconv import baseconv
|
||||
return baseconv('b32').tohex( b32_str.upper(), pad )
|
||||
|
||||
def hextob6d(self,hexstr:'sstr',pad=0,add_spaces=True):
|
||||
def hextob6d(self,
|
||||
hexstr:'sstr',
|
||||
pad: 'pad output to this width' = 0,
|
||||
add_spaces: 'add a space after every 5th character' = True):
|
||||
"convert a hexadecimal string to die roll base6 (base6d)"
|
||||
from ..baseconv import baseconv
|
||||
from ..util import block_format
|
||||
ret = baseconv('b6d').fromhex(hexstr,pad,tostr=True)
|
||||
return block_format( ret, gw=5, cols=None ).strip() if add_spaces else ret
|
||||
|
||||
def b6dtohex(self,b6d_str:'sstr',pad=0):
|
||||
def b6dtohex(self,b6d_str:'sstr',pad: 'pad output to this width' = 0):
|
||||
"convert a die roll base6 (base6d) string to hexadecimal"
|
||||
from ..baseconv import baseconv
|
||||
from ..util import remove_whitespace
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class TestSuiteHelp(TestSuiteBase):
|
|||
('longhelpscreens', (1,'help screens (--longhelp)',[])),
|
||||
('show_hash_presets', (1,'info screen (--show-hash-presets)',[])),
|
||||
('tool_help', (1,"'mmgen-tool' usage screen",[])),
|
||||
('tool_cmd_usage', (1,"'mmgen-tool' usage screen",[])),
|
||||
('test_help', (1,"'test.py' help screens",[])),
|
||||
)
|
||||
|
||||
|
|
@ -114,6 +115,19 @@ class TestSuiteHelp(TestSuiteBase):
|
|||
t = self.spawn_chk('mmgen-tool',args,extra_desc=f"('mmgen-tool {fmt_list(args,fmt='bare')}')")
|
||||
return t
|
||||
|
||||
def tool_cmd_usage(self):
|
||||
|
||||
if os.getenv('PYTHONOPTIMIZE') == '2':
|
||||
ymsg('Skipping tool cmd usage with PYTHONOPTIMIZE=2 (no docstrings)')
|
||||
return 'skip'
|
||||
|
||||
from mmgen.main_tool import mods
|
||||
|
||||
for cmdlist in mods.values():
|
||||
for cmd in cmdlist:
|
||||
t = self.spawn_chk( 'mmgen-tool', ['help',cmd], extra_desc=f'({cmd})' )
|
||||
return t
|
||||
|
||||
def test_help(self):
|
||||
for args in (
|
||||
['--help'],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue