Browse Source

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

The MMGen Project 2 years ago
parent
commit
217cd7ad39

+ 8 - 3
mmgen/addr.py

@@ -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 )

+ 1 - 1
mmgen/main_seedjoin.py

@@ -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):

+ 8 - 7
mmgen/obj.py

@@ -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')

+ 58 - 40
mmgen/objmethods.py

@@ -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:
 

+ 9 - 7
mmgen/proto/btc/tw/txhistory.py

@@ -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

+ 1 - 1
mmgen/proto/btc/tx/info.py

@@ -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,

+ 1 - 1
mmgen/seedsplit.py

@@ -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 + "'{}"

+ 2 - 2
mmgen/tw/addresses.py

@@ -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' ))

+ 1 - 1
mmgen/tw/ctl.py

@@ -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

+ 2 - 3
mmgen/tw/shared.py

@@ -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):

+ 2 - 2
mmgen/tw/txhistory.py

@@ -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 ),

+ 6 - 6
mmgen/tw/unspent.py

@@ -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(

+ 4 - 4
mmgen/wallet/mmgen.py

@@ -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

+ 1 - 1
test/test_py_d/ts_ethdev.py

@@ -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

+ 2 - 2
test/test_py_d/ts_seedsplit.py

@@ -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',