From 8e456dea5e5933d88559eba77e51c33e678d4e1f Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 22 Mar 2015 16:52:35 -0700 Subject: [PATCH] rewrite CLI tools to use argparse, remove Twisted dependency We used to use twisted.python.usage.Options, hence we depended upon Twisted. Now we depend upon "argparse" instead, which is in the py2.7 stdlib (and on pypi for 2.6). This package will still (eventually) provide Twisted support, but applications which need it will already express a dependency on twisted themselves, so by removing the dependency here, we make life easier for applications that don't use it. --- setup.py | 2 +- src/wormhole/scripts/cmd_receive_file.py | 10 +- src/wormhole/scripts/cmd_receive_text.py | 6 +- src/wormhole/scripts/cmd_send_file.py | 8 +- src/wormhole/scripts/cmd_send_text.py | 6 +- src/wormhole/scripts/runner.py | 134 ++++++++++------------- 6 files changed, 73 insertions(+), 93 deletions(-) diff --git a/setup.py b/setup.py index bb4b6e3..ac4b072 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup(name="wormhole-sync", "wormhole.test", "wormhole.util"], entry_points={"console_scripts": ["wormhole = wormhole.scripts.runner:entry"]}, - install_requires=["spake2", "pynacl", "requests", "twisted"], + install_requires=["spake2", "pynacl", "requests", "argparse"], test_suite="wormhole.test", cmdclass=commands, ) diff --git a/src/wormhole/scripts/cmd_receive_file.py b/src/wormhole/scripts/cmd_receive_file.py index dcd677b..f5d4a3c 100644 --- a/src/wormhole/scripts/cmd_receive_file.py +++ b/src/wormhole/scripts/cmd_receive_file.py @@ -6,9 +6,9 @@ from .progress import start_progress, update_progress, finish_progress APPID = "lothar.com/wormhole/file-xfer" -def receive_file(so): +def receive_file(args): # we're receiving - transit_receiver = TransitReceiver(transit_relay=so.parent["transit-helper"]) + transit_receiver = TransitReceiver(transit_relay=args.transit_helper) mydata = json.dumps({ "transit": { @@ -16,8 +16,8 @@ def receive_file(so): "relay_connection_hints": transit_receiver.get_relay_hints(), }, }).encode("utf-8") - r = Receiver(APPID, mydata, so.parent["relay-url"]) - code = so["code"] + r = Receiver(APPID, mydata, args.relay_url) + code = args.code if not code: code = r.input_code("Enter receive-file wormhole code: ") r.set_code(code) @@ -44,7 +44,7 @@ def receive_file(so): print("Receiving %d bytes for '%s' (%s).." % (filesize, filename, transit_receiver.describe())) - target = so["output-file"] + target = args.output_file if not target: # allow the sender to specify the filename, but only write to the # current directory, and never overwrite anything diff --git a/src/wormhole/scripts/cmd_receive_text.py b/src/wormhole/scripts/cmd_receive_text.py index a8215d4..c338b64 100644 --- a/src/wormhole/scripts/cmd_receive_text.py +++ b/src/wormhole/scripts/cmd_receive_text.py @@ -4,11 +4,11 @@ from wormhole.blocking.transcribe import Receiver, WrongPasswordError APPID = "lothar.com/wormhole/text-xfer" -def receive_text(so): +def receive_text(args): # we're receiving data = json.dumps({"message": "ok"}).encode("utf-8") - r = Receiver(APPID, data, so.parent["relay-url"]) - code = so["code"] + r = Receiver(APPID, data, args.relay_url) + code = args.code if not code: code = r.input_code("Enter receive-text wormhole code: ") r.set_code(code) diff --git a/src/wormhole/scripts/cmd_send_file.py b/src/wormhole/scripts/cmd_send_file.py index 12d8027..33e714f 100644 --- a/src/wormhole/scripts/cmd_send_file.py +++ b/src/wormhole/scripts/cmd_send_file.py @@ -6,11 +6,11 @@ from .progress import start_progress, update_progress, finish_progress APPID = "lothar.com/wormhole/file-xfer" -def send_file(so): +def send_file(args): # we're sending - filename = so["filename"] + filename = args.filename assert os.path.isfile(filename) - transit_sender = TransitSender(transit_relay=so.parent["transit-helper"]) + transit_sender = TransitSender(transit_relay=args.transit_helper) filesize = os.stat(filename).st_size data = json.dumps({ @@ -24,7 +24,7 @@ def send_file(so): }, }).encode("utf-8") - i = Initiator(APPID, data, so.parent["relay-url"]) + i = Initiator(APPID, data, args.relay_url) code = i.get_code() print("On the other computer, please run: wormhole receive-file") print("Wormhole code is '%s'" % code) diff --git a/src/wormhole/scripts/cmd_send_text.py b/src/wormhole/scripts/cmd_send_text.py index 8a8b45c..2822400 100644 --- a/src/wormhole/scripts/cmd_send_text.py +++ b/src/wormhole/scripts/cmd_send_text.py @@ -4,12 +4,12 @@ from wormhole.blocking.transcribe import Initiator, WrongPasswordError APPID = "lothar.com/wormhole/text-xfer" -def send_text(so): +def send_text(args): # we're sending - message = so["text"] + message = args.text data = json.dumps({"message": message, }).encode("utf-8") - i = Initiator(APPID, data, so.parent["relay-url"]) + i = Initiator(APPID, data, args.relay_url) code = i.get_code() print("On the other computer, please run: wormhole receive-text") print("Wormhole code is: %s" % code) diff --git a/src/wormhole/scripts/runner.py b/src/wormhole/scripts/runner.py index b5132ae..5f10d86 100644 --- a/src/wormhole/scripts/runner.py +++ b/src/wormhole/scripts/runner.py @@ -1,93 +1,69 @@ -import sys -from twisted.python import usage +import sys, argparse +from textwrap import dedent from .. import public_relay +from .. import __version__ +from . import cmd_send_text, cmd_receive_text, cmd_send_file, cmd_receive_file -class SendTextOptions(usage.Options): - def parseArgs(self, text): - self["text"] = text - synopsis = "TEXT" +parser = argparse.ArgumentParser( + usage="wormhole SUBCOMMAND (subcommand-options)", + description=dedent(""" + Create a Magic Wormhole and communicate through it. Wormholes are created + by speaking the same magic CODE in two different places at the same time. + Wormholes are secure against anyone who doesn't use the same code."""), + ) +parser.add_argument("--version", action="version", + version="magic-wormhole "+ __version__) +g = parser.add_argument_group("wormhole configuration options") +g.add_argument("--relay-url", default=public_relay.RENDEZVOUS_RELAY, + metavar="URL", help="rendezvous relay to use") +g.add_argument("--transit-helper", default=public_relay.TRANSIT_RELAY, + metavar="tcp:HOST:PORT", help="transit relay to use") +subparsers = parser.add_subparsers(title="subcommands", + dest="subcommand") -class ReceiveTextOptions(usage.Options): - def parseArgs(self, code=None): - self["code"] = code - synopsis = "[CODE]" -class SendFileOptions(usage.Options): - def parseArgs(self, filename): - self["filename"] = filename - synopsis = "FILENAME" +p = subparsers.add_parser("send-text", description="Send a text mesasge", + usage="wormhole send-text TEXT") +p.add_argument("text", metavar="TEXT", help="the message to send (a string)") +p.set_defaults(func=cmd_send_text.send_text) -class ReceiveFileOptions(usage.Options): - optParameters = [ - ["output-file", "o", None, "File to create"], - ] - def parseArgs(self, code=None): - self["code"] = code - synopsis = "[CODE]" +p = subparsers.add_parser("receive-text", description="Receive a text message", + usage="wormhole receive-text [CODE]") +p.add_argument("code", nargs="?", default=None, metavar="[CODE]", + help=dedent("""\ + The magic-wormhole code, from the sender. If omitted, the + program will ask for it, using tab-completion."""), + ) +p.set_defaults(func=cmd_receive_text.receive_text) -class Options(usage.Options): - synopsis = "\nUsage: wormhole " - optParameters = [ - ["relay-url", None, public_relay.RENDEZVOUS_RELAY, - "rendezvous relay to use (URL)"], - ["transit-helper", None, public_relay.TRANSIT_RELAY, - "transit relay to use (tcp:HOST:PORT)"], - ] - subCommands = [("send-text", None, SendTextOptions, "Send a text message"), - ("send-file", None, SendFileOptions, "Send a file"), - ("receive-text", None, ReceiveTextOptions, "Receive a text message"), - ("receive-file", None, ReceiveFileOptions, "Receive a file"), - ] +p = subparsers.add_parser("send-file", description="Send a file", + usage="wormhole send-file FILENAME") +p.add_argument("filename", metavar="FILENAME", help="The file to be sent") +p.set_defaults(func=cmd_send_file.send_file) - def getUsage(self, **kwargs): - t = usage.Options.getUsage(self, **kwargs) - return t + "\nPlease run 'wormhole --help' for more details on each command.\n" +p = subparsers.add_parser("receive-file", description="Receive a file", + usage="wormhole receive-file [-o FILENAME] [CODE]") +p.add_argument("-o", "--output-file", default=None, metavar="FILENAME", + help=dedent("""\ + The file to create, overriding the filename suggested by the + sender"""), + ) +p.add_argument("code", nargs="?", default=None, metavar="[CODE]", + help=dedent("""\ + The magic-wormhole code, from the sender. If omitted, the + program will ask for it, using tab-completion."""), + ) +p.set_defaults(func=cmd_receive_file.receive_file) - def postOptions(self): - if not hasattr(self, 'subOptions'): - raise usage.UsageError("must specify a command") - -def send_text(*args): - from . import cmd_send_text - return cmd_send_text.send_text(*args) - -def receive_text(*args): - from . import cmd_receive_text - return cmd_receive_text.receive_text(*args) - -def send_file(*args): - from . import cmd_send_file - return cmd_send_file.send_file(*args) - -def receive_file(*args): - from . import cmd_receive_file - return cmd_receive_file.receive_file(*args) - -DISPATCH = {"send-text": send_text, - "receive-text": receive_text, - "send-file": send_file, - "receive-file": receive_file, - } def run(args, stdout, stderr, executable=None): """This is invoked directly by the 'wormhole' entry-point script. It can also invoked by entry() below.""" - config = Options() + + args = parser.parse_args() try: - config.parseOptions(args) - except usage.error, e: - c = config - while hasattr(c, 'subOptions'): - c = c.subOptions - print >>stderr, str(c) - print >>stderr, e.args[0] - return 1 - command = config.subCommand - so = config.subOptions - so["executable"] = executable - try: - #rc = DISPATCH[command](so, stdout, stderr) - rc = DISPATCH[command](so) + #rc = command.func(args, stdout, stderr) + rc = args.func(args) return rc except ImportError, e: print >>stderr, "--- ImportError ---" @@ -100,3 +76,7 @@ def entry(): """This is used by a setuptools entry_point. When invoked this way, setuptools has already put the installed package on sys.path .""" return run(sys.argv[1:], sys.stdout, sys.stderr, executable=sys.argv[0]) + +if __name__ == "__main__": + args = parser.parse_args() + print args