123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- #!/usr/bin/env python3
- #
- # MMGen Wallet, a terminal-based cryptocurrency wallet
- # Copyright (C)2013-2025 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-wallet
- # https://gitlab.com/mmgen/mmgen-wallet
- """
- devtools: Developer tools for the MMGen suite
- """
- import sys
- color_funcs = {
- name: lambda s, n=n: f'\033[{n};1m{s}\033[0m'
- for name, n in (
- ('red', 31),
- ('green', 32),
- ('yellow', 33),
- ('blue', 34),
- ('purple', 35))
- }
- def pfmt(*args, color=None):
- import pprint
- ret = pprint.PrettyPrinter(indent=4, width=116).pformat(
- args if len(args) > 1 else '' if not args else args[0])
- return color_funcs[color](ret) if color else ret
- def pmsg(*args, color=None):
- sys.stderr.write(pfmt(*args, color=color) + '\n')
- def pmsg_r(*args, color=None):
- sys.stderr.write(pfmt(*args, color=color))
- def pdie(*args, exit_val=1):
- pmsg(*args, color='red' if exit_val else None)
- sys.exit(exit_val)
- def pexit(*args):
- pdie(*args, exit_val=0)
- def Pmsg(*args, color=None):
- sys.stdout.write(pfmt(*args, color=color) + '\n')
- def Pdie(*args, exit_val=1):
- Pmsg(*args, color=('yellow' if exit_val == 1 else 'red' if exit_val else None))
- 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):
- if not fh_list:
- import os
- 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, color=None):
- sys.stdout.write('\n'+self.pfmt(color=color))
- def pdie(self, exit_val=1):
- self.pmsg(color='red' if exit_val else None)
- sys.exit(exit_val)
- def pexit(self):
- self.pdie(exit_val=0)
- def pfmt(self, lvl=0, id_list=[], color=None):
- from decimal import Decimal
- scalars = (str, int, float, Decimal)
- def isDict(obj):
- return isinstance(obj, dict)
- def isList(obj):
- return isinstance(obj, list)
- def isScalar(obj):
- return isinstance(obj, scalars)
- 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(
- '',
- 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')
- 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 callable(e.pfmt) and not isinstance(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
- ret = re.sub('\n+', '\n', ''.join(out))
- return color_funcs[color](ret) if color else ret
|