support 80-screen-width tracking wallet labels

Labels with double-wide CJK characters can already occupy 80 terminal cells.
Extend the same privilege to all labels.
This commit is contained in:
The MMGen Project 2019-05-28 14:49:44 +00:00
commit d7bfc8307e
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
10 changed files with 84 additions and 54 deletions

View file

@ -777,6 +777,7 @@ class MMGenLabel(str,Hilite,InitErrors):
forbidden = []
max_len = 0
min_len = 0
max_screen_width = 0 # if != 0, overrides max_len
desc = 'label'
def __new__(cls,s,on_fail='die',msg=None):
if type(s) == cls: return s
@ -795,13 +796,19 @@ class MMGenLabel(str,Hilite,InitErrors):
if unicodedata.category(ch)[0] in 'CM':
t = { 'C':'control', 'M':'combining' }[unicodedata.category(ch)[0]]
raise ValueError('{}: {} characters not allowed'.format(ascii(ch),t))
assert len(s) <= cls.max_len, 'too long (>{} symbols)'.format(cls.max_len)
me = str.__new__(cls,s)
if cls.max_screen_width:
me.screen_width = len(s) + len([1 for ch in s if unicodedata.east_asian_width(ch) in ('F','W')])
assert me.screen_width <= cls.max_screen_width,(
'too wide (>{} screen width)'.format(cls.max_screen_width))
else:
assert len(s) <= cls.max_len, 'too long (>{} symbols)'.format(cls.max_len)
assert len(s) >= cls.min_len, 'too short (<{} symbols)'.format(cls.min_len)
assert not cls.allowed or set(list(s)).issubset(set(cls.allowed)),\
'contains non-allowed symbols: {}'.format(' '.join(set(list(s)) - set(cls.allowed)))
assert not cls.forbidden or not any(ch in s for ch in cls.forbidden),\
"contains one of these forbidden symbols: '{}'".format("', '".join(cls.forbidden))
return str.__new__(cls,s)
return me
except Exception as e:
return cls.init_fail(e,s)
@ -810,7 +817,7 @@ class MMGenWalletLabel(MMGenLabel):
desc = 'wallet label'
class TwComment(MMGenLabel):
max_len = 40
max_screen_width = 80
desc = 'tracking wallet comment'
class MMGenTXLabel(MMGenLabel):

View file

@ -182,7 +182,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
# allow for 7-digit confirmation nums
col1_w = max(3,len(str(len(unsp)))+1) # num + ')'
mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in unsp) or 12 # DEADBEEF:S:1
max_acct_w = max(len(i.label) for i in unsp) + mmid_w + 1
max_acct_w = max(i.label.screen_width for i in unsp) + mmid_w + 1
max_btcaddr_w = max(len(i.addr) for i in unsp)
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)
@ -472,7 +472,7 @@ class TwAddrList(MMGenDict):
fs = '{{mid}}{} {{cmt}} {{amt}}{}'.format(('',' {addr}')[showbtcaddrs],('',' {age}')[show_age])
mmaddrs = [k for k in self.keys() if k.type == 'mmgen']
max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
max_cmt_len = max(max(screen_width(v['lbl'].comment) for v in self.values()),7)
max_cmt_width = max(max(v['lbl'].comment.screen_width for v in self.values()),7)
addr_width = max(len(self[mmid]['addr']) for mmid in self)
# fp: fractional part
@ -480,7 +480,7 @@ class TwAddrList(MMGenDict):
out += [fs.format(
mid=MMGenID.fmtc('MMGenID',width=max_mmid_len),
addr=(CoinAddr.fmtc('ADDRESS',width=addr_width) if showbtcaddrs else None),
cmt=TwComment.fmtc('COMMENT',width=max_cmt_len+1),
cmt=TwComment.fmtc('COMMENT',width=max_cmt_width+1),
amt='BALANCE'.ljust(max_fp_len+4),
age=('CONFS','DAYS')[age_fmt=='days'],
)]
@ -512,7 +512,7 @@ class TwAddrList(MMGenDict):
out.append(fs.format(
mid=MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True),
addr=(e['addr'].fmt(color=True,width=addr_width) if showbtcaddrs else None),
cmt=e['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'),
cmt=e['lbl'].comment.fmt(width=max_cmt_width,color=True,nullrepl='-'),
amt=e['amt'].fmt('4.{}'.format(max(max_fp_len,3)),color=True),
age=mmid.confs // (1,confs_per_day)[age_fmt=='days'] if hasattr(mmid,'confs') and mmid.confs != None else '-'
))

