str.split() + match statement (11 files)

This commit is contained in:
The MMGen Project 2025-09-26 10:40:24 +00:00
commit 2e5ec4c9e8
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
13 changed files with 150 additions and 129 deletions

View file

@ -129,15 +129,18 @@ class MMGenID(HiliteStr, InitErrors, MMGenObject):
trunc_ok = False
def __new__(cls, proto, id_str):
try:
ss = str(id_str).split(':')
assert len(ss) in (2, 3), 'not 2 or 3 colon-separated items'
t = proto.addr_type((ss[1], proto.dfl_mmtype)[len(ss)==2])
me = str.__new__(cls, f'{ss[0]}:{t}:{ss[-1]}')
me.sid = SeedID(sid=ss[0])
me.idx = AddrIdx(ss[-1])
me.mmtype = t
assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}'
me.al_id = str.__new__(AddrListID, me.sid+':'+me.mmtype) # checks already done
match id_str.split(':', 2):
case [sid, mmtype, idx]:
assert mmtype in proto.mmtypes, f'{mmtype}: invalid address type for {proto.cls_name}'
case [sid, idx]:
mmtype = proto.dfl_mmtype
case _:
raise ValueError('not 2 or 3 colon-separated items')
me = str.__new__(cls, f'{sid}:{mmtype}:{idx}')
me.sid = SeedID(sid=sid)
me.mmtype = proto.addr_type(mmtype)
me.idx = AddrIdx(idx)
me.al_id = str.__new__(AddrListID, me.sid + ':' + me.mmtype) # checks already done
me.sort_key = f'{me.sid}:{me.mmtype}:{me.idx:0{me.idx.max_digits}}'
me.proto = proto
return me

View file

@ -265,10 +265,12 @@ class AddrFile(MMGenObject):
match len(ls):
case 2 if type(p).__name__ == 'PasswordList':
ss = ls.pop().split(':')
assert len(ss) == 2, f'{ss!r}: invalid password length specifier (must contain colon)'
p.set_pw_fmt(ss[0])
p.set_pw_len(ss[1])
match ls.pop().split(':', 1):
case [a, b]:
p.set_pw_fmt(a)
p.set_pw_len(b)
case x:
die(1, f'{x!r}: invalid password length specifier (must contain colon)')
p.pw_id_str = MMGenPWIDString(ls.pop())
modname, funcname = p.pw_info[p.pw_fmt].chk_func.split('.')
import importlib

View file

@ -17,28 +17,27 @@ def parse_data():
'idx chain name')
def parse_line(line):
l = line.split()
if l[2] == '-':
return _u(
idx = int(l[0]),
chain = l[1],
name = ' '.join(l[3:]),
)
else:
return _d(
idx = int(l[0]),
chain = l[1],
curve = defaults.curve if l[2] == 'x' else l[2],
network = 'mainnet' if l[3] == 'm' else 'testnet' if l[3] == 'T' else None,
addr_cls = l[4],
vb_prv = defaults.vb_prv if l[5] == 'x' else l[5],
vb_pub = defaults.vb_pub if l[6] == 'x' else l[6],
vb_wif = l[7],
vb_addr = l[8],
def_path = defaults.def_path if l[9] == 'x' else l[9],
name = ' '.join(l[10:]),
)
match line.split():
case [idx, chain, col3, *name] if col3 == '-':
return _u(
idx = int(idx),
chain = chain,
name = ' '.join(name))
case [idx, chain, curve, net, acls, vprv, vpub, vwif, vaddr, dpath, *name]:
return _d(
idx = int(idx),
chain = chain,
curve = defaults.curve if curve == 'x' else curve,
network = 'mainnet' if net == 'm' else 'testnet' if net == 'T' else None,
addr_cls = acls,
vb_prv = defaults.vb_prv if vprv == 'x' else vprv,
vb_pub = defaults.vb_pub if vpub == 'x' else vpub,
vb_wif = vwif,
vb_addr = vaddr,
def_path = defaults.def_path if dpath == 'x' else dpath,
name = ' '.join(name))
case _:
raise ValueError(f'{line!r}: invalid line')
out = {}
for line in _data_in.strip().splitlines():

