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.
This commit is contained in:
Brian Warner 2015-03-22 16:52:35 -07:00
parent 9e7d807171
commit 8e456dea5e
6 changed files with 73 additions and 93 deletions

View File

@ -23,7 +23,7 @@ setup(name="wormhole-sync",
"wormhole.test", "wormhole.util"], "wormhole.test", "wormhole.util"],
entry_points={"console_scripts": entry_points={"console_scripts":
["wormhole = wormhole.scripts.runner:entry"]}, ["wormhole = wormhole.scripts.runner:entry"]},
install_requires=["spake2", "pynacl", "requests", "twisted"], install_requires=["spake2", "pynacl", "requests", "argparse"],
test_suite="wormhole.test", test_suite="wormhole.test",
cmdclass=commands, cmdclass=commands,
) )

View File

@ -6,9 +6,9 @@ from .progress import start_progress, update_progress, finish_progress
APPID = "lothar.com/wormhole/file-xfer" APPID = "lothar.com/wormhole/file-xfer"
def receive_file(so): def receive_file(args):
# we're receiving # we're receiving
transit_receiver = TransitReceiver(transit_relay=so.parent["transit-helper"]) transit_receiver = TransitReceiver(transit_relay=args.transit_helper)
mydata = json.dumps({ mydata = json.dumps({
"transit": { "transit": {
@ -16,8 +16,8 @@ def receive_file(so):
"relay_connection_hints": transit_receiver.get_relay_hints(), "relay_connection_hints": transit_receiver.get_relay_hints(),
}, },
}).encode("utf-8") }).encode("utf-8")
r = Receiver(APPID, mydata, so.parent["relay-url"]) r = Receiver(APPID, mydata, args.relay_url)
code = so["code"] code = args.code
if not code: if not code:
code = r.input_code("Enter receive-file wormhole code: ") code = r.input_code("Enter receive-file wormhole code: ")
r.set_code(code) r.set_code(code)
@ -44,7 +44,7 @@ def receive_file(so):
print("Receiving %d bytes for '%s' (%s).." % (filesize, filename, print("Receiving %d bytes for '%s' (%s).." % (filesize, filename,
transit_receiver.describe())) transit_receiver.describe()))
target = so["output-file"] target = args.output_file
if not target: if not target:
# allow the sender to specify the filename, but only write to the # allow the sender to specify the filename, but only write to the
# current directory, and never overwrite anything # current directory, and never overwrite anything

View File

@ -4,11 +4,11 @@ from wormhole.blocking.transcribe import Receiver, WrongPasswordError
APPID = "lothar.com/wormhole/text-xfer" APPID = "lothar.com/wormhole/text-xfer"
def receive_text(so): def receive_text(args):
# we're receiving # we're receiving
data = json.dumps({"message": "ok"}).encode("utf-8") data = json.dumps({"message": "ok"}).encode("utf-8")
r = Receiver(APPID, data, so.parent["relay-url"]) r = Receiver(APPID, data, args.relay_url)
code = so["code"] code = args.code
if not code: if not code:
code = r.input_code("Enter receive-text wormhole code: ") code = r.input_code("Enter receive-text wormhole code: ")
r.set_code(code) r.set_code(code)

View File

@ -6,11 +6,11 @@ from .progress import start_progress, update_progress, finish_progress
APPID = "lothar.com/wormhole/file-xfer" APPID = "lothar.com/wormhole/file-xfer"
def send_file(so): def send_file(args):
# we're sending # we're sending
filename = so["filename"] filename = args.filename
assert os.path.isfile(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 filesize = os.stat(filename).st_size
data = json.dumps({ data = json.dumps({
@ -24,7 +24,7 @@ def send_file(so):
}, },
}).encode("utf-8") }).encode("utf-8")
i = Initiator(APPID, data, so.parent["relay-url"]) i = Initiator(APPID, data, args.relay_url)
code = i.get_code() code = i.get_code()
print("On the other computer, please run: wormhole receive-file") print("On the other computer, please run: wormhole receive-file")
print("Wormhole code is '%s'" % code) print("Wormhole code is '%s'" % code)