View file

@ -20,7 +20,7 @@
util.py: Low-level routines imported by other modules in the MMGen suite
"""
import sys,os,time,stat,re,unicodedata
import sys,os,time,stat,re
from hashlib import sha256
from string import hexdigits,digits
from mmgen.color import *
@ -231,9 +231,6 @@ def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
def split_into_cols(col_wid,s):
return ' '.join([s[col_wid*i:col_wid*(i+1)] for i in range(len(s)//col_wid+1)]).rstrip()
def screen_width(s):
return len(s) + len([1 for ch in s if unicodedata.east_asian_width(ch) in ('F','W')])
def capfirst(s): # different from str.capitalize() - doesn't downcase any uc in string
return s if len(s) == 0 else s[0].upper() + s[1:]

View file

@ -20,17 +20,31 @@
common.py: Shared routines and data for the MMGen test suites
"""
sample_text = 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
ref_kafile_pass = 'kafile password'
ref_kafile_hash_preset = '1'
class TestSuiteException(Exception): pass
class TestSuiteFatalException(Exception): pass
import os
from mmgen.common import *
ascii_uc = ''.join(map(chr,list(range(65,91)))) # 26 chars
ascii_lc = ''.join(map(chr,list(range(97,123)))) # 26 chars
lat_accent = ''.join(map(chr,list(range(192,383)))) # 191 chars
ru_uc = ''.join(map(chr,list(range(1040,1072)))) # 32 chars
gr_uc = ''.join(map(chr,list(range(913,930)) + list(range(931,940)))) # 26 chars (930 is ctrl char)
lat_cyr_gr = lat_accent[:130:5] + ru_uc + gr_uc # 84 chars
utf8_text = '[α-$ample UTF-8 text-ω]' * 10 # 230 chars, unicode types L,N,P,S,Z
utf8_combining = '[α-$ámple UTF-8 téxt-ω]' * 10 # L,N,P,S,Z,M
utf8_ctrl = '[α-$ample\nUTF-8\ntext-ω]' * 10 # L,N,P,S,Z,C
text_jp = '必要なのは、信用ではなく暗号化された証明に基づく電子取引システムであり、これにより希望する二者が信用できる第三者機関を介さずに直接取引できるよう' # 72 chars ('W'ide)
text_zh = '所以,我們非常需要這樣一種電子支付系統,它基於密碼學原理而不基於信用,使得任何達成一致的雙方,能夠直接進行支付,從而不需要協力廠商仲介的參與。。' # 72 chars ('F'ull + 'W'ide)
sample_text = 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
ref_kafile_pass = 'kafile password'
ref_kafile_hash_preset = '1'
def getrandnum(n): return int(os.urandom(n).hex(),16)
def getrandhex(n): return os.urandom(n).hex()
def getrandnum_range(nbytes,rn_max):

View file