View file

@ -848,28 +848,28 @@ def check_opts(cfg): # Raises exception if any check fails
out_fmt = in_fmt
def hidden_incog_params():
a = val.rsplit(',', 1) # permit comma in filename
if len(a) != 2:
display_opt(name, val)
die('UserOptError', 'Option requires two comma-separated arguments')
fn, offset = a
opt_is_int(offset)
match val.rsplit(',', 1): # permit comma in filename
case [fn, offset]:
opt_is_int(offset)
case _:
display_opt(name, val)
die('UserOptError', 'Option requires two comma-separated arguments')
from .fileutil import check_infile, check_outdir, check_outfile
if name == 'hidden_incog_input_params':
check_infile(fn, blkdev_ok=True)
key2 = 'in_fmt'
else:
try:
os.stat(fn)
except:
b = os.path.dirname(fn)
if b:
check_outdir(b)
else:
check_outfile(fn, blkdev_ok=True)
key2 = 'out_fmt'
match name:
case 'hidden_incog_input_params':
check_infile(fn, blkdev_ok=True)
key2 = 'in_fmt'
case 'hidden_incog_output_params':
try:
os.stat(fn)
except:
b = os.path.dirname(fn)
if b:
check_outdir(b)
else:
check_outfile(fn, blkdev_ok=True)
key2 = 'out_fmt'
if hasattr(cfg, key2):
val2 = getattr(cfg, key2)
@ -895,17 +895,19 @@ def check_opts(cfg): # Raises exception if any check fails
opt_is_in_list(val, list(Crypto.hash_presets.keys()))
def brain_params():
a = val.split(',')
if len(a) != 2:
display_opt(name, val)
die('UserOptError', 'Option requires two comma-separated arguments')
opt_is_int(a[0], desc_pfx='seed length')
from .seed import Seed
opt_is_in_list(int(a[0]), Seed.lens, desc_pfx='seed length')
from .crypto import Crypto
opt_is_in_list(a[1], list(Crypto.hash_presets.keys()), desc_pfx='hash preset')
match val.split(',', 1):
case [seed_len, hash_preset]:
opt_is_int(seed_len, desc_pfx='seed length')
from .seed import Seed
opt_is_in_list(int(seed_len), Seed.lens, desc_pfx='seed length')
from .crypto import Crypto
opt_is_in_list(
hash_preset,
list(Crypto.hash_presets.keys()),
desc_pfx = 'hash preset')
case _:
display_opt(name, val)
die('UserOptError', 'Option requires two comma-separated arguments')
def usr_randchars():
if val != 0:

View file

@ -1 +1 @@
16.1.dev0
16.1.dev1

View file

@ -33,15 +33,19 @@ class MMGenIDRange(HiliteStr, InitErrors, MMGenObject):
from .addr import AddrListID
from .seed import SeedID
try:
ss = str(id_str).split(':')
assert len(ss) in (2, 3), 'not 2 or 3 colon-separated items'
t = proto.addr_type((ss[1], proto.dfl_mmtype)[len(ss)==2])
me = str.__new__(cls, '{}:{}:{}'.format(ss[0], t, ss[-1]))
me.sid = SeedID(sid=ss[0])
me.idxlist = AddrIdxList(fmt_str=ss[-1])
me.mmtype = t
assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}'
me.al_id = str.__new__(AddrListID, me.sid+':'+me.mmtype) # checks already done
match id_str.split(':'):
case [sid, t, fmt_str]:
assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}'
mmtype = proto.addr_type(t)
case [sid, fmt_str]:
mmtype = proto.addr_type(proto.dfl_mmtype)
case _:
raise ValueError('not 2 or 3 colon-separated items')
me = str.__new__(cls, f'{sid}:{mmtype}:{fmt_str}')
me.sid = SeedID(sid=sid)
me.idxlist = AddrIdxList(fmt_str=fmt_str)
me.mmtype = mmtype
me.al_id = str.__new__(AddrListID, me.sid + ':' + me.mmtype) # checks already done
me.proto = proto
return me
except Exception as e:

