From 823e325c6ce175fd499ba12ed4506290da3a3d5e Mon Sep 17 00:00:00 2001 From: MMGen Date: Wed, 27 Mar 2019 16:52:26 +0000 Subject: [PATCH] [msys2]: enable color output, handle exceptions in main.py --- mmgen/color.py | 55 ++++++++++++++++++++------------- mmgen/globalvars.py | 4 +-- mmgen/main.py | 59 ++++++++++++++++-------------------- mmgen/opts.py | 9 +++--- mmgen/term.py | 3 ++ mmgen/util.py | 40 +++++++++++++++--------- scripts/test-release.sh | 3 +- test/test_py_d/common.py | 17 +++++------ test/test_py_d/ts_base.py | 2 +- test/test_py_d/ts_regtest.py | 2 +- test/test_py_d/ts_wallet.py | 1 + 11 files changed, 107 insertions(+), 88 deletions(-) diff --git a/mmgen/color.py b/mmgen/color.py index e7b2bf3f..c8a9377e 100755 --- a/mmgen/color.py +++ b/mmgen/color.py @@ -52,30 +52,42 @@ for c in _colors: def nocolor(s): return s -def init_color(enable_color=True,num_colors='auto'): - if enable_color: - assert num_colors in ('auto',8,16,256) - globals()['_reset'] = '\033[0m' - if num_colors in (8,16): - pfx = '_16_' - elif num_colors in (256,): +def init_color(num_colors='auto'): + assert num_colors in ('auto',8,16,256) + globals()['_reset'] = '\033[0m' + if num_colors in (8,16): + pfx = '_16_' + elif num_colors in (256,): + pfx = '_256_' + else: + try: + import os + assert os.environ['TERM'][-8:] == '256color' pfx = '_256_' - else: + except: try: - import os - assert os.environ['TERM'][-8:] == '256color' - pfx = '_256_' + import subprocess + a = subprocess.check_output(['infocmp','-0']).decode() + b = [e.split('#')[1] for e in a.split(',') if e[:6] == 'colors'][0] + pfx = ('_16_','_256_')[b=='256'] except: - try: - import subprocess - a = subprocess.check_output(['infocmp','-0']) - b = [e.split('#')[1] for e in a.split(',') if e[:6] == 'colors'][0] - pfx = ('_16_','_256_')[b=='256'] - except: - pfx = '_16_' + pfx = '_16_' - for c in _colors: - globals()['_clr_'+c] = globals()[pfx+c] + for c in _colors: + globals()['_clr_'+c] = globals()[pfx+c] + +def start_mscolor(): + import sys + from mmgen.globalvars import g + try: + import colorama + colorama.init(strip=True,convert=True) + except: + from mmgen.util import msg + msg('Import of colorama module failed') + else: + g.stdout = sys.stdout + g.stderr = sys.stderr def test_color(): try: @@ -83,8 +95,9 @@ def test_color(): colorama.init(strip=True,convert=True) except: pass + for desc,n in (('auto','auto'),('8-color',8),('256-color',256)): - if n != 'auto': init_color(num_colors=n) + init_color(num_colors=n) print('{:9}: {}'.format(desc,' '.join([globals()[c](c) for c in sorted(_colors)]))) if __name__ == '__main__': test_color() diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index e7b84a52..848aff17 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -45,7 +45,7 @@ class g(object): author = 'The MMGen Project' email = '' Cdates = '2013-2019' - keywords = 'Bitcoin, BTC, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin, Armbian, Raspbian, Raspberry Pi, Orange Pi, BCash, BCH, Litecoin, LTC, altcoin, ZEC, Zcash, DASH, Dashpay, ETH, Ethereum, Classic, SHA256Compress, XMR, Monero, monerod, EMC, Emercoin, ERC20, token, deploy, contract, gas, fee, smart contract, solidity, Parity, testnet, devmode, Kovan' + keywords = 'Bitcoin, BTC, Ethereum, ETH, Monero, XMR, ERC20, cryptocurrency, wallet, BIP32, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MSYS2, MinGW, MinGW64, MSWin, Armbian, Raspbian, Raspberry Pi, Orange Pi, BCash, BCH, Litecoin, LTC, altcoin, ZEC, Zcash, DASH, Dashpay, SHA256Compress, monerod, EMC, Emercoin, token, deploy, contract, gas, fee, smart contract, solidity, Parity, testnet, devmode, Kovan' max_int = 0xffffffff stdin_tty = bool(sys.stdin.isatty() or os.getenv('MMGEN_TEST_SUITE_POPEN_SPAWN')) @@ -111,7 +111,7 @@ class g(object): else: die(1,"'{}': platform not supported by {}\n".format(sys.platform,proj_name)) - color = sys.stdout.isatty() and platform != 'win' + color = sys.stdout.isatty() if os.getenv('HOME'): # Linux or MSYS home_dir = os.getenv('HOME') diff --git a/mmgen/main.py b/mmgen/main.py index 945cab27..5497a1b9 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -26,41 +26,34 @@ def launch(what): what = 'wallet' if what == 'keygen': what = 'addrgen' - import sys + import sys,os + from mmgen.globalvars import g + + if g.platform == 'linux' and sys.stdin.isatty(): + import termios,atexit + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + atexit.register(lambda: termios.tcsetattr(fd,termios.TCSADRAIN,old)) try: - import termios - platform = 'linux' - except: - platform = 'win' - - if platform == 'win': __import__('mmgen.main_' + what) - else: - import os,atexit - if sys.stdin.isatty(): - fd = sys.stdin.fileno() - old = termios.tcgetattr(fd) - def at_exit(): termios.tcsetattr(fd, termios.TCSADRAIN, old) - atexit.register(at_exit) - try: __import__('mmgen.main_' + what) - except KeyboardInterrupt: - sys.stderr.write('\nUser interrupt\n') - except EOFError: - sys.stderr.write('\nEnd of file\n') - except Exception as e: - if os.getenv('MMGEN_TRACEBACK'): - raise - else: - try: m = '{}'.format(e.args[0]) - except: m = repr(e.args[0]) + except KeyboardInterrupt: + sys.stderr.write('\nUser interrupt\n') + except EOFError: + sys.stderr.write('\nEnd of file\n') + except Exception as e: + if os.getenv('MMGEN_TRACEBACK'): + raise + else: + try: m = '{}'.format(e.args[0]) + except: m = repr(e.args[0]) - from mmgen.util import die,ydie,rdie - d = [ (ydie,2,'\nMMGen Unhandled Exception ({n}): {m}'), - (die, 1,'{m}'), - (ydie,2,'{m}'), - (ydie,3,'\nMMGen Error ({n}): {m}'), - (rdie,4,'\nMMGen Fatal Error ({n}): {m}') - ][e.mmcode if hasattr(e,'mmcode') else 0] + from mmgen.util import die,ydie,rdie + d = [ (ydie,2,'\nMMGen Unhandled Exception ({n}): {m}'), + (die, 1,'{m}'), + (ydie,2,'{m}'), + (ydie,3,'\nMMGen Error ({n}): {m}'), + (rdie,4,'\nMMGen Fatal Error ({n}): {m}') + ][e.mmcode if hasattr(e,'mmcode') else 0] - d[0](d[1],d[2].format(n=type(e).__name__,m=m)) + d[0](d[1],d[2].format(n=type(e).__name__,m=m)) diff --git a/mmgen/opts.py b/mmgen/opts.py index 2a10a98e..3de922c7 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -75,10 +75,11 @@ def opt_postproc_initializations(): from mmgen.term import set_terminal_vars set_terminal_vars() - from mmgen.color import init_color - init_color(enable_color=g.color,num_colors=('auto',256)[bool(g.force_256_color)]) - - if g.color and g.platform == 'win': start_mscolor() + if g.color: # MMGEN_DISABLE_COLOR sets this to False + from mmgen.color import start_mscolor,init_color + if g.platform == 'win': + start_mscolor() + init_color(num_colors=('auto',256)[bool(g.force_256_color)]) g.coin = g.coin.upper() # allow user to use lowercase g.dcoin = g.coin diff --git a/mmgen/term.py b/mmgen/term.py index a7475cb3..aec748c1 100755 --- a/mmgen/term.py +++ b/mmgen/term.py @@ -112,6 +112,7 @@ def _kb_hold_protect_mswin(): def _get_keypress_mswin(prompt='',immed_chars='',prehold_protect=True,num_chars=None): msg_r(prompt) + sys.stderr.flush() timeout = float(0.5) while True: @@ -134,12 +135,14 @@ def _get_keypress_mswin(prompt='',immed_chars='',prehold_protect=True,num_chars= def _get_keypress_mswin_raw(prompt='',immed_chars='',prehold_protect=None,num_chars=None): msg_r(prompt) + sys.stderr.flush() ch = msvcrt.getch() if ch == 3: raise KeyboardInterrupt return ch def _get_keypress_mswin_stub(prompt='',immed_chars='',prehold_protect=None,num_chars=None): msg_r(prompt) + sys.stderr.flush() return os.read(0,1) def _get_terminal_size_linux(): diff --git a/mmgen/util.py b/mmgen/util.py index 8df2c5f6..d153d66e 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -25,11 +25,26 @@ from hashlib import sha256 from string import hexdigits,digits from mmgen.color import * from mmgen.exception import * +from mmgen.globalvars import g -def msg(s): g.stderr.write(s + '\n') -def msg_r(s): g.stderr.write(s) -def Msg(s): g.stdout.write(s + '\n') -def Msg_r(s): g.stdout.write(s) +if g.platform == 'win': + def msg_r(s): + try: + g.stderr.write(s) + except: + os.write(2,s.encode()) + def Msg_r(s): + try: + g.stdout.write(s) + except: + os.write(1,s.encode()) + def msg(s): msg_r(s + '\n') + def Msg(s): Msg_r(s + '\n') +else: + def msg_r(s): g.stderr.write(s) + def Msg_r(s): g.stdout.write(s) + def msg(s): g.stderr.write(s + '\n') + def Msg(s): g.stdout.write(s + '\n') def msgred(s): msg(red(s)) def rmsg(s): msg(red(s)) @@ -428,15 +443,6 @@ def strip_comments(line): def remove_comments(lines): return [m for m in [strip_comments(l) for l in lines] if m != ''] -from mmgen.globalvars import g - -def start_mscolor(): - try: - import colorama - colorama.init(strip=True,convert=True) - except: - msg('Import of colorama module failed') - def get_hash_params(hash_preset): if hash_preset in g.hash_presets: return g.hash_presets[hash_preset] # N,p,r,buflen @@ -756,7 +762,13 @@ def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True): reply = input(prompt) else: from getpass import getpass - reply = getpass(prompt) + if g.platform == 'win': + # MSWin hack - getpass('foo') doesn't flush stderr + msg_r(prompt.strip()) # getpass('') adds a space + sys.stderr.flush() + reply = getpass('') + else: + reply = getpass(prompt) kb_hold_protect() diff --git a/scripts/test-release.sh b/scripts/test-release.sh index 3f9acfa1..48b8c691 100755 --- a/scripts/test-release.sh +++ b/scripts/test-release.sh @@ -1,6 +1,5 @@ #!/bin/bash -# Tested on Linux, MinGW-64 -# MinGW's bash 3.1.17 doesn't do ${var^^} +# Tested on Linux, MSys2 trap 'echo -e "${GREEN}Exiting at user request$RESET"; exit' INT diff --git a/test/test_py_d/common.py b/test/test_py_d/common.py index 31c89f6b..48150419 100755 --- a/test/test_py_d/common.py +++ b/test/test_py_d/common.py @@ -68,7 +68,7 @@ chksum_pat = r'\b[A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4}\b' def ok_msg(): if opt.profile: return - os.write(2,green('\nOK\n').encode() if opt.exact_output or opt.verbose else b' OK\n') + sys.stderr.write(green('\nOK\n') if opt.exact_output or opt.verbose else ' OK\n') def skip(name,reason=None): msg('Skipping {}{}'.format(name,' ({})'.format(reason) if reason else '')) @@ -81,9 +81,9 @@ def confirm_continue(): raise KeyboardInterrupt('Exiting at user request') def omsg(s): - os.write(2,s.encode() + b'\n') + sys.stderr.write(s + '\n') def omsg_r(s): - os.write(2,s.encode()) + sys.stderr.write(s) def imsg(s): if opt.exact_output or opt.verbose: omsg(s) def imsg_r(s): @@ -93,13 +93,10 @@ def iqmsg(s): def iqmsg_r(s): if not opt.quiet: omsg_r(s) -if g.platform == 'win': - def silence(): pass -else: - devnull_fh = open('/dev/null','w') - def silence(): - if not (opt.verbose or opt.exact_output): - g.stdout = g.stderr = devnull_fh +devnull_fh = open(('/dev/null','null.out')[g.platform == 'win'],'w') +def silence(): + if not (opt.verbose or opt.exact_output): + g.stdout = g.stderr = devnull_fh def end_silence(): if not (opt.verbose or opt.exact_output): diff --git a/test/test_py_d/ts_base.py b/test/test_py_d/ts_base.py index 38f7843c..d2ebb83f 100755 --- a/test/test_py_d/ts_base.py +++ b/test/test_py_d/ts_base.py @@ -67,7 +67,7 @@ class TestSuiteBase(object): def skip_for_win(self): if g.platform == 'win': - msg("Skipping test '{}': not supported on MinGW platform".format(self.test_name)) + msg("Skipping test '{}': not supported on MSys2 platform".format(self.test_name)) return True else: return False diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index aace280f..18a92c58 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -527,7 +527,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): return t def alice_add_label_badaddr1(self): - return self.alice_add_label_badaddr(rt_pw,'Invalid coin address for this chain: '+rt_pw) + return self.alice_add_label_badaddr(rt_pw,'Invalid coin address for this chain: ') def alice_add_label_badaddr2(self): addr = g.proto.pubhash2addr('00'*20,False) # mainnet zero address diff --git a/test/test_py_d/ts_wallet.py b/test/test_py_d/ts_wallet.py index 743fdaf2..951ff906 100755 --- a/test/test_py_d/ts_wallet.py +++ b/test/test_py_d/ts_wallet.py @@ -147,6 +147,7 @@ class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared): pw = True ) def ref_hincog_blkdev_conv_out(self): + if self.skip_for_win(): return 'skip' imsg('Creating block device image file') ic_img = joinpath(self.tmpdir,'hincog_blkdev_img') subprocess.check_output(['dd','if=/dev/zero','of='+ic_img,'bs=1K','count=1'],stderr=subprocess.PIPE)