devtools.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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. if not fh_list:
  46. import os
  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