View File

@ -4,12 +4,12 @@ from wormhole.blocking.transcribe import Initiator, WrongPasswordError
APPID = "lothar.com/wormhole/text-xfer" APPID = "lothar.com/wormhole/text-xfer"
def send_text(so): def send_text(args):
# we're sending # we're sending
message = so["text"] message = args.text
data = json.dumps({"message": message, data = json.dumps({"message": message,
}).encode("utf-8") }).encode("utf-8")
i = Initiator(APPID, data, so.parent["relay-url"]) i = Initiator(APPID, data, args.relay_url)
code = i.get_code() code = i.get_code()
print("On the other computer, please run: wormhole receive-text") print("On the other computer, please run: wormhole receive-text")
print("Wormhole code is: %s" % code) print("Wormhole code is: %s" % code)

View File

@ -1,93 +1,69 @@
import sys import sys, argparse
from twisted.python import usage from textwrap import dedent
from .. import public_relay 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): parser = argparse.ArgumentParser(
def parseArgs(self, text): usage="wormhole SUBCOMMAND (subcommand-options)",
self["text"] = text description=dedent("""
synopsis = "TEXT" 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): p = subparsers.add_parser("send-text", description="Send a text mesasge",
def parseArgs(self, filename): usage="wormhole send-text TEXT")
self["filename"] = filename p.add_argument("text", metavar="TEXT", help="the message to send (a string)")
synopsis = "FILENAME" p.set_defaults(func=cmd_send_text.send_text)
class ReceiveFileOptions(usage.Options): p = subparsers.add_parser("receive-text", description="Receive a text message",
optParameters = [ usage="wormhole receive-text [CODE]")
["output-file", "o", None, "File to create"], p.add_argument("code", nargs="?", default=None, metavar="[CODE]",
] help=dedent("""\
def parseArgs(self, code=None): The magic-wormhole code, from the sender. If omitted, the
self["code"] = code program will ask for it, using tab-completion."""),
synopsis = "[CODE]" )
p.set_defaults(func=cmd_receive_text.receive_text)
class Options(usage.Options): p = subparsers.add_parser("send-file", description="Send a file",
synopsis = "\nUsage: wormhole <command>" usage="wormhole send-file FILENAME")
optParameters = [ p.add_argument("filename", metavar="FILENAME", help="The file to be sent")
["relay-url", None, public_relay.RENDEZVOUS_RELAY, p.set_defaults(func=cmd_send_file.send_file)
"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"),
]
def getUsage(self, **kwargs): p = subparsers.add_parser("receive-file", description="Receive a file",
t = usage.Options.getUsage(self, **kwargs) usage="wormhole receive-file [-o FILENAME] [CODE]")
return t + "\nPlease run 'wormhole <command> --help' for more details on each command.\n" 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): def run(args, stdout, stderr, executable=None):
"""This is invoked directly by the 'wormhole' entry-point script. It can """This is invoked directly by the 'wormhole' entry-point script. It can
also invoked by entry() below.""" also invoked by entry() below."""
config = Options()
args = parser.parse_args()
try: try:
config.parseOptions(args) #rc = command.func(args, stdout, stderr)
except usage.error, e: rc = args.func(args)
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)
return rc return rc
except ImportError, e: except ImportError, e:
print >>stderr, "--- ImportError ---" print >>stderr, "--- ImportError ---"
@ -100,3 +76,7 @@ def entry():
"""This is used by a setuptools entry_point. When invoked this way, """This is used by a setuptools entry_point. When invoked this way,
setuptools has already put the installed package on sys.path .""" setuptools has already put the installed package on sys.path ."""
return run(sys.argv[1:], sys.stdout, sys.stderr, executable=sys.argv[0]) return run(sys.argv[1:], sys.stdout, sys.stderr, executable=sys.argv[0])
if __name__ == "__main__":
args = parser.parse_args()
print args