View file

@ -223,18 +223,19 @@ class MMGenRange(tuple, InitErrors, MMGenObject):
def __new__(cls, *args):
try:
if len(args) == 1:
s = args[0]
if isinstance(s, cls):
return s
assert isinstance(s, str), 'not a string or string subclass'
ss = s.split('-', 1)
first = int(ss[0])
last = int(ss.pop())
else:
s = repr(args) # needed if exception occurs
assert len(args) == 2, 'one format string arg or two start, stop args required'
first, last = args
match args:
case [str(s)]:
match s.split('-', 1):
case [first]:
last = first
case [first, last]:
pass
first = int(first)
last = int(last)
case [int(first), int(last)]:
pass
case _:
raise ValueError('one format string arg or two integer args (start, stop) required')
assert first <= last, 'start of range greater than end of range'
if cls.min_idx is not None:
assert first >= cls.min_idx, f'start of range < {cls.min_idx:,}'
@ -242,7 +243,7 @@ class MMGenRange(tuple, InitErrors, MMGenObject):
assert last <= cls.max_idx, f'end of range > {cls.max_idx:,}'
return tuple.__new__(cls, (first, last))
except Exception as e:
return cls.init_fail(e, s)
return cls.init_fail(e, args)
@property
def first(self):

View file

@ -43,13 +43,16 @@ class SeedSplitSpecifier(HiliteStr, InitErrors, MMGenObject):
if isinstance(s, cls):
return s
try:
arr = s.split(':')
assert len(arr) in (2, 3), 'cannot be parsed'
a, b, c = arr if len(arr) == 3 else ['default'] + arr
me = str.__new__(cls, s)
me.id = SeedSplitIDString(a)
me.idx = SeedShareIdx(b)
me.count = SeedShareCount(c)
match s.split(':', 2):
case [id_str, idx, count]:
me.id = SeedSplitIDString(id_str)
case [idx, count]:
me.id = SeedSplitIDString('default')
case _:
raise ValueError('seed split specifier cannot be parsed')
me.idx = SeedShareIdx(idx)
me.count = SeedShareCount(count)
assert me.idx <= me.count, 'share index greater than share count'
return me
except Exception as e:

View file

@ -111,13 +111,15 @@ class TwAddresses(TwView):
self.used_w = 4 if self.has_used else 0
if mmgen_addrs:
a = mmgen_addrs.rsplit(':', 1)
if len(a) != 2:
die(1,
f'{mmgen_addrs}: invalid address list argument ' +
'(must be in form <seed ID>:[<type>:]<idx list>)')
from ..addrlist import AddrIdxList
self.usr_addr_list = [MMGenID(self.proto, f'{a[0]}:{i}') for i in AddrIdxList(fmt_str=a[1])]
match mmgen_addrs.rsplit(':', 1):
case [mmid, fmt_str]:
from ..addrlist import AddrIdxList
self.usr_addr_list = [
MMGenID(self.proto, f'{mmid}:{i}') for i in AddrIdxList(fmt_str=fmt_str)]
case _:
die(1,
f'{mmgen_addrs}: invalid address list argument ' +
'(must be in form <seed ID>:[<type>:]<idx list>)')
else:
self.usr_addr_list = []

View file

@ -56,9 +56,12 @@ class TwLabel(str, InitErrors, MMGenObject):
if isinstance(text, cls):
return text
try:
ts = text.split(None, 1)
mmid = TwMMGenID(proto, ts[0])
comment = TwComment(ts[1] if len(ts) == 2 else '')
match text.split(None, 1):
case [mmid_in]:
comment = TwComment('')
case [mmid_in, comment]:
comment = TwComment(comment)
mmid = TwMMGenID(proto, mmid_in)
me = str.__new__(cls, mmid + (' ' + comment if comment else ''))
me.mmid = mmid
me.comment = comment

