Hilite: new hl2(), fmt2() methods; optimize fmt(), fmtc()

This commit is contained in:
The MMGen Project 2022-12-03 17:40:44 +00:00
commit 217cd7ad39
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
15 changed files with 106 additions and 81 deletions

View file

@ -172,9 +172,14 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
return self._parsed
@classmethod
def fmtc(cls,addr,**kwargs):
w = kwargs['width'] or cls.width
return super().fmtc(addr[:w-2]+'..' if w < len(addr) else addr, **kwargs)
def fmtc(cls,s,width,**kwargs):
return super().fmtc( s=s[:width-2]+'..' if len(s) > width else s, width=width, **kwargs )
def fmt(self,width,**kwargs):
return (
super().fmtc( s=self[:width-2]+'..', width=width, **kwargs ) if len(self) > width else
super().fmt( width=width, **kwargs )
)
def is_coin_addr(proto,s):
return get_obj( CoinAddr, proto=proto, addr=s, silent=True, return_bool=True )

View file

@ -99,7 +99,7 @@ def print_shares_info():
shares[0].sid,
share1.sid,
master_idx,
id_str.hl(encl="''"),
id_str.hl2(encl='‘’'),
len(shares) )
si = 1
for n,s in enumerate(shares[si:],si+1):

View file

@ -272,13 +272,15 @@ class Int(int,Hilite,InitErrors):
except Exception as e:
return cls.init_fail(e,n)
def fmt(self,**kwargs):
return super().fmtc(self.__str__(),**kwargs)
@classmethod
def fmtc(cls,s,**kwargs):
return super().fmtc(s.__str__(),**kwargs)
@classmethod
def colorize(cls,s,**kwargs):
return super().colorize(s.__str__(),**kwargs)
def hl(self,**kwargs):
return super().colorize(self.__str__(),**kwargs)
class NonNegativeInt(Int):
min_val = 0
@ -315,11 +317,10 @@ class HexStr(str,Hilite,InitErrors):
except Exception as e:
return cls.init_fail(e,s)
def truncate(self,width,color=True,color_override=''):
def truncate(self,width,color=True):
return self.colorize(
self if width == None or width >= self.width else self[:width-2] + '..',
color = color,
color_override = color_override )
self if width >= self.width else self[:width-2] + '..',
color = color )
class CoinTxID(HexStr):
color,width,hexcase = ('purple',64,'lower')

View file

