tw.common: dynamic format strings; tw.unspent: reimplement display

This commit is contained in:
The MMGen Project 2022-11-11 09:48:58 +00:00
commit f44078a187
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
10 changed files with 201 additions and 194 deletions

View file

@ -1 +1 @@
13.3.dev17
13.3.dev18

View file

@ -45,11 +45,6 @@ Actions: [q]uit, r[e]draw, add [l]abel:
'p':'a_print_detail',
'l':'a_comment_add' }
squeezed_fs_fs = ' {{n:>{nw}}} {{m}} {{u}}%s {{c}} {{A}} {{d}}'
squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}}%s {{c:{cw}}} {{A:{Aw}}} {{d}}'
wide_fs_fs = ' {{n:>{nw}}} {{m}} {{u}} {{a}} {{c}} {{A}} {{b:<{bw}}} {{D}}'
wide_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}} {{a:{aw}}} {{c:{cw}}} {{A:{Aw}}} {{b:{bw}}} {{D}}'
async def get_rpc_data(self):
msg_r('Getting unspent outputs...')

View file

@ -253,9 +253,6 @@ Actions: [q]uit, r[e]draw:
'p':'a_print_squeezed',
'P':'a_print_detail' }
squeezed_fs_fs = ' {{n:>{nw}}} {{d:>{dw}}} {txid_fs}{{i}} {{A}} {{o}} {{c}}'
squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{d:{dw}}} {txid_fs}{{i:{iw}}} {{A}} {{o:{ow}}} {{c}}'
async def get_rpc_data(self):
blockhash = (
await self.rpc.call( 'getblockhash', self.sinceblock )

View file

@ -52,11 +52,6 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel:
'w':'a_view_detail',
'l':'a_comment_add' }
col_adj = 38
squeezed_fs_fs = ' {{n:{cw}}} {{t:{tw}}} {{v:2}} {{a}} {{A}} {{c:<}}'
squeezed_hdr_fs_fs = ' {{n:{cw}}} {{t:{tw}}} {{a}} {{A}} {{c:<}}'
wide_fs_fs = ' {{n:4}} {{t:{tw}}} {{a}} {{m}} {{A:{aw}}} {cf}{{b:<8}} {{D:<19}} {{l}}'
async def get_rpc_data(self):
# bitcoin-cli help listunspent:
# Arguments:

View file

@ -40,10 +40,30 @@ Actions: [q]uit, r[e]draw, [D]elete address, add [l]abel:
'w':'a_view_detail',
'p':'a_print_detail' }
squeezed_fs_fs = ' {{n:>{nw}}} {{m:}}%s {{c:}} {{A:}}'
squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}}%s {{c:{cw}}} {{A:{Aw}}}'
wide_fs_fs = ' {{n:>{nw}}} {{m:}} {{a:}} {{c:}} {{A:}}'
wide_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{a:{aw}}} {{c:{cw}}} {{A:{Aw}}}'
def get_column_widths(self,data,wide=False):
return self.compute_column_widths(
widths = { # fixed cols
'num': max(2,len(str(len(data)))+1),
'mmid': max(len(d.twmmid.disp) for d in data),
'used': 0,
'amt': self.disp_prec + 5,
'date': 0,
'block': 0,
'date_time': 0,
'spc': 5, # 4 spaces between cols + 1 leading space in fs
},
maxws = { # expandable cols
'addr': max(len(d.addr) for d in data) if self.showcoinaddrs else 0,
'comment': max(d.comment.screen_width for d in data),
},
minws = {
'addr': 12 if self.showcoinaddrs else 0,
'comment': len('Comment'),
},
maxws_nice = {'addr': 18},
wide = wide,
)
async def get_rpc_data(self):

View file