@ -144,15 +144,22 @@ tests = OrderedDict([
)
}),
('MMGenWalletLabel', {
'bad': (utf8_text[:49],utf8_text_combining[:48],utf8_text_control[:48]),
'bad': (utf8_text[:49],utf8_combining[:48],utf8_ctrl[:48]),
'good': (utf8_text[:48],)
}),
('TwComment', {
'bad': (utf8_text[:41],utf8_text_combining[:40],utf8_text_control[:40]),
'good': (utf8_text[:40],)
'bad': ( utf8_combining[:40],
utf8_ctrl[:40],
text_jp[:41],
text_zh[:41],
utf8_text[:81] ),
'good': ( utf8_text[:80],
(ru_uc + gr_uc + utf8_text)[:80],
text_jp[:40],
text_zh[:40] )
}),
('MMGenTXLabel',{
'bad': (utf8_text[:73],utf8_text_combining[:72],utf8_text_control[:72]),
'bad': (utf8_text[:73],utf8_combining[:72],utf8_ctrl[:72]),
'good': (utf8_text[:72],)
}),
('MMGenPWIDString', { # forbidden = list(u' :/\\')

View file

@ -9,9 +9,7 @@ test.objtest_py_d.ot_common: shared data for MMGen data objects tests
import os
from mmgen.globalvars import g
from ..common import *
r32,r24,r16,r17,r18 = os.urandom(32),os.urandom(24),os.urandom(16),os.urandom(17),os.urandom(18)
tw_pfx = g.proto.base_coin.lower()+':'
utf8_text = '[α-$ample UTF-8 text-ω]' * 10 # 230 chars, unicode types L,N,P,S,Z
utf8_text_combining = '[α-$ámple UTF-8 téxt-ω]' * 10 # L,N,P,S,Z,M
utf8_text_control = '[α-$ample\nUTF-8\ntext-ω]' * 10 # L,N,P,S,Z,C

View file

@ -22,6 +22,7 @@ common.py: Shared routines and data for the test.py test suite
import os,time,subprocess
from mmgen.common import *
from ..common import *
log_file = 'test.py.log'
@ -46,17 +47,15 @@ non_mmgen_fn = 'coinkey'
ref_dir = os.path.join('test','ref')
dfl_words_file = os.path.join(ref_dir,'98831F3A.mmwords')
from mmgen.obj import MMGenTXLabel
from mmgen.obj import MMGenTXLabel,TwComment
ref_tx_label_jp = '必要なのは、信用ではなく暗号化された証明に基づく電子取引システムであり、これにより希望する二者が信用できる第三者機関を介さずに直接取引できるよう' # 72 chars ('W'ide)
ref_tx_label_zh = '所以,我們非常需要這樣一種電子支付系統,它基於密碼學原理而不基於信用,使得任何達成一致的雙方,能夠直接進行支付,從而不需要協力廠商仲介的參與。。' # 72 chars ('F'ull + 'W'ide)
ref_tx_label_lat_cyr_gr = ''.join(map(chr,
list(range(65,91)) +
list(range(1040,1072)) + # cyrillic
list(range(913,939)) + # greek
list(range(97,123))))[:MMGenTXLabel.max_len] # 72 chars
utf8_label = ref_tx_label_zh[:40]
utf8_label_pat = utf8_label
tx_label_jp = text_jp
tx_label_zh = text_zh
tx_label_lat_cyr_gr = lat_cyr_gr[:MMGenTXLabel.max_len] # 72 chars
tw_label_zh = text_zh[:TwComment.max_screen_width // 2]
tw_label_lat_cyr_gr = lat_cyr_gr[:TwComment.max_screen_width] # 80 chars
ref_bw_hash_preset = '1'
ref_bw_file = 'wallet.mmbrain'
@ -140,8 +139,8 @@ labels = [
"Automotive",
"Travel expenses",
"Healthcare",
ref_tx_label_jp[:40],
ref_tx_label_zh[:40],
tx_label_jp[:40],
tx_label_zh[:40],
"Alice's allowance",
"Bob's bequest",
"House purchase",

View file

@ -156,8 +156,10 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
('addrimport_burn_addr',"importing burn address"),
('bal5', 'the {} balance'.format(g.coin)),
('add_label', 'adding a UTF-8 label'),
('chk_label', 'the label'),
('add_label1', 'adding a UTF-8 label (zh)'),
('chk_label1', 'the label'),
('add_label2', 'adding a UTF-8 label (lat+cyr+gr)'),
('chk_label2', 'the label'),
('remove_label', 'removing the label'),
('token_compile1', 'compiling ERC20 token #1'),
@ -241,8 +243,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
('token_twview2','twview --token=mm1 wide=1'),
('token_twview3','twview --token=mm1 wide=1 sort=age (ignored)'),
('edit_label1','adding label to addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
('edit_label2','adding label to addr #{} in {} tracking wallet'.format(del_addrs[1],g.coin)),
('edit_label1','adding label to addr #{} in {} tracking wallet (zh)'.format(del_addrs[0],g.coin)),
('edit_label2','adding label to addr #{} in {} tracking wallet (lat+cyr+gr)'.format(del_addrs[1],g.coin)),
('edit_label3','removing label from addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
('remove_addr1','removing addr #{} from {} tracking wallet'.format(del_addrs[0],g.coin)),
@ -349,7 +351,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
fee_res = fee_res,
fee_desc = fee_desc,
eth_fee_res = eth_fee_res,
add_comment = ref_tx_label_jp )
add_comment = tx_label_jp )
def txsign(self,ni=False,ext='{}.rawtx',add_args=[]):
ext = ext.format('' if g.debug_utf8 else '')
@ -472,16 +474,21 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
return t
def add_label(self,addr='98831F3A:E:3',lbl=utf8_label):
def add_label(self,lbl,addr='98831F3A:E:3'):
t = self.spawn('mmgen-tool', self.eth_args + ['add_label',addr,lbl])
t.expect('Added label.*in tracking wallet',regex=True)
return t
def chk_label(self,addr='98831F3A:E:3',label_pat=utf8_label_pat):
def chk_label(self,lbl_pat,addr='98831F3A:E:3'):
t = self.spawn('mmgen-tool', self.eth_args + ['listaddresses','all_labels=1'])
t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,(label_pat or label)),regex=True)
t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,lbl_pat),regex=True)
return t
def add_label1(self): return self.add_label(lbl=tw_label_zh)
def chk_label1(self): return self.chk_label(lbl_pat=tw_label_zh)
def add_label2(self): return self.add_label(lbl=tw_label_lat_cyr_gr)
def chk_label2(self): return self.chk_label(lbl_pat=tw_label_lat_cyr_gr)
def remove_label(self,addr='98831F3A:E:3'):
t = self.spawn('mmgen-tool', self.eth_args + ['remove_label',addr])
t.expect('Removed label.*in tracking wallet',regex=True)
@ -646,7 +653,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
inputs = inputs,
input_sels_prompt = 'to spend from',
file_desc = 'Ethereum token transaction',
add_comment = ref_tx_label_lat_cyr_gr)
add_comment = tx_label_lat_cyr_gr)
def token_txsign(self,ext='',token=''):
return self.txsign(ni=True,ext=ext,add_args=['--token='+token])
def token_txsend(self,ext='',token=''):
@ -768,9 +775,9 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
return t
def edit_label1(self):
return self.edit_label(out_num=del_addrs[0],label_text='First added label-α')
return self.edit_label(out_num=del_addrs[0],label_text=tw_label_zh)
def edit_label2(self):
return self.edit_label(out_num=del_addrs[1],label_text='Second added label')
return self.edit_label(out_num=del_addrs[1],label_text=tw_label_lat_cyr_gr)
def edit_label3(self):
return self.edit_label(out_num=del_addrs[0],label_text='')