@ -48,61 +48,79 @@ class Hilite:
width = 0
trunc_ok = True
# supports single-width characters only
def fmt( self, width, color=False ):
if len(self) > width:
assert self.trunc_ok, "If 'trunc_ok' is false, 'width' must be >= width of string"
return self.colorize( self[:width].ljust(width), color=color )
else:
return self.colorize( self.ljust(width), color=color )
# class method equivalent of fmt()
@classmethod
# 'width' is screen width (greater than len(s) for CJK strings)
# 'append_chars' and 'encl' must consist of single-width chars only
def fmtc(cls,s,width=None,color=False,encl='',trunc_ok=None,
center=False,nullrepl='',append_chars='',append_color=False,color_override=''):
s_wide_count = len([1 for ch in s if unicodedata.east_asian_width(ch) in ('F','W')])
if encl:
a,b = list(encl)
add_len = len(append_chars) + 2
def fmtc( cls, s, width, color=False ):
if len(s) > width:
assert cls.trunc_ok, "If 'trunc_ok' is false, 'width' must be >= width of string"
return cls.colorize( s[:width].ljust(width), color=color )
else:
a,b = ('','')
add_len = len(append_chars)
if width == None:
width = cls.width
if trunc_ok == None:
trunc_ok = cls.trunc_ok
if g.test_suite:
assert isinstance(encl,str) and len(encl) in (0,2),"'encl' must be 2-character str"
assert width >= 2 + add_len, f'{s!r}: invalid width ({width}) (must be at least 2)' # CJK: 2 cells
if len(s) + s_wide_count + add_len > width:
assert trunc_ok, "If 'trunc_ok' is false, 'width' must be >= screen width of string"
s = truncate_str(s,width-add_len)
if s == '' and nullrepl:
s = nullrepl
return cls.colorize( s.ljust(width), color=color )
# an alternative to fmt(), with double-width char support and other features
def fmt2(
self,
width, # screen width - must be at least 2 (one wide char)
color = False,
encl = '', # if set, must be exactly 2 single-width chars
nullrepl = '',
append_chars = '', # single-width chars only
append_color = False,
color_override = '' ):
if self == '':
return getattr( color_mod, self.color )(nullrepl.ljust(width)) if color else nullrepl.ljust(width)
s_wide_count = len(['' for ch in self if unicodedata.east_asian_width(ch) in ('F','W')])
a,b = encl or ('','')
add_len = len(append_chars) + len(encl)
if len(self) + s_wide_count + add_len > width:
assert self.trunc_ok, "If 'trunc_ok' is false, 'width' must be >= screen width of string"
s = a + (truncate_str(self,width-add_len) if s_wide_count else self[:width-add_len]) + b
else:
s = a+s+b
if center:
s = s.center(width)
s = a + self + b
if append_chars:
return (
cls.colorize(s,color=color)
+ cls.colorize(
self.colorize(s,color=color)
+ self.colorize2(
append_chars.ljust(width-len(s)-s_wide_count),
color_override = append_color ))
else:
return cls.colorize(s.ljust(width-s_wide_count),color=color,color_override=color_override)
return self.colorize2( s.ljust(width-s_wide_count), color=color, color_override=color_override )
@classmethod
def colorize(cls,s,color=True,color_override=''):
def colorize(cls,s,color=True):
return getattr( color_mod, cls.color )(s) if color else s
@classmethod
def colorize2(cls,s,color=True,color_override=''):
return getattr( color_mod, color_override or cls.color )(s) if color else s
def fmt(self,*args,**kwargs):
assert args == () # forbid invocation w/o keywords
return self.fmtc(self,*args,**kwargs)
def hl(self,color=True):
return getattr( color_mod, self.color )(self) if color else self
@classmethod
def hlc(cls,s,color=True,encl='',color_override=''):
if encl:
assert isinstance(encl,str) and len(encl) == 2, "'encl' must be 2-character str"
s = encl[0] + s + encl[1]
return cls.colorize(s,color=color,color_override=color_override)
def hlc(cls,s,color=True):
return getattr( color_mod, cls.color )(s) if color else s
def hl(self,*args,**kwargs):
assert args == () # forbid invocation w/o keywords
return self.hlc(self,*args,**kwargs)
# an alternative to hl(), with enclosure and color override
# can be called as an unbound method with class as first argument
def hl2(self,s=None,color=True,encl='',color_override=''):
if encl:
return self.colorize2( encl[0]+(s or self)+encl[1], color=color, color_override=color_override )
else:
return self.colorize2( (s or self), color=color, color_override=color_override )
class InitErrors:

View file

@ -141,8 +141,8 @@ class BitcoinTwTransaction:
def txdate_disp(self,age_fmt):
return self.parent.date_formatter[age_fmt](self.rpc,self.time)
def txid_disp(self,width,color):
return self.txid.truncate(width=width,color=color)
def txid_disp(self,color,width=None):
return self.txid.hl(color=color) if width == None else self.txid.truncate(width=width,color=color)
def vouts_list_disp(self,src,color,indent=''):
@ -167,8 +167,9 @@ class BitcoinTwTransaction:
yield fs2.format(
i = CoinTxID(e.txid).hl(color=color),
n = (nocolor,red)[color](str(e.data['n']).ljust(3)),
a = TwMMGenID.hlc(
'{:{w}}'.format( addr_out + bal_star, w=self.max_addrlen[src] ),
a = TwMMGenID.hl2(
TwMMGenID,
s = '{:{w}}'.format( addr_out + bal_star, w=self.max_addrlen[src] ),
color = color,
color_override = co ),
A = self.proto.coin_amt( e.data['value'] ).fmt(color=color),
@ -194,13 +195,14 @@ class BitcoinTwTransaction:
mmid_disp = mmid + bal_star
if width and x.space_left < len(mmid_disp):
break
yield TwMMGenID.hlc( mmid_disp, color=color, color_override=co )
yield TwMMGenID.hl2( TwMMGenID, s=mmid_disp, color=color, color_override=co )
x.space_left -= len(mmid_disp)
else:
if width and x.space_left < addr_w:
break
yield TwMMGenID.hlc(
CoinAddr.fmtc( mmid.split(':',1)[1] + bal_star, width=addr_w ),
yield TwMMGenID.hl2(
TwMMGenID,
s = CoinAddr.fmtc( mmid.split(':',1)[1] + bal_star, width=addr_w ),
color = color,
color_override = co )
x.space_left -= addr_w

View file

@ -74,7 +74,7 @@ class TxInfo(TxInfo):
confs = e.confs + blockcount - tx.blockcount
days = int(confs // confs_per_day)
if e.mmid:
mmid_fmt = e.mmid.fmt(
mmid_fmt = e.mmid.fmt2(
width=max_mmwid,
encl='()',
color=True,

View file

@ -183,7 +183,7 @@ class SeedShareBase(MMGenObject):
m = ( yellow("(share {} of {} of ")
+ pl.parent_seed.sid.hl()
+ yellow(', split id ')
+ pl.id_str.hl(encl="''")
+ pl.id_str.hl2(encl='‘’')
+ yellow('{})') )
else:
m = "share {} of {} of " + pl.parent_seed.sid + ", split id '" + pl.id_str + "'{}"

View file

@ -184,7 +184,7 @@ class TwAddresses(TwView):
m = d.twmmid.fmt( width=cw.mmid, color=color ),
u = yes if d.recvd else no,
a = d.addr.fmt( color=color, width=cw.addr ),
c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ),
c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ),
A = d.amt.fmt( color=color, iwidth=cw.iwidth, prec=self.disp_prec ),
d = self.age_disp( d, self.age_fmt )
)
@ -195,7 +195,7 @@ class TwAddresses(TwView):
m = d.twmmid.fmt( width=cw.mmid, color=color ),
u = yes if d.recvd else no,
a = d.addr.fmt( color=color, width=cw.addr ),
c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ),
c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ),
A = d.amt.fmt( color=color, iwidth=cw.iwidth, prec=self.disp_prec ),
b = self.age_disp( d, 'block' ),
D = self.age_disp( d, 'date_time' ))

View file

@ -279,7 +279,7 @@ class TwCtl(MMGenObject,metaclass=AsyncInit):
res.twmmid.type.replace('mmgen','MMGen'),
res.twmmid.addr.hl() )
if comment:
msg('Added label {} to {}'.format(comment.hl(encl="''"),desc))
msg('Added label {} to {}'.format(comment.hl2(encl='‘’'),desc))
else:
msg(f'Removed label from {desc}')
return True