@ -20,20 +20,28 @@
proto.eth.twuo: Ethereum tracking wallet unspent outputs class
"""
from ....globalvars import g
from ....tw.common import TwLabel
from ....tw.unspent import TwUnspentOutputs
from .common import EthereumTwCommon
# No unspent outputs with Ethereum, but naming must be consistent
class EthereumTwUnspentOutputs(TwUnspentOutputs):
class EthereumTwUnspentOutputs(EthereumTwCommon,TwUnspentOutputs):
class display_type(TwUnspentOutputs.display_type):
class squeezed(TwUnspentOutputs.display_type.squeezed):
cols = ('num','addr','mmid','comment','amt','amt2')
class detail(TwUnspentOutputs.display_type.detail):
cols = ('num','addr','mmid','amt','amt2','comment')
class MMGenTwUnspentOutput(TwUnspentOutputs.MMGenTwUnspentOutput):
valid_attrs = {'txid','vout','amt','amt2','comment','twmmid','addr','confs','skip'}
invalid_attrs = {'proto'}
token_cls = False
has_age = False
can_group = False
col_adj = 29
hdr_lbl = 'tracked accounts'
desc = 'account balances'
item_desc = 'account'
@ -59,17 +67,35 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view,
'D':'a_addr_delete',
'R':'a_balance_refresh' }
squeezed_fs_fs = squeezed_hdr_fs_fs = ' {{n:{cw}}} {{a}} {{A}}'
wide_fs_fs = ' {{n:4}} {{a}} {{m}} {{A:{aw}}} {{l}}'
no_data_errmsg = 'No accounts in tracking wallet!'
def subheader(self,color):
if g.cached_balances:
from ....color import nocolor,yellow
return (nocolor,yellow)[color](
'WARNING: Using cached balances. These may be out of date!') + '\n'
else:
return ''
def get_column_widths(self,data,wide=False):
# min screen width: 80 cols
# num addr [mmid] [comment] amt [amt2]
return self.compute_column_widths(
widths = { # fixed cols
'num': max(2,len(str(len(data)))+1),
'mmid': max(len(d.twmmid.disp) for d in data) if self.show_mmid else 0,
'amt': self.disp_prec + 5,
'amt2': self.disp_prec + 5 if self.token_cls else 0,
'spc': (5 if self.show_mmid else 3) + self.token_cls, # 5(3) spaces in fs
'txid': 0,
'vout': 0,
'block': 0,
'date': 0,
'date_time': 0,
},
maxws = { # expandable cols
'addr': max(len(d.addr) for d in data),
'comment': max(d.comment.screen_width for d in data) if self.show_mmid else 0,
},
minws = {
'addr': 10,
'comment': len('Comment') if self.show_mmid else 0,
},
maxws_nice = {'addr': 14} if self.show_mmid else {},
wide = wide,
)
def do_sort(self,key=None,reverse=False):
if key == 'txid': return
@ -86,15 +112,10 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view,
'confirmations': 0, # TODO
} for d in wl]
def age_disp(self,o,age_fmt): # TODO
pass
class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
prompt_fs = 'Total to spend: {} {}\n\n'
col_adj = 37
squeezed_fs_fs = squeezed_hdr_fs_fs = ' {{n:{cw}}} {{a}} {{A}} {{A2}}'
wide_fs_fs = ' {{n:4}} {{a}} {{m}} {{A:{aw}}} {{A2:{aw}}} {{l}}'
token_cls = True
async def __init__(self,proto,*args,**kwargs):
await super().__init__(proto,*args,**kwargs)

View file

@ -37,6 +37,14 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
all_labels = False
no_data_errmsg = 'No addresses in tracking wallet!'
class display_type(TwCommon.display_type):
class squeezed(TwCommon.display_type.squeezed):
cols = ('num','mmid','used','addr','comment','amt','date')
class detail(TwCommon.display_type.detail):
cols = ('num','mmid','used','addr','comment','amt','block','date_time')
class TwAddress(MMGenListItem):
valid_attrs = {'twmmid','addr','al_id','confs','comment','amt','recvd','date','skip'}
invalid_attrs = {'proto'}
@ -153,20 +161,7 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
else:
return ''
def gen_squeezed_display(self,data,cw,color):
fs_parms = {
'nw': cw.num,
'mw': cw.mmid,
'uw': cw.used,
'aw': cw.addr,
'cw': cw.comment,
'Aw': cw.amt,
'dw': cw.date
}
hdr_fs = (self.squeezed_hdr_fs_fs % ('',' {{a:{aw}}}')[self.showcoinaddrs]).format(**fs_parms)
fs = (self.squeezed_fs_fs % ('',' {{a}}')[self.showcoinaddrs]).format(**fs_parms)
def gen_squeezed_display(self,data,cw,hdr_fs,fs,color):
yield hdr_fs.format(
n = '',
@ -194,21 +189,7 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
d = self.age_disp( d, self.age_fmt )
)
def gen_detail_display(self,data,cw,color):
fs_parms = {
'nw': cw.num,
'mw': cw.mmid,
'uw': cw.used,
'aw': cw.addr,
'cw': cw.comment,
'Aw': cw.amt,
'bw': self.age_col_params['block'][0],
'Dw': self.age_col_params['date_time'][0],
}
hdr_fs = self.wide_hdr_fs_fs.format(**fs_parms)
fs = self.wide_fs_fs.format(**fs_parms)
def gen_detail_display(self,data,cw,hdr_fs,fs,color):
yield hdr_fs.format(
n = '',

View file

@ -42,6 +42,24 @@ class TwCommon:
_display_data = {}
filters = ()
fp = namedtuple('fs_params',['fs_key','hdr_fs_repl','fs_repl','hdr_fs','fs'])
fs_params = {
'num': fp('n', True, True, ' {n:>%s}', ' {n:>%s}'),
'txid': fp('t', True, False, ' {t:%s}', ' {t}'),
'vout': fp('v', True, False, '{v:%s}', '{v}'),
'used': fp('u', True, False, ' {u:%s}', ' {u}'),
'addr': fp('a', True, False, ' {a:%s}', ' {a}'),
'mmid': fp('m', True, False, ' {m:%s}', ' {m}'),
'comment': fp('c', True, False, ' {c:%s}', ' {c}'),
'amt': fp('A', True, False, ' {A:%s}', ' {A}'),
'amt2': fp('B', True, False, ' {B:%s}', ' {B}'),
'date': fp('d', True, True, ' {d:%s}', ' {d:<%s}'),
'date_time': fp('D', True, True, ' {D:%s}', ' {D:%s}'),
'block': fp('b', True, True, ' {b:%s}', ' {b:<%s}'),
'inputs': fp('i', True, False, ' {i:%s}', ' {i}'),
'outputs': fp('o', True, False, ' {o:%s}', ' {o}'),
}
age_fmts = ('confs','block','days','date','date_time')
age_fmts_date_dependent = ('days','date','date_time')
age_fmts_interactive = ('confs','block','days','date','date_time')
@ -283,12 +301,21 @@ class TwCommon:
data = self.disp_data = list(self.filter_data()) # method could be a generator
cw = self.get_column_widths(data,wide=dt.detail) if data and dt.need_column_widths else None
if data and dt.need_column_widths:
cw = self.get_column_widths(data,wide=dt.detail)
cwh = cw._asdict()
fp = self.fs_params
hdr_fs = ''.join(fp[name].hdr_fs % ((),cwh[name])[fp[name].hdr_fs_repl]
for name in dt.cols if cwh[name])
fs = ''.join(fp[name].fs % ((),cwh[name])[fp[name].fs_repl]
for name in dt.cols if cwh[name])
else:
cw = hdr_fs = fs = None
self._display_data[display_type] = '{a}{b}\n{c}\n'.format(
a = self.header(color),
b = self.subheader(color),
c = dt.item_separator.join(getattr(self,dt.fmt_method)(data,cw,color=color))
c = dt.item_separator.join(getattr(self,dt.fmt_method)(data,cw,hdr_fs,fs,color=color))
if data else (nocolor,yellow)[color]('[no data for requested parameters]')
)

View file

@ -25,6 +25,9 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
class display_type(TwCommon.display_type):
class squeezed(TwCommon.display_type.squeezed):
cols = ('num','txid','date','inputs','amt','outputs','comment')
class detail(TwCommon.display_type.detail):
need_column_widths = False
item_separator = '\n\n'
@ -87,7 +90,7 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
return self.compute_column_widths(widths,maxws,minws,maxws_nice,wide=wide)
def gen_squeezed_display(self,data,cw,color):
def gen_squeezed_display(self,data,cw,hdr_fs,fs,color):
if self.sinceblock:
yield f'Displaying transactions since block {self.sinceblock.hl(color=color)}'
@ -98,18 +101,6 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
yield 'Due to screen width limitations, not all addresses could be displayed'
yield ''
hdr_fs = self.squeezed_hdr_fs_fs.format(
nw = cw.num,
dw = self.age_w,
txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '',
iw = cw.inputs,
ow = cw.outputs )
fs = self.squeezed_fs_fs.format(
nw = cw.num,
dw = self.age_w,
txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '' )
yield hdr_fs.format(
n = '',
t = 'TxID',
@ -127,9 +118,9 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
i = d.vouts_disp( 'inputs', width=cw.inputs, color=color ),
A = d.amt_disp(self.show_total_amt).fmt( 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 ) ).rstrip()
c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ) ).rstrip()
def gen_detail_display(self,data,cw,color):
def gen_detail_display(self,data,cw,hdr_fs,fs,color):
if self.sinceblock:
yield f'Displaying transactions since block {self.sinceblock.hl(color=color)}'

View file

@ -20,8 +20,6 @@
twuo: Tracking wallet unspent outputs class for the MMGen suite
"""
from collections import namedtuple
from ..globalvars import g
from ..util import msg,suf,fmt
from ..base_obj import AsyncInit
@ -41,6 +39,14 @@ from .common import TwCommon,TwMMGenID,get_tw_label
class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
class display_type(TwCommon.display_type):
class squeezed(TwCommon.display_type.squeezed):
cols = ('num','txid','vout','addr','mmid','comment','amt','amt2','date')
class detail(TwCommon.display_type.detail):
cols = ('num','txid','vout','addr','mmid','amt','amt2','block','date_time','comment')
def __new__(cls,proto,*args,**kwargs):
return MMGenObject.__new__(proto.base_proto_subclass(cls,'tw','unspent'))
@ -124,120 +130,94 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
return data
def get_column_widths(self,data,wide=False):
# min screen width: 80 cols
# num txid vout addr [mmid] [comment] amt [amt2] date
maxws_nice = {'txid': 12}
if self.show_mmid:
maxws_nice['addr'] = 16
self.cols = self.get_term_columns(g.min_screen_width)
return self.compute_column_widths(
widths = { # fixed cols
'num': max(2,len(str(len(data)))+1),
'vout': 4,
'mmid': max(len(d.twmmid.disp) for d in data) if self.show_mmid else 0,
'amt': self.disp_prec + 5,
'amt2': 0,
'block': self.age_col_params['block'][0] if wide else 0,
'date_time': self.age_col_params['date_time'][0] if wide else 0,
'date': self.age_w,
'spc': 7 if self.show_mmid else 5, # 7(5) spaces in fs
},
maxws = { # expandable cols
'txid': self.txid_w,
'addr': max(len(d.addr) for d in data),
'comment': max(d.comment.screen_width for d in data) if self.show_mmid else 0,
},
minws = {
'txid': 7,
'addr': 10,
'comment': len('Comment') if self.show_mmid else 0,
},
maxws_nice = maxws_nice,
wide = wide,
)
# allow for 7-digit confirmation nums
col1_w = max(3,len(str(len(data)))+1) # num + ')'
mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in data) or 12 # DEADBEEF:S:1
max_acct_w = max(i.comment.screen_width for i in data) + mmid_w + 1
max_btcaddr_w = max(len(i.addr) for i in data)
min_addr_w = self.cols - self.col_adj
addr_w = min(max_btcaddr_w + (0,1+max_acct_w)[self.show_mmid],min_addr_w)
acct_w = min(max_acct_w, max(24,addr_w-10))
btaddr_w = addr_w - acct_w - 1
comment_w = acct_w - mmid_w - 1
tx_w = min(self.txid_w,self.cols-addr_w-29-col1_w) # min=6 TODO
def gen_squeezed_display(self,data,cw,hdr_fs,fs,color):
return namedtuple(
'column_widths',
['num','mmid','addr','btaddr','comment','tx']
)(col1_w, mmid_w, addr_w, btaddr_w, comment_w, tx_w)
def gen_squeezed_display(self,data,cw,color):
fs = self.squeezed_fs_fs.format( cw=cw.num, tw=cw.tx )
hdr_fs = self.squeezed_hdr_fs_fs.format( cw=cw.num, tw=cw.tx )
yield hdr_fs.format(
n = 'Num',
t = 'TXid'.ljust(cw.tx - 2) + ' Vout',
a = 'Address'.ljust(cw.addr),
A = f'Amt({self.proto.dcoin})'.ljust(self.disp_prec+5),
A2 = f' Amt({self.proto.coin})'.ljust(self.disp_prec+4),
c = self.age_hdr ).rstrip()
for n,i in enumerate(data):
addr_dots = '|' + '.'*(cw.addr-1)
mmid_disp = MMGenID.fmtc(
(
'.'*cw.mmid if i.skip == 'addr' else
i.twmmid if i.twmmid.type == 'mmgen' else
f'Non-{g.proj_name}'
),
width = cw.mmid,
color = color )
if self.show_mmid:
addr_out = '{} {}{}'.format((
type(i.addr).fmtc(addr_dots,width=cw.btaddr,color=color) if i.skip == 'addr' else
i.addr.fmt(width=cw.btaddr,color=color)
),
mmid_disp,
(' ' + i.comment.fmt(width=cw.comment,color=color)) if cw.comment > 0 else ''
)
else:
addr_out = (
type(i.addr).fmtc(addr_dots,width=cw.addr,color=color) if i.skip=='addr' else
i.addr.fmt(width=cw.addr,color=color) )
n = '',
t = 'TxID',
v = 'Vout',
a = 'Address',
m = 'MMGenID',
c = 'Comment',
A = 'Amt({})'.format(self.proto.dcoin),
B = 'Amt({})'.format(self.proto.coin),
d = self.age_hdr )
for n,d in enumerate(data):
yield fs.format(
n = str(n+1)+')',
t = (
'' if not i.txid else
' ' * (cw.tx-4) + '|...' if i.skip == 'txid' else
i.txid.truncate(width=cw.tx,color=True) ),
v = i.vout,
a = addr_out,
A = i.amt.fmt(color=color,prec=self.disp_prec),
A2 = (i.amt2.fmt(color=color,prec=self.disp_prec) if i.amt2 is not None else ''),
c = self.age_disp(i,self.age_fmt),
).rstrip()
n = str(n+1)+')',
t = (CoinTxID.fmtc('|' + '.'*(cw.txid-1),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 = type(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'
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,
A = d.amt.fmt(color=color,prec=self.disp_prec),
B = d.amt2.fmt(color=color,prec=self.disp_prec) if cw.amt2 else None,
d = self.age_disp(d,self.age_fmt),
)
def gen_detail_display(self,data,cw,color):
def gen_detail_display(self,data,cw,hdr_fs,fs,color):
addr_w = max(len(i.addr) for i in data)
mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in data) or 12 # DEADBEEF:S:1
yield hdr_fs.format(
n = '',
t = 'TxID',
v = 'Vout',
a = 'Address',
m = 'MMGenID',
A = 'Amt({})'.format(self.proto.dcoin),
B = 'Amt({})'.format(self.proto.coin) if cw.amt2 else None,
b = 'Block',
D = 'Date/Time',
c = 'Comment' ).rstrip()
fs = self.wide_fs_fs.format(
tw = self.txid_w + 3,
cf = '{c:<8} ',
aw = self.proto.coin_amt.max_prec + 5 )
yield fs.format(
n = 'Num',
t = 'Tx ID,Vout',
a = 'Address'.ljust(addr_w),
m = 'MMGen ID'.ljust(mmid_w),
A = f'Amount({self.proto.dcoin})',
A2 = f'Amount({self.proto.coin})',
c = 'Confs', # skipped for eth
b = 'Block', # skipped for eth
D = 'Date',
l = 'Label' )
max_comment_len = max([len(i.comment) for i in data if i.comment] or [2])
for n,i in enumerate(data):
for n,d in enumerate(data):
yield fs.format(
n = str(n+1) + ')',
t = '{},{}'.format(
('|'+'.'*63 if i.skip == 'txid' and self.group else i.txid),
i.vout ),
a = (
'|'+'.' * addr_w if i.skip == 'addr' and self.group else
i.addr.fmt(color=color,width=addr_w) ),
m = MMGenID.fmtc( i.twmmid.disp, width=mmid_w, color=color ),
A = i.amt.fmt(color=color),
A2 = ( i.amt2.fmt(color=color) if i.amt2 is not None else '' ),
c = i.confs,
b = self.rpc.blockcount - (i.confs - 1),
D = self.age_disp(i,'date_time'),
l = i.comment.hl(color=color) if i.comment else
TwComment.fmtc(
s = '',
color = color,
nullrepl = '-',
width = max_comment_len )
).rstrip()
n = str(n+1) + ')',
t = d.txid.fmt(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),
A = d.amt.fmt(color=color,prec=self.disp_prec),
B = d.amt2.fmt(color=color,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='-'),
).rstrip()
def display_total(self):
msg('\nTotal unspent: {} {} ({} output{})'.format(