devtools.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. devtools: Developer tools for the MMGen suite
  12. """
  13. import sys
  14. color_funcs = {
  15. name: lambda s, n=n: f'\033[{n};1m{s}\033[0m'
  16. for name,n in (
  17. ('red', 31),
  18. ('green', 32),
  19. ('yellow',33),
  20. ('blue', 34),
  21. ('purple',35))
  22. }
  23. def pfmt(*args,color=None):
  24. import pprint
  25. ret = pprint.PrettyPrinter(indent=4,width=116).pformat(
  26. args if len(args) > 1 else '' if not args else args[0] )
  27. return color_funcs[color](ret) if color else ret
  28. def pmsg(*args,color=None):
  29. sys.stderr.write(pfmt(*args,color=color) + '\n')
  30. def pmsg_r(*args,color=None):
  31. sys.stderr.write(pfmt(*args,color=color))
  32. def pdie(*args,exit_val=1):
  33. pmsg(*args,color='red' if exit_val else None)
  34. sys.exit(exit_val)
  35. def pexit(*args):
  36. pdie(*args,exit_val=0)
  37. def Pmsg(*args,color=None):
  38. sys.stdout.write(pfmt(*args,color=color) + '\n')
  39. def Pdie(*args,exit_val=1):
  40. Pmsg(*args,color=('yellow' if exit_val == 1 else 'red' if exit_val else None))
  41. sys.exit(exit_val)
  42. def Pexit(*args):
  43. Pdie(*args,exit_val=0)
  44. def print_stack_trace(message=None,fh_list=[],nl='\n',sep='\n ',trim=4):
  45. import os
  46. if not fh_list:
  47. fh_list.append(open(f'devtools.trace.{os.getpid()}','w'))
  48. nl = ''
  49. res = get_stack_trace(message,nl,sep,trim)
  50. sys.stderr.write(res)
  51. fh_list[0].write(res)
  52. def get_stack_trace(message=None,nl='\n',sep='\n ',trim=3):
  53. import os,re,traceback
  54. tb = [t for t in traceback.extract_stack() if t.filename[:1] != '<']
  55. fs = '{}:{}: in {}:\n {}'
  56. out = [
  57. fs.format(
  58. re.sub(r'^\./','',os.path.relpath(t.filename)),
  59. t.lineno,
  60. (t.name+'()' if t.name[-1] != '>' else t.name),
  61. t.line or '(none)')
  62. for t in (tb[:-trim] if trim else tb) ]
  63. return f'{nl}STACK TRACE {message or "[unnamed]"}:{sep}{sep.join(out)}\n'
  64. def print_diff(*args,**kwargs):
  65. sys.stderr.write(get_diff(*args,**kwargs))
  66. def get_diff(a,b,a_fn='',b_fn='',from_json=True):
  67. if from_json:
  68. import json
  69. a = json.dumps(json.loads(a),indent=4)
  70. b = json.dumps(json.loads(b),indent=4)
  71. from difflib import unified_diff
  72. # chunk headers have trailing newlines, hence the rstrip()
  73. return ' DIFF:\n {}\n'.format(
  74. '\n '.join(a.rstrip('\n') for a in unified_diff(
  75. a.split('\n'),
  76. b.split('\n'),
  77. a_fn,
  78. b_fn )))
  79. def print_ndiff(*args,**kwargs):
  80. sys.stderr.write(get_ndiff(*args,**kwargs))
  81. def get_ndiff(a,b):
  82. from difflib import ndiff
  83. return list(ndiff(
  84. a.split('\n'),
  85. b.split('\n') ))
  86. class MMGenObjectMethods: # mixin class for MMGenObject
  87. # Pretty-print an MMGenObject instance, recursing into sub-objects - WIP
  88. def pmsg(self,color=None):
  89. sys.stdout.write('\n'+self.pfmt(color=color))
  90. def pdie(self,exit_val=1):
  91. self.pmsg(color='red' if exit_val else None)
  92. sys.exit(exit_val)
  93. def pexit(self):
  94. self.pdie(exit_val=0)
  95. def pfmt(self,lvl=0,id_list=[],color=None):
  96. from decimal import Decimal
  97. scalars = (str,int,float,Decimal)
  98. def isDict(obj):
  99. return isinstance(obj,dict)
  100. def isList(obj):
  101. return isinstance(obj,list)
  102. def isScalar(obj):
  103. return isinstance(obj,scalars)
  104. def do_list(out,e,lvl=0,is_dict=False):
  105. out.append('\n')
  106. for i in e:
  107. el = i if not is_dict else e[i]
  108. if is_dict:
  109. # out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8))
  110. out.append('{s1}{i}{s2}'.format(
  111. i = i,
  112. s1 = ' ' * (4*lvl+8),
  113. s2 = ' ' * 10 ))
  114. if hasattr(el,'pfmt'):
  115. out.append('{:>{l}}{}'.format(
  116. '',
  117. el.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
  118. l = (lvl+1)*8 ))
  119. elif isinstance(el,scalars):
  120. if isList(e):
  121. out.append( '{:>{l}}{!r:16}\n'.format( '', el, l=lvl*8 ))
  122. else:
  123. out.append(f' {el!r}')
  124. elif isList(el) or isDict(el):
  125. indent = 1 if is_dict else lvl*8+4
  126. out.append('{:>{l}}{:16}'.format( '', f'<{type(el).__name__}>', l=indent ))
  127. if isList(el) and isinstance(el[0],scalars):
  128. out.append('\n')
  129. do_list(out,el,lvl=lvl+1,is_dict=isDict(el))
  130. else:
  131. out.append('{:>{l}}{:16} {!r}\n'.format( '', f'<{type(el).__name__}>', el, l=(lvl*8)+8 ))
  132. out.append('\n')
  133. if not e:
  134. out.append(f'{e!r}\n')
  135. out = [f'<{type(self).__name__}>{" "+repr(self) if isScalar(self) else ""}\n']
  136. if id(self) in id_list:
  137. return out[-1].rstrip() + ' [RECURSION]\n'
  138. if isList(self) or isDict(self):
  139. do_list(out,self,lvl=lvl,is_dict=isDict(self))
  140. for k in self.__dict__:
  141. e = getattr(self,k)
  142. if isList(e) or isDict(e):
  143. out.append('{:>{l}}{:<10} {:16}'.format( '', k, f'<{type(e).__name__}>', l=(lvl*8)+4 ))
  144. do_list(out,e,lvl=lvl,is_dict=isDict(e))
  145. elif hasattr(e,'pfmt') and callable(e.pfmt) and not isinstance(e,type):
  146. out.append('{:>{l}}{:10} {}'.format(
  147. '',
  148. k,
  149. e.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
  150. l = (lvl*8)+4 ))
  151. else:
  152. out.append('{:>{l}}{:<10} {:16} {}\n'.format(
  153. '',
  154. k,
  155. f'<{type(e).__name__}>',
  156. repr(e),
  157. l=(lvl*8)+4 ))
  158. import re
  159. ret = re.sub('\n+','\n',''.join(out))
  160. return color_funcs[color](ret) if color else ret