objmethods.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. objmethods.py: Mixin classes for MMGen data objects
  20. """
  21. import unicodedata
  22. from .color import *
  23. from .globalvars import g
  24. from .devtools import *
  25. def truncate_str(s,width): # width = screen width
  26. wide_count = 0
  27. for i in range(len(s)):
  28. wide_count += unicodedata.east_asian_width(s[i]) in ('F','W')
  29. if wide_count + i >= width:
  30. return s[:i] + ('',' ')[
  31. unicodedata.east_asian_width(s[i]) in ('F','W')
  32. and wide_count + i == width]
  33. else: # pad the string to width if necessary
  34. return s + ' '*(width-len(s)-wide_count)
  35. class Hilite:
  36. color = 'red'
  37. width = 0
  38. trunc_ok = True
  39. @classmethod
  40. # 'width' is screen width (greater than len(s) for CJK strings)
  41. # 'append_chars' and 'encl' must consist of single-width chars only
  42. def fmtc(cls,s,width=None,color=False,encl='',trunc_ok=None,
  43. center=False,nullrepl='',append_chars='',append_color=False):
  44. s_wide_count = len([1 for ch in s if unicodedata.east_asian_width(ch) in ('F','W')])
  45. if encl:
  46. a,b = list(encl)
  47. add_len = len(append_chars) + 2
  48. else:
  49. a,b = ('','')
  50. add_len = len(append_chars)
  51. if width == None:
  52. width = cls.width
  53. if trunc_ok == None:
  54. trunc_ok = cls.trunc_ok
  55. if g.test_suite:
  56. assert isinstance(encl,str) and len(encl) in (0,2),"'encl' must be 2-character str"
  57. assert width >= 2 + add_len, f'{s!r}: invalid width ({width}) (must be at least 2)' # CJK: 2 cells
  58. if len(s) + s_wide_count + add_len > width:
  59. assert trunc_ok, "If 'trunc_ok' is false, 'width' must be >= screen width of string"
  60. s = truncate_str(s,width-add_len)
  61. if s == '' and nullrepl:
  62. s = nullrepl.center(width)
  63. else:
  64. s = a+s+b
  65. if center:
  66. s = s.center(width)
  67. if append_chars:
  68. return (
  69. cls.colorize(s,color=color)
  70. + cls.colorize(
  71. append_chars.ljust(width-len(s)-s_wide_count),
  72. color_override = append_color ))
  73. else:
  74. return cls.colorize(s.ljust(width-s_wide_count),color=color)
  75. @classmethod
  76. def colorize(cls,s,color=True,color_override=''):
  77. return globals()[color_override or cls.color](s) if color else s
  78. def fmt(self,*args,**kwargs):
  79. assert args == () # forbid invocation w/o keywords
  80. return self.fmtc(self,*args,**kwargs)
  81. @classmethod
  82. def hlc(cls,s,color=True,encl=''):
  83. if encl:
  84. assert isinstance(encl,str) and len(encl) == 2, "'encl' must be 2-character str"
  85. s = encl[0] + s + encl[1]
  86. return cls.colorize(s,color=color)
  87. def hl(self,*args,**kwargs):
  88. assert args == () # forbid invocation w/o keywords
  89. return self.hlc(self,*args,**kwargs)
  90. class InitErrors:
  91. @classmethod
  92. def init_fail(cls,e,m,e2=None,m2=None,objname=None,preformat=False):
  93. if preformat:
  94. errmsg = m
  95. else:
  96. errmsg = '{!r}: value cannot be converted to {} {}({!s})'.format(
  97. m,
  98. (objname or cls.__name__),
  99. (f'({e2!s}) ' if e2 else ''),
  100. e )
  101. if m2:
  102. errmsg = repr(m2) + '\n' + errmsg
  103. if hasattr(cls,'passthru_excs') and type(e) in cls.passthru_excs:
  104. raise
  105. elif hasattr(cls,'exc'):
  106. raise cls.exc(errmsg)
  107. else:
  108. from .exception import ObjectInitError
  109. raise ObjectInitError(errmsg)
  110. @classmethod
  111. def method_not_implemented(cls):
  112. import traceback
  113. raise NotImplementedError(
  114. 'method {}() not implemented for class {!r}'.format(
  115. traceback.extract_stack()[-2].name, cls.__name__) )