|
@@ -1,162 +1,180 @@
|
|
|
#!/usr/bin/env python3
|
|
|
-
|
|
|
-class MMGenObject(object):
|
|
|
- 'placeholder - overridden when testing'
|
|
|
- def immutable_attr_init_check(self): pass
|
|
|
-
|
|
|
-import os
|
|
|
-if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN_EXEC_WRAPPER'):
|
|
|
-
|
|
|
- import sys,re,traceback,json,pprint
|
|
|
- from decimal import Decimal
|
|
|
- from difflib import unified_diff,ndiff
|
|
|
-
|
|
|
- def pmsg(*args,out=sys.stderr):
|
|
|
- d = args if len(args) > 1 else '' if not args else args[0]
|
|
|
- out.write(pprint.PrettyPrinter(indent=4).pformat(d) + '\n')
|
|
|
- def pdie(*args,exit_val=1,out=sys.stderr):
|
|
|
- pmsg(*args,out=out)
|
|
|
+#
|
|
|
+# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
|
|
+# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
|
|
+# Licensed under the GNU General Public License, Version 3:
|
|
|
+# https://www.gnu.org/licenses
|
|
|
+# Public project repositories:
|
|
|
+# https://github.com/mmgen/mmgen
|
|
|
+# https://gitlab.com/mmgen/mmgen
|
|
|
+
|
|
|
+"""
|
|
|
+devtools.py: Developer tools for the MMGen suite
|
|
|
+"""
|
|
|
+
|
|
|
+import sys
|
|
|
+
|
|
|
+def pfmt(*args):
|
|
|
+ import pprint
|
|
|
+ return (
|
|
|
+ pprint.PrettyPrinter(indent=4).pformat(
|
|
|
+ args if len(args) > 1 else '' if not args else args[0] )
|
|
|
+ + '\n' )
|
|
|
+
|
|
|
+def pmsg(*args):
|
|
|
+ sys.stderr.write(pfmt(*args))
|
|
|
+
|
|
|
+def pdie(*args,exit_val=1):
|
|
|
+ pmsg(*args)
|
|
|
+ sys.exit(exit_val)
|
|
|
+
|
|
|
+def pexit(*args):
|
|
|
+ pdie(*args,exit_val=0)
|
|
|
+
|
|
|
+def Pmsg(*args):
|
|
|
+ sys.stdout.write(pfmt(*args))
|
|
|
+
|
|
|
+def Pdie(*args,exit_val=1):
|
|
|
+ Pmsg(*args)
|
|
|
+ sys.exit(exit_val)
|
|
|
+
|
|
|
+def Pexit(*args):
|
|
|
+ Pdie(*args,exit_val=0)
|
|
|
+
|
|
|
+def print_stack_trace(message=None,fh_list=[],nl='\n',sep='\n ',trim=4):
|
|
|
+ import os
|
|
|
+ if not fh_list:
|
|
|
+ fh_list.append(open(f'devtools.trace.{os.getpid()}','w'))
|
|
|
+ nl = ''
|
|
|
+ res = get_stack_trace(message,nl,sep,trim)
|
|
|
+ sys.stderr.write(res)
|
|
|
+ fh_list[0].write(res)
|
|
|
+
|
|
|
+def get_stack_trace(message=None,nl='\n',sep='\n ',trim=3):
|
|
|
+
|
|
|
+ import os,re,traceback
|
|
|
+
|
|
|
+ tb = [t for t in traceback.extract_stack() if t.filename[:1] != '<']
|
|
|
+ fs = '{}:{}: in {}:\n {}'
|
|
|
+ out = [
|
|
|
+ fs.format(
|
|
|
+ re.sub(r'^\./','',os.path.relpath(t.filename)),
|
|
|
+ t.lineno,
|
|
|
+ (t.name+'()' if t.name[-1] != '>' else t.name),
|
|
|
+ t.line or '(none)')
|
|
|
+ for t in (tb[:-trim] if trim else tb) ]
|
|
|
+
|
|
|
+ return f'{nl}STACK TRACE {message or "[unnamed]"}:{sep}{sep.join(out)}\n'
|
|
|
+
|
|
|
+def print_diff(*args,**kwargs):
|
|
|
+ sys.stderr.write(get_diff(*args,**kwargs))
|
|
|
+
|
|
|
+def get_diff(a,b,a_fn='',b_fn='',from_json=True):
|
|
|
+
|
|
|
+ if from_json:
|
|
|
+ import json
|
|
|
+ a = json.dumps(json.loads(a),indent=4)
|
|
|
+ b = json.dumps(json.loads(b),indent=4)
|
|
|
+
|
|
|
+ from difflib import unified_diff
|
|
|
+ # chunk headers have trailing newlines, hence the rstrip()
|
|
|
+ return ' DIFF:\n {}\n'.format(
|
|
|
+ '\n '.join(a.rstrip('\n') for a in unified_diff(
|
|
|
+ a.split('\n'),
|
|
|
+ b.split('\n'),
|
|
|
+ a_fn,
|
|
|
+ b_fn )))
|
|
|
+
|
|
|
+def print_ndiff(*args,**kwargs):
|
|
|
+ sys.stderr.write(get_ndiff(*args,**kwargs))
|
|
|
+
|
|
|
+def get_ndiff(a,b):
|
|
|
+ from difflib import ndiff
|
|
|
+ return list(ndiff(
|
|
|
+ a.split('\n'),
|
|
|
+ b.split('\n') ))
|
|
|
+
|
|
|
+class MMGenObjectMethods: # mixin class for MMGenObject
|
|
|
+
|
|
|
+ # Pretty-print an MMGenObject instance, recursing into sub-objects - WIP
|
|
|
+ def pmsg(self):
|
|
|
+ sys.stdout.write('\n'+self.pfmt())
|
|
|
+
|
|
|
+ def pdie(self,exit_val=1):
|
|
|
+ self.pmsg()
|
|
|
sys.exit(exit_val)
|
|
|
- def pexit(*args,out=sys.stderr):
|
|
|
- pdie(*args,exit_val=0,out=out)
|
|
|
-
|
|
|
- def Pmsg(*args):
|
|
|
- pmsg(*args,out=sys.stdout)
|
|
|
- def Pdie(*args):
|
|
|
- pdie(*args,out=sys.stdout)
|
|
|
- def Pexit(*args):
|
|
|
- pexit(*args,out=sys.stdout)
|
|
|
-
|
|
|
- def print_stack_trace(message=None,fh=[],nl='\n',sep='\n '):
|
|
|
-
|
|
|
- if not fh:
|
|
|
- fh.append(open(f'devtools.trace.{os.getpid()}','w'))
|
|
|
- nl = ''
|
|
|
-
|
|
|
- tb = [t for t in traceback.extract_stack() if t.filename[:1] != '<'][:-1]
|
|
|
- fs = '{}:{}: in {}:\n {}'
|
|
|
- out = [
|
|
|
- fs.format(
|
|
|
- re.sub(r'^\./','',os.path.relpath(t.filename)),
|
|
|
- t.lineno,
|
|
|
- (t.name+'()' if t.name[-1] != '>' else t.name),
|
|
|
- t.line or '(none)')
|
|
|
- for t in tb ]
|
|
|
-
|
|
|
- text = f'{nl}STACK TRACE {message or "[unnamed]"}:{sep}{sep.join(out)}\n'
|
|
|
- sys.stderr.write(text)
|
|
|
- fh[0].write(text)
|
|
|
-
|
|
|
- class MMGenObject(object):
|
|
|
-
|
|
|
- # Pretty-print any object subclassed from MMGenObject, recursing into sub-objects - WIP
|
|
|
- def pmsg(self,*args):
|
|
|
- print(args[0] if len(args) == 1 else args if args else self.pfmt())
|
|
|
-
|
|
|
- def pdie(self,*args):
|
|
|
- self.pmsg(*args)
|
|
|
- sys.exit(1)
|
|
|
-
|
|
|
- def pexit(self,*args):
|
|
|
- self.pmsg(*args)
|
|
|
- sys.exit(0)
|
|
|
-
|
|
|
- def pfmt(self,lvl=0,id_list=[]):
|
|
|
- scalars = (str,int,float,Decimal)
|
|
|
- def do_list(out,e,lvl=0,is_dict=False):
|
|
|
- out.append('\n')
|
|
|
- for i in e:
|
|
|
- el = i if not is_dict else e[i]
|
|
|
- if is_dict:
|
|
|
- out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8))
|
|
|
- if hasattr(el,'pfmt'):
|
|
|
- out.append('{:>{l}}{}'.format(
|
|
|
- '',
|
|
|
- el.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
|
|
|
- l = (lvl+1)*8 ))
|
|
|
- elif isinstance(el,scalars):
|
|
|
- if isList(e):
|
|
|
- out.append( '{:>{l}}{!r:16}\n'.format( '', el, l=lvl*8 ))
|
|
|
- else:
|
|
|
- out.append(f' {el!r}')
|
|
|
- elif isList(el) or isDict(el):
|
|
|
- indent = 1 if is_dict else lvl*8+4
|
|
|
- out.append('{:>{l}}{:16}'.format( '', f'<{type(el).__name__}>', l=indent ))
|
|
|
- if isList(el) and isinstance(el[0],scalars):
|
|
|
- out.append('\n')
|
|
|
- do_list(out,el,lvl=lvl+1,is_dict=isDict(el))
|
|
|
- else:
|
|
|
- out.append('{:>{l}}{:16} {!r}\n'.format( '', f'<{type(el).__name__}>', el, l=(lvl*8)+8 ))
|
|
|
- out.append('\n')
|
|
|
-
|
|
|
- if not e:
|
|
|
- out.append(f'{e!r}\n')
|
|
|
-
|
|
|
- def isDict(obj):
|
|
|
- return isinstance(obj,dict)
|
|
|
- def isList(obj):
|
|
|
- return isinstance(obj,list)
|
|
|
- def isScalar(obj):
|
|
|
- return isinstance(obj,scalars)
|
|
|
-
|
|
|
- out = [f'<{type(self).__name__}>{" "+repr(self) if isScalar(self) else ""}\n']
|
|
|
-
|
|
|
- if id(self) in id_list:
|
|
|
- return out[-1].rstrip() + ' [RECURSION]\n'
|
|
|
- if isList(self) or isDict(self):
|
|
|
- do_list(out,self,lvl=lvl,is_dict=isDict(self))
|
|
|
-
|
|
|
- for k in self.__dict__:
|
|
|
- e = getattr(self,k)
|
|
|
- if isList(e) or isDict(e):
|
|
|
- out.append('{:>{l}}{:<10} {:16}'.format( '', k, f'<{type(e).__name__}>', l=(lvl*8)+4 ))
|
|
|
- do_list(out,e,lvl=lvl,is_dict=isDict(e))
|
|
|
- elif hasattr(e,'pfmt') and type(e) != type:
|
|
|
- out.append('{:>{l}}{:10} {}'.format(
|
|
|
- '',
|
|
|
- k,
|
|
|
- e.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
|
|
|
- l = (lvl*8)+4 ))
|
|
|
- else:
|
|
|
- out.append('{:>{l}}{:<10} {:16} {}\n'.format(
|
|
|
+
|
|
|
+ def pexit(self):
|
|
|
+ self.pdie(exit_val=0)
|
|
|
+
|
|
|
+ def pfmt(self,lvl=0,id_list=[]):
|
|
|
+ from decimal import Decimal
|
|
|
+ scalars = (str,int,float,Decimal)
|
|
|
+ def do_list(out,e,lvl=0,is_dict=False):
|
|
|
+ out.append('\n')
|
|
|
+ for i in e:
|
|
|
+ el = i if not is_dict else e[i]
|
|
|
+ if is_dict:
|
|
|
+# out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8))
|
|
|
+ out.append('{s1}{i}{s2}'.format(
|
|
|
+ i = i,
|
|
|
+ s1 = ' ' * (4*lvl+8),
|
|
|
+ s2 = ' ' * 10 ))
|
|
|
+ if hasattr(el,'pfmt'):
|
|
|
+ out.append('{:>{l}}{}'.format(
|
|
|
'',
|
|
|
- k,
|
|
|
- f'<{type(e).__name__}>',
|
|
|
- repr(e),
|
|
|
- l=(lvl*8)+4 ))
|
|
|
-
|
|
|
- import re
|
|
|
- return re.sub('\n+','\n',''.join(out))
|
|
|
-
|
|
|
- # Check that all immutables have been initialized. Expensive, so do only when testing.
|
|
|
- def immutable_attr_init_check(self):
|
|
|
- from .globalvars import g
|
|
|
- if g.test_suite:
|
|
|
- from .util import die
|
|
|
- cls = type(self)
|
|
|
- for attrname in sorted({a for a in self.valid_attrs if a[0] != '_'}):
|
|
|
- for o in (cls,cls.__bases__[0]): # assume there's only one base class
|
|
|
- if attrname in o.__dict__:
|
|
|
- attr = o.__dict__[attrname]
|
|
|
- break
|
|
|
+ el.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
|
|
|
+ l = (lvl+1)*8 ))
|
|
|
+ elif isinstance(el,scalars):
|
|
|
+ if isList(e):
|
|
|
+ out.append( '{:>{l}}{!r:16}\n'.format( '', el, l=lvl*8 ))
|
|
|
else:
|
|
|
- die(4,f'unable to find descriptor {cls.__name__}.{attrname}')
|
|
|
- if type(attr).__name__ == 'ImmutableAttr':
|
|
|
- if attrname not in self.__dict__:
|
|
|
- die(4,
|
|
|
- f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!')
|
|
|
-
|
|
|
- def print_diff(a,b,from_file='',to_file='',from_json=True):
|
|
|
- if from_json:
|
|
|
- a = json.dumps(json.loads(a),indent=4).split('\n') if a else []
|
|
|
- b = json.dumps(json.loads(b),indent=4).split('\n') if b else []
|
|
|
- else:
|
|
|
- a = a.split('\n')
|
|
|
- b = b.split('\n')
|
|
|
- sys.stderr.write(' DIFF:\n {}\n'.format(
|
|
|
- '\n '.join(unified_diff(a,b,from_file,to_file)) ))
|
|
|
-
|
|
|
- def get_ndiff(a,b):
|
|
|
- a = a.split('\n')
|
|
|
- b = b.split('\n')
|
|
|
- return list(ndiff(a,b))
|
|
|
+ out.append(f' {el!r}')
|
|
|
+ elif isList(el) or isDict(el):
|
|
|
+ indent = 1 if is_dict else lvl*8+4
|
|
|
+ out.append('{:>{l}}{:16}'.format( '', f'<{type(el).__name__}>', l=indent ))
|
|
|
+ if isList(el) and isinstance(el[0],scalars):
|
|
|
+ out.append('\n')
|
|
|
+ do_list(out,el,lvl=lvl+1,is_dict=isDict(el))
|
|
|
+ else:
|
|
|
+ out.append('{:>{l}}{:16} {!r}\n'.format( '', f'<{type(el).__name__}>', el, l=(lvl*8)+8 ))
|
|
|
+ out.append('\n')
|
|
|
+
|
|
|
+ if not e:
|
|
|
+ out.append(f'{e!r}\n')
|
|
|
+
|
|
|
+ def isDict(obj):
|
|
|
+ return isinstance(obj,dict)
|
|
|
+ def isList(obj):
|
|
|
+ return isinstance(obj,list)
|
|
|
+ def isScalar(obj):
|
|
|
+ return isinstance(obj,scalars)
|
|
|
+
|
|
|
+ out = [f'<{type(self).__name__}>{" "+repr(self) if isScalar(self) else ""}\n']
|
|
|
+
|
|
|
+ if id(self) in id_list:
|
|
|
+ return out[-1].rstrip() + ' [RECURSION]\n'
|
|
|
+ if isList(self) or isDict(self):
|
|
|
+ do_list(out,self,lvl=lvl,is_dict=isDict(self))
|
|
|
+
|
|
|
+ for k in self.__dict__:
|
|
|
+ e = getattr(self,k)
|
|
|
+ if isList(e) or isDict(e):
|
|
|
+ out.append('{:>{l}}{:<10} {:16}'.format( '', k, f'<{type(e).__name__}>', l=(lvl*8)+4 ))
|
|
|
+ do_list(out,e,lvl=lvl,is_dict=isDict(e))
|
|
|
+ elif hasattr(e,'pfmt') and type(e) != type:
|
|
|
+ out.append('{:>{l}}{:10} {}'.format(
|
|
|
+ '',
|
|
|
+ k,
|
|
|
+ e.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
|
|
|
+ l = (lvl*8)+4 ))
|
|
|
+ else:
|
|
|
+ out.append('{:>{l}}{:<10} {:16} {}\n'.format(
|
|
|
+ '',
|
|
|
+ k,
|
|
|
+ f'<{type(e).__name__}>',
|
|
|
+ repr(e),
|
|
|
+ l=(lvl*8)+4 ))
|
|
|
+
|
|
|
+ import re
|
|
|
+ return re.sub('\n+','\n',''.join(out))
|