View file

@ -125,28 +125,31 @@ class wallet(wallet):
self.check_usr_seed_len(bitlen=int(d3))
d.pw_status, d.timestamp = d4, d5
hpdata = lines[3].split()
match lines[3].split():
case [hp_lbl, *params] if len(params) == 3:
d.hash_preset = hp_lbl.removesuffix(':')
case _:
raise ValueError(f'{lines[3]}: invalid hash preset line')
d.hash_preset = hp = hpdata[0][:-1] # a string!
self.cfg._util.qmsg(f'Hash preset of wallet: {hp!r}')
if self.cfg.hash_preset and self.cfg.hash_preset != hp:
self.cfg._util.qmsg(f'Hash preset of wallet: {d.hash_preset!r}')
if self.cfg.hash_preset and self.cfg.hash_preset != d.hash_preset:
self.cfg._util.qmsg(f'Warning: ignoring user-requested hash preset {self.cfg.hash_preset!r}')
hash_params = tuple(map(int, hpdata[1:]))
if hash_params != self.crypto.get_hash_params(d.hash_preset):
msg(f'Hash parameters {" ".join(hash_params)!r} don’t match hash preset {d.hash_preset!r}')
if tuple(map(int, params)) != self.crypto.get_hash_params(d.hash_preset):
msg(f'Hash parameters {" ".join(params)!r} don’t match hash preset {d.hash_preset!r}')
return False
lmin, _, lmax = sorted(baseconv('b58').seedlen_map_rev) # 22, 33, 44
for i, key in (4, 'salt'), (5, 'enc_seed'):
l = lines[i].split(' ')
chksum = l.pop(0)
b58_val = ''.join(l)
if len(b58_val) < lmin or len(b58_val) > lmax:
msg(f'Invalid format for {key} in {self.desc}: {l}')
return False
match lines[i].split(' '):
case [chksum, *b58_chunks]:
b58_val = ''.join(b58_chunks)
if len(b58_val) < lmin or len(b58_val) > lmax:
msg(f'Invalid format for {key} in {self.desc}: {lines[i]}')
return False
case _:
msg(f'Invalid format for {key} in {self.desc}: {lines[i]}')
return False
if not self.cfg._util.compare_chksums(
chksum,

View file

@ -28,13 +28,12 @@ class wallet(wallet):
self.fmt_data = f'{self.ssdata.chksum} {split_into_cols(4, seed_hex)}\n'
def _deformat(self):
d = self.fmt_data.split()
try:
d[1]
chksum, hex_str = d[0], ''.join(d[1:])
except:
msg(f'{self.fmt_data.strip()!r}: invalid {self.desc}')
return False
match self.fmt_data.split():
case [chksum, *hex_chunks] if hex_chunks:
hex_str = ''.join(hex_chunks)
case _:
msg(f'{self.fmt_data!r}: invalid {self.desc}')
return False
if not len(hex_str) * 4 in Seed.lens:
msg(f'Invalid data length ({len(hex_str)}) in {self.desc}')

View file

@ -227,7 +227,7 @@ tests = {
{'text': 'F00BAA12:Z:99', 'proto': proto},
{'text': tw_pfx+' x', 'proto': proto},
{'text': tw_pfx+'я x', 'proto': proto},
{'text': utf8_ctrl[:40], 'proto': proto},
{'text': utf8_ctrl[:40], 'proto': proto, 'exc_name': 'BadTwComment'},
{'text': 'F00BAA12:S:1 ' + utf8_ctrl[:40], 'proto': proto, 'exc_name': 'BadTwComment'},
{'text': tw_pfx+'x comment', 'proto': proto},
),