From 0185391c723339e3c60d5b1383519b51b6193f60 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 13 Feb 2019 23:42:31 -0500 Subject: [PATCH 1/2] Add --stdin to enter commands and arguments over stdin --- hwilib/cli.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/hwilib/cli.py b/hwilib/cli.py index f2f74fb..23eeaaf 100644 --- a/hwilib/cli.py +++ b/hwilib/cli.py @@ -58,7 +58,7 @@ def prompt_pin_handler(args, client): def send_pin_handler(args, client): return send_pin(client, pin=args.pin) -def process_commands(args): +def process_commands(cli_args): parser = argparse.ArgumentParser(description='Hardware Wallet Interface, version {}.\nAccess and send commands to a hardware wallet device. Responses are in JSON format.'.format(__version__), formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('--device-path', '-d', help='Specify the device path of the device to connect to') parser.add_argument('--device-type', '-t', help='Specify the type of device that will be connected. If `--device-path` not given, the first device of this type enumerated is used.') @@ -68,6 +68,7 @@ def process_commands(args): parser.add_argument('--debug', help='Print debug statements', action='store_true') parser.add_argument('--fingerprint', '-f', help='Specify the device to connect to using the first 4 bytes of the hash160 of the master public key. It will connect to the first device that matches this fingerprint.') parser.add_argument('--version', action='version', version='%(prog)s {}'.format(__version__)) + parser.add_argument('--stdin', help='Enter commands and arguments via stdin', action='store_true') subparsers = parser.add_subparsers(description='Commands', dest='command') # work-around to make subparser required @@ -135,7 +136,23 @@ def process_commands(args): sendpin_parser.add_argument('pin', help='The numeric positions of the PIN') sendpin_parser.set_defaults(func=send_pin_handler) - args = parser.parse_args(args) + if any(arg == '--stdin' for arg in cli_args): + blank_count = 0 + while True: + try: + line = input() + # Exit loop when we see 2 consecutive newlines (i.e. an empty line) + if line == '': + break + # Split the line and append it to the cli args + import shlex + cli_args.extend(shlex.split(line)) + except EOFError: + # If we see EOF, stop taking input + break + + # Parse arguments again for anything entered over stdin + args = parser.parse_args(cli_args) device_path = args.device_path device_type = args.device_type From 49bc7fa5dab02668c8a8b33cc4b6932acf688110 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 15 Feb 2019 00:14:37 -0500 Subject: [PATCH 2/2] Add tests for stdin interface and travis job --- .travis.yml | 2 ++ test/run_tests.py | 2 +- test/test_device.py | 5 +++++ test/test_keepkey.py | 5 +++++ test/test_trezor.py | 5 +++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94a8d57..1f5c38d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,8 @@ jobs: script: cd test; poetry run ./run_tests.py --interface=library - name: With command line interface script: cd test; poetry run ./run_tests.py --interface=cli + - name: With stdin interface + script: cd test; ./run_tests.py --interface=stdin - name: With linux binary distribution command line interface services: docker before_script: diff --git a/test/run_tests.py b/test/run_tests.py index 18c43a3..0d6493f 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -32,7 +32,7 @@ dbb_group.add_argument('--no_bitbox', help='Do not run Digital Bitbox test with dbb_group.add_argument('--bitbox', help='Path to Digital bitbox simulator.', default='work/mcu/build/bin/simulator') parser.add_argument('--bitcoind', help='Path to bitcoind.', default='work/bitcoin/src/bitcoind') -parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library') +parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist', 'stdin'], default='library') args = parser.parse_args() # Run tests diff --git a/test/test_device.py b/test/test_device.py index 03623ab..40fcdb2 100644 --- a/test/test_device.py +++ b/test/test_device.py @@ -87,6 +87,11 @@ class DeviceTestCase(unittest.TestCase): proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) result = proc.communicate() return json.loads(result[0].decode()) + elif self.interface == 'stdin': + input_str = '\n'.join(args) + '\n' + proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + result = proc.communicate(input_str.encode()) + return json.loads(result[0].decode()) else: return process_commands(args) diff --git a/test/test_keepkey.py b/test/test_keepkey.py index 4b116aa..5bd4834 100755 --- a/test/test_keepkey.py +++ b/test/test_keepkey.py @@ -90,6 +90,11 @@ class KeepkeyTestCase(unittest.TestCase): proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) result = proc.communicate() return json.loads(result[0].decode()) + elif self.interface == 'stdin': + input_str = '\n'.join(args) + '\n' + proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + result = proc.communicate(input_str.encode()) + return json.loads(result[0].decode()) else: return process_commands(args) diff --git a/test/test_trezor.py b/test/test_trezor.py index d2abfe9..747c55d 100755 --- a/test/test_trezor.py +++ b/test/test_trezor.py @@ -90,6 +90,11 @@ class TrezorTestCase(unittest.TestCase): proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True) result = proc.communicate() return json.loads(result[0].decode()) + elif self.interface == 'stdin': + input_str = '\n'.join(args) + '\n' + proc = subprocess.Popen(['hwi', '--stdin'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + result = proc.communicate(input_str.encode()) + return json.loads(result[0].decode()) else: return process_commands(args)