View file

@ -46,9 +46,8 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
me.proto = proto
return me
@classmethod
def fmtc(cls,twmmid,*args,**kwargs):
return super().fmtc(twmmid.disp,*args,**kwargs)
def fmt(self,**kwargs):
return super().fmtc(self.disp,**kwargs)
# non-displaying container for TwMMGenID,TwComment
class TwLabel(str,InitErrors,MMGenObject):

View file

@ -133,7 +133,7 @@ class TwTxHistory(TwView):
i = d.vouts_disp( 'inputs', width=cw.inputs, color=color ),
A = d.amt_disp(self.show_total_amt).fmt( iwidth=cw.iwidth, prec=self.disp_prec, color=color ),
o = d.vouts_disp( 'outputs', width=cw.outputs, color=color ),
c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ) )
c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ) )
def gen_detail_display(self,data,cw,fs,color,fmt_method):
@ -156,7 +156,7 @@ class TwTxHistory(TwView):
d = d.age_disp( 'date_time', width=None, color=None ),
b = d.blockheight_disp(color=color),
D = d.txdate_disp( 'date_time' ),
t = d.txid_disp( width=None, color=color ),
t = d.txid_disp( color=color ),
A = d.amt_disp(show_total_amt=True).hl( color=color ),
B = d.amt_disp(show_total_amt=False).hl( color=color ),
f = d.fee_disp( color=color ),

View file