View file

@ -435,7 +435,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
self.txcreate_ui_common(t,
menu=(['M'],['M','D','m','g'])[self.test_name=='txcreate'],
inputs=' '.join(map(str,outputs_list)),
add_comment=('',ref_tx_label_lat_cyr_gr)[do_label],
add_comment=('',tx_label_lat_cyr_gr)[do_label],
non_mmgen_inputs=(0,1)[bool(non_mmgen_input and not txdo_args)],
view=view)

View file

@ -82,7 +82,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
('fund_bob', "funding Bob's wallet"),
('fund_alice', "funding Alice's wallet"),
('bob_bal1', "Bob's balance"),
('bob_add_label', "adding a 40-character UTF-8 encoded label"),
('bob_add_label', "adding an 80-screen-width label (lat+cyr+gr)"),
('bob_twview1', "viewing Bob's tracking wallet"),
('bob_split1', "splitting Bob's funds"),
('generate', 'mining a block'),
@ -136,7 +136,8 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
('alice_chk_label1', 'the label'),
('alice_add_label2', 'adding a label'),
('alice_chk_label2', 'the label'),
('alice_edit_label1', 'editing a label'),
('alice_edit_label1', 'editing a label (zh)'),
('alice_edit_label2', 'editing a label (lat+cyr+gr)'),
('alice_chk_label3', 'the label'),
('alice_remove_label1', 'removing a label'),
('alice_chk_label4', 'the label'),
@ -432,7 +433,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
inputs = outputs_list,
file_desc = 'Signed transaction',
interactive_fee = (tx_fee,'')[bool(fee)],
add_comment = ref_tx_label_jp,
add_comment = tx_label_jp,
view = 't',save=True)
t.passphrase('MMGen wallet',rt_pw)
@ -593,7 +594,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
def bob_add_label(self):
sid = self._user_sid('bob')
return self.user_add_label('bob',sid+':C:1',utf8_label)
return self.user_add_label('bob',sid+':C:1',tw_label_lat_cyr_gr)
def alice_add_label1(self):
sid = self._user_sid('alice')
@ -663,13 +664,13 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
sid = self._user_sid('alice')
return self.user_chk_label('alice',sid+':C:1','Replacement Label')
def alice_edit_label1(self):
return self.user_edit_label('alice','4',utf8_label)
def alice_edit_label1(self): return self.user_edit_label('alice','4',tw_label_lat_cyr_gr)
def alice_edit_label2(self): return self.user_edit_label('alice','3',tw_label_zh)
def alice_chk_label3(self):
sid = self._user_sid('alice')
mmid = sid + (':S:3',':L:3')[g.coin=='BCH']
return self.user_chk_label('alice',mmid,utf8_label,label_pat=utf8_label_pat)
return self.user_chk_label('alice',mmid,tw_label_zh,label_pat=tw_label_lat_cyr_gr)
def alice_chk_label4(self):
sid = self._user_sid('alice')