@ -31,7 +31,7 @@ from ..obj import (
HexStr,
CoinTxID,
NonNegativeInt )
from ..addr import CoinAddr,MMGenID
from ..addr import CoinAddr
from .shared import TwMMGenID,get_tw_label
from .view import TwView
@ -183,14 +183,14 @@ class TwUnspentOutputs(TwView):
for n,d in enumerate(data):
yield fs.format(
n = str(n+1) + ')',
t = (CoinTxID.fmtc('|' + '.'*(cw.txid-1),color=color) if d.skip == 'txid'
t = (d.txid.fmtc( '|' + '.'*(cw.txid-1), width=d.txid.width, color=color ) if d.skip == 'txid'
else d.txid.truncate( width=cw.txid, color=color )) if cw.txid else None,
v = ' ' + d.vout.fmt( width=cw.vout-1, color=color ) if cw.vout else None,
a = d.addr.fmtc( '|' + '.'*(cw.addr-1), width=cw.addr, color=color ) if d.skip == 'addr'
else d.addr.fmt( width=cw.addr, color=color ),
m = (MMGenID.fmtc( '.'*cw.mmid, color=color ) if d.skip == 'addr'
m = (d.twmmid.fmtc( '.'*cw.mmid, color=color ) if d.skip == 'addr'
else d.twmmid.fmt( width=cw.mmid, color=color )) if cw.mmid else None,
c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ) if cw.comment else None,
c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ) if cw.comment else None,
A = d.amt.fmt( color=color, iwidth=cw.iwidth, prec=self.disp_prec ),
B = d.amt2.fmt( color=color, iwidth=cw.iwidth2, prec=self.disp_prec ) if cw.amt2 else None,
d = self.age_disp(d,self.age_fmt),
@ -201,7 +201,7 @@ class TwUnspentOutputs(TwView):
for n,d in enumerate(data):
yield fs.format(
n = str(n+1) + ')',
t = d.txid.fmt( color=color ) if cw.txid else None,
t = d.txid.fmt( width=d.txid.width, color=color ) if cw.txid else None,
v = ' ' + d.vout.fmt( width=cw.vout-1, color=color ) if cw.vout else None,
a = d.addr.fmt( width=cw.addr, color=color ),
m = d.twmmid.fmt( width=cw.mmid, color=color ),
@ -209,7 +209,7 @@ class TwUnspentOutputs(TwView):
B = d.amt2.fmt( color=color, iwidth=cw.iwidth2, prec=self.disp_prec ) if cw.amt2 else None,
b = self.age_disp(d,'block'),
D = self.age_disp(d,'date_time'),
c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ))
c = d.comment.fmt2( width=cw.comment, color=color, nullrepl='-' ))
def display_total(self):
msg('\nTotal unspent: {} {} ({} output{})'.format(

View file

@ -41,7 +41,7 @@ class wallet(wallet):
# logic identical to _get_hash_preset_from_user()
def _get_label_from_user(self,old_lbl=''):
prompt = 'Enter a wallet label, or hit ENTER {}: '.format(
'to reuse the label {}'.format(old_lbl.hl(encl="''")) if old_lbl else
'to reuse the label {}'.format(old_lbl.hl2(encl='‘’')) if old_lbl else
'for no label' )
from ..ui import line_input
while True:
@ -61,17 +61,17 @@ class wallet(wallet):
old_lbl = self.ss_in.ssdata.label
if opt.keep_label:
lbl = old_lbl
qmsg('Reusing label {} at user request'.format( lbl.hl(encl="''") ))
qmsg('Reusing label {} at user request'.format( lbl.hl2(encl='‘’') ))
elif self.label:
lbl = self.label
qmsg('Using label {} requested on command line'.format( lbl.hl(encl="''") ))
qmsg('Using label {} requested on command line'.format( lbl.hl2(encl='‘’') ))
else: # Prompt, using old value as default
lbl = self._get_label_from_user(old_lbl)
if (not opt.keep_label) and self.op == 'pwchg_new':
qmsg('Label {}'.format( 'unchanged' if lbl == old_lbl else f'changed to {lbl!r}' ))
elif self.label:
lbl = self.label
qmsg('Using label {} requested on command line'.format( lbl.hl(encl="''") ))
qmsg('Using label {} requested on command line'.format( lbl.hl2(encl='‘’') ))
else:
lbl = self._get_label_from_user()
self.ssdata.label = lbl

View file

@ -910,7 +910,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
res = await tx.get_receipt(txid)
imsg(f'Gas sent: {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')
imsg(f'Gas used: {res.gas_used.hl():<9} {(res.gas_used*res.gas_price).hl2(encl="()")}')
imsg(f'Gas price: {res.gas_price.hl2()}')
imsg(f'Gas price: {res.gas_price.hl()}')
if res.gas_used == res.gas_sent:
omsg(yellow(f'Warning: all gas was used!'))
return res

View file

@ -107,7 +107,7 @@ class TestSuiteSeedSplit(TestSuiteBase):
if spec:
from mmgen.seedsplit import SeedSplitSpecifier
sss = SeedSplitSpecifier(spec)
pat = rf'Processing .*\b{sss.idx}\b of \b{sss.count}\b of .* id .*{sss.id!r}'
pat = rf'Processing .*\b{sss.idx}\b of \b{sss.count}\b of .* id .*{sss.id}'
else:
pat = f'master share #{master}'
t.expect(pat,regex=True)
@ -144,7 +144,7 @@ class TestSuiteSeedSplit(TestSuiteBase):
if icls:
t.passphrase(icls.desc,sh1_passwd)
if master:
fs = "master share #{}, split id.*'{}'.*, share count {}"
fs = "master share #{}, split id.*{}.*, share count {}"
pat = fs.format(
master,
id_str or 'default',