[UI] [#1973] Improve passing extra args to UIs
Current solution for passing arguments to UI when invoking deluge entry script is to select an UI with the --ui option and supply quoted arguments with the --args option. This patch cleans this up by removing both options and change to using subparsers for valid UIs. All command line options are now parsed directly by the child UI which is chosen by a positional argument, i.e. the UI name. The help text now also shows the current default UI.
This commit is contained in:
parent
d6fec88932
commit
d689ad72e8
|
@ -1,16 +1,78 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import exceptions
|
||||
import StringIO
|
||||
import sys
|
||||
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
import deluge.component as component
|
||||
import deluge.ui.console
|
||||
import deluge.ui.web.server
|
||||
from deluge.ui import ui_entry
|
||||
from deluge.ui.web.server import DelugeWeb
|
||||
|
||||
from . import common
|
||||
from .basetest import BaseTestCase
|
||||
|
||||
sys_stdout = sys.stdout
|
||||
|
||||
|
||||
class TestStdout(object):
|
||||
|
||||
def __init__(self, fd):
|
||||
self.out = StringIO.StringIO()
|
||||
self.fd = fd
|
||||
for a in ["encoding"]:
|
||||
setattr(self, a, getattr(sys_stdout, a))
|
||||
|
||||
def write(self, *data, **kwargs):
|
||||
print(data, file=self.out)
|
||||
|
||||
def flush(self):
|
||||
self.out.flush()
|
||||
|
||||
|
||||
class DelugeEntryTestCase(BaseTestCase):
|
||||
|
||||
def set_up(self):
|
||||
common.set_tmp_config_dir()
|
||||
return component.start()
|
||||
|
||||
def tear_down(self):
|
||||
return component.shutdown()
|
||||
|
||||
def test_deluge_help(self):
|
||||
self.patch(sys, "argv", ["./deluge", "-h"])
|
||||
config = deluge.configmanager.ConfigManager("ui.conf", ui_entry.DEFAULT_PREFS)
|
||||
config.config["default_ui"] = "console"
|
||||
config.save()
|
||||
|
||||
fd = TestStdout(sys.stdout)
|
||||
self.patch(argparse._sys, "stdout", fd)
|
||||
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
self.assertRaises(exceptions.SystemExit, ui_entry.start_ui)
|
||||
self.assertTrue("usage: deluge" in fd.out.getvalue())
|
||||
self.assertTrue("UI Options:" in fd.out.getvalue())
|
||||
self.assertTrue("* console" in fd.out.getvalue())
|
||||
|
||||
def test_start_default(self):
|
||||
self.patch(sys, "argv", ["./deluge"])
|
||||
config = deluge.configmanager.ConfigManager("ui.conf", ui_entry.DEFAULT_PREFS)
|
||||
config.config["default_ui"] = "console"
|
||||
config.save()
|
||||
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
# Just test that no exception is raised
|
||||
ui_entry.start_ui()
|
||||
|
||||
|
||||
@pytest.mark.gtkui
|
||||
class UIEntryTestCase(BaseTestCase):
|
||||
class GtkUIEntryTestCase(BaseTestCase):
|
||||
|
||||
def set_up(self):
|
||||
common.set_tmp_config_dir()
|
||||
|
@ -20,30 +82,108 @@ class UIEntryTestCase(BaseTestCase):
|
|||
return component.shutdown()
|
||||
|
||||
def test_start_gtkui(self):
|
||||
import deluge.ui.gtkui.gtkui
|
||||
import sys
|
||||
self.patch(sys, "argv", ['./deluge', "--ui", 'gtk'])
|
||||
self.patch(sys, "argv", ["./deluge", "gtk"])
|
||||
|
||||
with mock.patch.object(deluge.ui.gtkui.gtkui.GtkUI, 'start', autospec=True):
|
||||
from deluge.ui.gtkui import gtkui
|
||||
with mock.patch.object(gtkui.GtkUI, "start", autospec=True):
|
||||
ui_entry.start_ui()
|
||||
|
||||
def test_start_console(self):
|
||||
import sys
|
||||
self.patch(sys, "argv", ['./deluge', "--ui", 'console'])
|
||||
with mock.patch('deluge.ui.console.main.ConsoleUI'):
|
||||
ui_entry.start_ui()
|
||||
|
||||
class WebUIEntryTestCase(BaseTestCase):
|
||||
|
||||
def set_up(self):
|
||||
common.set_tmp_config_dir()
|
||||
return component.start()
|
||||
|
||||
def tear_down(self):
|
||||
return component.shutdown()
|
||||
|
||||
def test_start_webserver(self):
|
||||
import sys
|
||||
from deluge.ui.web.server import DelugeWeb
|
||||
|
||||
self.patch(sys, "argv", ['./deluge', "--ui", 'web', '--do-not-daemonize'])
|
||||
self.patch(sys, "argv", ["./deluge", "web", "--do-not-daemonize"])
|
||||
|
||||
class DelugeWebMock(DelugeWeb):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["daemon"] = False
|
||||
DelugeWeb.__init__(self, *args, **kwargs)
|
||||
|
||||
import deluge.ui.web.server
|
||||
self.patch(deluge.ui.web.server, 'DelugeWeb', DelugeWebMock)
|
||||
self.patch(deluge.ui.web.server, "DelugeWeb", DelugeWebMock)
|
||||
ui_entry.start_ui()
|
||||
|
||||
|
||||
class ConsoleUIBaseTestCase(object):
|
||||
|
||||
def __init__(self):
|
||||
self.var = dict()
|
||||
|
||||
def set_up(self):
|
||||
common.set_tmp_config_dir()
|
||||
return component.start()
|
||||
|
||||
def tear_down(self):
|
||||
return component.shutdown()
|
||||
|
||||
def test_start_console(self):
|
||||
self.patch(sys, "argv", self.var["sys_arg_cmd"])
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
self.var["start_cmd"]()
|
||||
|
||||
def test_console_help(self):
|
||||
self.patch(sys, "argv", self.var["sys_arg_cmd"] + ["-h"])
|
||||
fd = TestStdout(sys.stdout)
|
||||
self.patch(argparse._sys, "stdout", fd)
|
||||
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
self.assertRaises(exceptions.SystemExit, self.var["start_cmd"])
|
||||
std_output = fd.out.getvalue()
|
||||
self.assertTrue(("usage: %s" % self.var["cmd_name"]) in std_output) # Check command name
|
||||
self.assertTrue("Common Options:" in std_output)
|
||||
self.assertTrue("Console Options:" in std_output)
|
||||
self.assertTrue(r"Console commands:\n The following console commands are available:" in std_output)
|
||||
self.assertTrue("The following console commands are available:" in std_output)
|
||||
|
||||
def test_console_command_info(self):
|
||||
self.patch(sys, "argv", self.var["sys_arg_cmd"] + ["info"])
|
||||
fd = TestStdout(sys.stdout)
|
||||
self.patch(argparse._sys, "stdout", fd)
|
||||
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
self.var["start_cmd"]()
|
||||
|
||||
def test_console_command_info_help(self):
|
||||
self.patch(sys, "argv", self.var["sys_arg_cmd"] + ["info", "-h"])
|
||||
fd = TestStdout(sys.stdout)
|
||||
self.patch(argparse._sys, "stdout", fd)
|
||||
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
self.assertRaises(exceptions.SystemExit, self.var["start_cmd"])
|
||||
std_output = fd.out.getvalue()
|
||||
self.assertTrue("usage: info" in std_output)
|
||||
self.assertTrue("Show information about the torrents" in std_output)
|
||||
|
||||
def test_console_unrecognized_arguments(self):
|
||||
self.patch(sys, "argv", ["./deluge", "--ui", "console"]) # --ui is not longer supported
|
||||
fd = TestStdout(sys.stdout)
|
||||
self.patch(argparse._sys, "stderr", fd)
|
||||
with mock.patch("deluge.ui.console.main.ConsoleUI"):
|
||||
self.assertRaises(exceptions.SystemExit, self.var["start_cmd"])
|
||||
self.assertTrue("unrecognized arguments: --ui" in fd.out.getvalue())
|
||||
|
||||
|
||||
class ConsoleUIEntryTestCase(BaseTestCase, ConsoleUIBaseTestCase):
|
||||
|
||||
def __init__(self, testname):
|
||||
BaseTestCase.__init__(self, testname)
|
||||
ConsoleUIBaseTestCase.__init__(self)
|
||||
self.var["cmd_name"] = "deluge console"
|
||||
self.var["start_cmd"] = ui_entry.start_ui
|
||||
self.var["sys_arg_cmd"] = ["./deluge", "console"]
|
||||
|
||||
|
||||
class ConsoleUIScriptTestCase(BaseTestCase, ConsoleUIBaseTestCase):
|
||||
|
||||
def __init__(self, testname):
|
||||
BaseTestCase.__init__(self, testname)
|
||||
ConsoleUIBaseTestCase.__init__(self)
|
||||
self.var["cmd_name"] = "deluge-console"
|
||||
self.var["start_cmd"] = deluge.ui.console.start
|
||||
self.var["sys_arg_cmd"] = ["./deluge-console"]
|
||||
|
|
|
@ -20,6 +20,69 @@ from deluge.configmanager import get_config_dir, set_config_dir
|
|||
from deluge.log import setup_logger
|
||||
|
||||
|
||||
def find_subcommand(self, args=None):
|
||||
"""Find if a subcommand has been supplied.
|
||||
|
||||
Args:
|
||||
args (list, optional): The argument list handed to parse_args().
|
||||
|
||||
Returns:
|
||||
int: Index of the subcommand or '-1' if none found.
|
||||
|
||||
"""
|
||||
subcommand_found = -1
|
||||
test_args = args if args else sys.argv[1:]
|
||||
|
||||
for x in self._subparsers._actions:
|
||||
if not isinstance(x, argparse._SubParsersAction):
|
||||
continue
|
||||
for sp_name in x._name_parser_map.keys():
|
||||
if sp_name in test_args:
|
||||
subcommand_found = test_args.index(sp_name) + 1
|
||||
|
||||
return subcommand_found
|
||||
|
||||
|
||||
def set_default_subparser(self, name, abort_opts=None):
|
||||
"""Sets the default argparse subparser.
|
||||
|
||||
Args:
|
||||
name (str): The name of the default subparser.
|
||||
abort_opts (list): The arguments to test for in case no subcommand is found.
|
||||
If any of the values are found, the default subparser will
|
||||
not be inserted into sys.argv.
|
||||
|
||||
Returns:
|
||||
list: The arguments found in sys.argv if no subcommand found, else None
|
||||
|
||||
"""
|
||||
found_abort_opts = []
|
||||
abort_opts = [] if abort_opts is None else abort_opts
|
||||
test_args = sys.argv[1:]
|
||||
subparser_found = self.find_subcommand(args=test_args)
|
||||
|
||||
for i, arg in enumerate(test_args):
|
||||
if subparser_found == i:
|
||||
break
|
||||
if arg in abort_opts:
|
||||
found_abort_opts.append(arg)
|
||||
|
||||
if subparser_found == -1:
|
||||
if found_abort_opts:
|
||||
# Found one or more of arguments in abort_opts
|
||||
return found_abort_opts
|
||||
|
||||
# insert default in first position, this implies no
|
||||
# global options without a sub_parsers specified
|
||||
sys.argv.insert(1, name)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
argparse.ArgumentParser.find_subcommand = find_subcommand
|
||||
argparse.ArgumentParser.set_default_subparser = set_default_subparser
|
||||
|
||||
|
||||
def get_version():
|
||||
version_str = "%s\n" % (common.get_version())
|
||||
try:
|
||||
|
@ -33,8 +96,7 @@ def get_version():
|
|||
|
||||
|
||||
class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
|
||||
"""Help message formatter which retains formatting of all help text.
|
||||
"""
|
||||
"""Help message formatter which retains formatting of all help text."""
|
||||
|
||||
def _split_lines(self, text, width):
|
||||
"""
|
||||
|
@ -80,9 +142,9 @@ class HelpAction(argparse._HelpAction):
|
|||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
if hasattr(parser, "subparser"):
|
||||
subparser = getattr(parser, "subparser")
|
||||
# If -h on a subparser is given, the subparser will exit after help message
|
||||
subparser.parse_args()
|
||||
parser.print_help()
|
||||
subparser.print_help()
|
||||
else:
|
||||
parser.print_help()
|
||||
parser.exit()
|
||||
|
||||
|
||||
|
@ -91,13 +153,16 @@ class BaseArgParser(argparse.ArgumentParser):
|
|||
def __init__(self, *args, **kwargs):
|
||||
if "formatter_class" not in kwargs:
|
||||
kwargs["formatter_class"] = lambda prog: DelugeTextHelpFormatter(prog, max_help_position=33, width=90)
|
||||
super(BaseArgParser, self).__init__(*args, add_help=False, **kwargs)
|
||||
kwargs["add_help"] = kwargs.get("add_help", False)
|
||||
common_help = kwargs.pop("common_help", True)
|
||||
super(BaseArgParser, self).__init__(*args, **kwargs)
|
||||
|
||||
self.common_setup = False
|
||||
self.process_arg_group = False
|
||||
self.group = self.add_argument_group(_("Common Options"))
|
||||
self.group.add_argument("-h", "--help", action=HelpAction,
|
||||
help=_("Print this help message"))
|
||||
if common_help:
|
||||
self.group.add_argument("-h", "--help", action=HelpAction,
|
||||
help=_("Print this help message"))
|
||||
self.group.add_argument("-V", "--version", action="version", version="%(prog)s " + get_version(),
|
||||
help=_("Print version information"))
|
||||
self.group.add_argument("-v", action="version", version="%(prog)s " + get_version(),
|
||||
|
@ -117,9 +182,22 @@ class BaseArgParser(argparse.ArgumentParser):
|
|||
help=_("Profile %(prog)s with cProfile. Outputs to stdout "
|
||||
"unless a filename is specified"))
|
||||
|
||||
def parse_args(self, *args):
|
||||
options, remaining = super(BaseArgParser, self).parse_known_args(*args)
|
||||
def parse_args(self, args=None):
|
||||
"""Parse UI arguments and handle common and process group options.
|
||||
|
||||
Unknown arguments return an error and resulting usage text.
|
||||
"""
|
||||
options = super(BaseArgParser, self).parse_args(args=args)
|
||||
return self._parse_args(options)
|
||||
|
||||
def parse_known_ui_args(self, args=None):
|
||||
"""Parse UI arguments and handle common and process group options without error.
|
||||
"""
|
||||
options, remaining = super(BaseArgParser, self).parse_known_args(args=args)
|
||||
options.remaining = remaining
|
||||
return self._parse_args(options)
|
||||
|
||||
def _parse_args(self, options):
|
||||
|
||||
if not self.common_setup:
|
||||
self.common_setup = True
|
||||
|
|
|
@ -32,10 +32,28 @@ class Commander(object):
|
|||
print(strip_colors(line))
|
||||
|
||||
def do_command(self, cmd_line):
|
||||
"""
|
||||
Processes a command.
|
||||
"""Run a console command
|
||||
|
||||
:param cmd: str, the command string
|
||||
Args:
|
||||
cmd_line (str): Console command
|
||||
|
||||
Returns:
|
||||
Deferred: A deferred that fires when command has been executed
|
||||
|
||||
"""
|
||||
options = self.parse_command(cmd_line)
|
||||
if options:
|
||||
return self.exec_command(options)
|
||||
return defer.succeed(None)
|
||||
|
||||
def parse_command(self, cmd_line):
|
||||
"""Parse a console command and process with argparse
|
||||
|
||||
Args:
|
||||
cmd_line (str): Console command
|
||||
|
||||
Returns:
|
||||
argparse.Namespace: The parsed command
|
||||
|
||||
"""
|
||||
if not cmd_line:
|
||||
|
@ -76,6 +94,7 @@ class Commander(object):
|
|||
|
||||
try:
|
||||
options = parser.parse_args(args=args)
|
||||
options.command = cmd
|
||||
except TypeError as ex:
|
||||
self.write("{!error!}Error parsing options: %s" % ex)
|
||||
import traceback
|
||||
|
@ -86,14 +105,28 @@ class Commander(object):
|
|||
parser.print_help()
|
||||
return
|
||||
|
||||
if not getattr(parser, "_exit", False):
|
||||
try:
|
||||
ret = self._commands[cmd].handle(options)
|
||||
except Exception as ex:
|
||||
self.write("{!error!} %s" % ex)
|
||||
log.exception(ex)
|
||||
import traceback
|
||||
self.write("%s" % traceback.format_exc())
|
||||
return defer.succeed(True)
|
||||
else:
|
||||
return ret
|
||||
if getattr(parser, "_exit", False):
|
||||
return
|
||||
return options
|
||||
|
||||
def exec_command(self, options, *args):
|
||||
"""
|
||||
Execute a console command.
|
||||
|
||||
Args:
|
||||
options (argparse.Namespace): The command to execute
|
||||
|
||||
Returns:
|
||||
Deferred: A deferred that fires when command has been executed
|
||||
|
||||
"""
|
||||
try:
|
||||
ret = self._commands[options.command].handle(options)
|
||||
except Exception as ex:
|
||||
self.write("{!error!} %s" % ex)
|
||||
log.exception(ex)
|
||||
import traceback
|
||||
self.write("%s" % traceback.format_exc())
|
||||
return defer.succeed(True)
|
||||
else:
|
||||
return ret
|
||||
|
|
|
@ -92,7 +92,7 @@ class Command(BaseCommand):
|
|||
epilog = """
|
||||
You can give the first few characters of a torrent-id to identify the torrent.
|
||||
|
||||
Tab Completion (info *pattern*<tab>):\n
|
||||
Tab Completion in interactive mode (info *pattern*<tab>):\n
|
||||
| First press of <tab> will output up to 15 matches;
|
||||
| hitting <tab> a second time, will print 15 more matches;
|
||||
| and a third press will print all remaining matches.
|
||||
|
|
|
@ -53,7 +53,7 @@ class Console(UI):
|
|||
cmd_description = """Console or command-line user interface"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Console, self).__init__("console", *args, description="Test", **kwargs)
|
||||
super(Console, self).__init__(*args, **kwargs)
|
||||
|
||||
group = self.parser.add_argument_group(_("Console Options"), "These daemon connect options will be "
|
||||
"used for commands, or if console ui autoconnect is enabled.")
|
||||
|
@ -65,20 +65,25 @@ class Console(UI):
|
|||
# To properly print help message for the console commands ( e.g. deluge-console info -h),
|
||||
# we add a subparser for each command which will trigger the help/usage when given
|
||||
from deluge.ui.console.main import ConsoleCommandParser # import here because (see top)
|
||||
self.console_parser = ConsoleCommandParser(parents=[self.parser], add_help=False,
|
||||
self.console_parser = ConsoleCommandParser(parents=[self.parser], add_help=False, prog=self.parser.prog,
|
||||
description="Starts the Deluge console interface",
|
||||
formatter_class=lambda prog:
|
||||
DelugeTextHelpFormatter(prog, max_help_position=33, width=90))
|
||||
self.parser.subparser = self.console_parser
|
||||
subparsers = self.console_parser.add_subparsers(title="Console commands", help="Description", dest="commands",
|
||||
self.console_parser.base_parser = self.parser
|
||||
subparsers = self.console_parser.add_subparsers(title="Console commands", help="Description", dest="command",
|
||||
description="The following console commands are available:",
|
||||
metavar="command")
|
||||
self.console_cmds = load_commands(os.path.join(UI_PATH, "commands"))
|
||||
for c in sorted(self.console_cmds):
|
||||
self.console_cmds[c].add_subparser(subparsers)
|
||||
|
||||
def start(self, args=None):
|
||||
super(Console, self).start(args)
|
||||
def start(self):
|
||||
i = self.console_parser.find_subcommand(args=self.ui_args)
|
||||
self.console_parser.subcommand = False
|
||||
self.parser.subcommand = False if i == -1 else True
|
||||
|
||||
super(Console, self).start(self.console_parser)
|
||||
from deluge.ui.console.main import ConsoleUI # import here because (see top)
|
||||
|
||||
def run(options):
|
||||
|
|
|
@ -24,6 +24,7 @@ from deluge.error import DelugeError
|
|||
from deluge.ui.client import client
|
||||
from deluge.ui.console import colors
|
||||
from deluge.ui.console.colors import ConsoleColorFormatter
|
||||
from deluge.ui.console.commander import Commander
|
||||
from deluge.ui.console.eventlog import EventLog
|
||||
from deluge.ui.console.statusbars import StatusBars
|
||||
from deluge.ui.coreconfig import CoreConfig
|
||||
|
@ -49,6 +50,58 @@ class ConsoleCommandParser(argparse.ArgumentParser):
|
|||
self.epilog = epilog
|
||||
return help_str
|
||||
|
||||
def _split_args(self, args):
|
||||
command_options = []
|
||||
for a in args:
|
||||
if not a:
|
||||
continue
|
||||
if ";" in a:
|
||||
cmd_lines = [arg.strip() for arg in a.split(";")]
|
||||
elif " " in a:
|
||||
cmd_lines = [a]
|
||||
else:
|
||||
continue
|
||||
|
||||
for cmd_line in cmd_lines:
|
||||
cmds = shlex.split(cmd_line)
|
||||
cmd_options = super(ConsoleCommandParser, self).parse_args(args=cmds)
|
||||
cmd_options.command = cmds[0]
|
||||
command_options.append(cmd_options)
|
||||
|
||||
return command_options
|
||||
|
||||
def parse_args(self, args=None):
|
||||
multi_command = self._split_args(args)
|
||||
# If multiple commands were passed to console
|
||||
if multi_command:
|
||||
# With multiple commands, normal parsing will fail, so only parse
|
||||
# known arguments using the base parser, and then set
|
||||
# options.parsed_cmds to the already parsed commands
|
||||
options, remaining = self.base_parser.parse_known_args(args=args)
|
||||
options.parsed_cmds = multi_command
|
||||
else:
|
||||
subcommand = False
|
||||
if hasattr(self.base_parser, "subcommand"):
|
||||
subcommand = getattr(self.base_parser, "subcommand")
|
||||
if not subcommand:
|
||||
# We must use parse_known_args to handle case when no subcommand
|
||||
# is provided, because argparse does not support parsing without
|
||||
# a subcommand
|
||||
options, remaining = self.base_parser.parse_known_args(args=args)
|
||||
# If any options remain it means they do not exist. Reparse with
|
||||
# parse_args to trigger help message
|
||||
if remaining:
|
||||
options = self.base_parser.parse_args(args=args)
|
||||
options.parsed_cmds = []
|
||||
else:
|
||||
options = super(ConsoleCommandParser, self).parse_args(args=args)
|
||||
options.parsed_cmds = [options]
|
||||
|
||||
if not hasattr(options, "remaining"):
|
||||
options.remaining = []
|
||||
|
||||
return options
|
||||
|
||||
|
||||
class OptionParser(ConsoleCommandParser):
|
||||
|
||||
|
@ -130,6 +183,7 @@ class BaseCommand(object):
|
|||
opts["usage"] = self.usage
|
||||
parser = OptionParser(**opts)
|
||||
parser.add_argument(self.name, metavar="")
|
||||
parser.base_parser = parser
|
||||
self.add_arguments(parser)
|
||||
return parser
|
||||
|
||||
|
@ -167,8 +221,7 @@ class ConsoleUI(component.Component):
|
|||
# Set the interactive flag to indicate where we should print the output
|
||||
self.interactive = True
|
||||
self._commands = cmds
|
||||
|
||||
if options.remaining:
|
||||
if options.parsed_cmds:
|
||||
self.interactive = False
|
||||
if not cmds:
|
||||
print("Sorry, couldn't find any commands")
|
||||
|
@ -194,14 +247,6 @@ Please use commands from the command line, e.g.:\n
|
|||
reactor.run()
|
||||
|
||||
def exec_args(self, options):
|
||||
args = options.remaining
|
||||
commands = []
|
||||
if args:
|
||||
cmd = " ".join([arg for arg in args])
|
||||
# Multiple commands split by ";"
|
||||
commands += [arg.strip() for arg in cmd.split(";")]
|
||||
|
||||
from deluge.ui.console.commander import Commander
|
||||
commander = Commander(self._commands)
|
||||
|
||||
def on_connect(result):
|
||||
|
@ -209,11 +254,14 @@ Please use commands from the command line, e.g.:\n
|
|||
def on_started(result):
|
||||
def do_command(result, cmd):
|
||||
return commander.do_command(cmd)
|
||||
|
||||
def exec_command(result, cmd):
|
||||
return commander.exec_command(cmd)
|
||||
d = defer.succeed(None)
|
||||
for command in commands:
|
||||
if command in ("quit", "exit"):
|
||||
for command in options.parsed_cmds:
|
||||
if command.command in ("quit", "exit"):
|
||||
break
|
||||
d.addCallback(do_command, command)
|
||||
d.addCallback(exec_command, command)
|
||||
d.addCallback(do_command, "quit")
|
||||
|
||||
# We need to wait for the rpcs in start() to finish before processing
|
||||
|
@ -230,17 +278,9 @@ Please use commands from the command line, e.g.:\n
|
|||
commander.do_command("quit")
|
||||
|
||||
d = None
|
||||
if not self.interactive:
|
||||
if commands[0] is not None:
|
||||
if commands[0].startswith("connect"):
|
||||
d = commander.do_command(commands.pop(0))
|
||||
if d is None:
|
||||
# Error parsing command
|
||||
sys.exit(0)
|
||||
elif "help" in commands:
|
||||
commander.do_command("help")
|
||||
sys.exit(0)
|
||||
if not d:
|
||||
if not self.interactive and options.parsed_cmds[0].command == "connect":
|
||||
d = commander.do_command(options.parsed_cmds.pop(0))
|
||||
else:
|
||||
log.info("connect: host=%s, port=%s, username=%s, password=%s",
|
||||
options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass)
|
||||
d = client.connect(options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass)
|
||||
|
|
|
@ -27,8 +27,8 @@ class Gtk(UI):
|
|||
help=_("Add one or more torrent files, torrent URLs or magnet URIs"
|
||||
" to a currently running Deluge GTK instance"))
|
||||
|
||||
def start(self, args=None):
|
||||
super(Gtk, self).start(args)
|
||||
def start(self):
|
||||
super(Gtk, self).start()
|
||||
from deluge.ui.gtkui.gtkui import GtkUI
|
||||
import deluge.common
|
||||
|
||||
|
|
|
@ -25,16 +25,20 @@ except ImportError:
|
|||
|
||||
|
||||
class UI(object):
|
||||
"""
|
||||
Base class for UI implementations.
|
||||
|
||||
cmd_description = """Insert command description"""
|
||||
"""
|
||||
cmd_description = """Override with command description"""
|
||||
|
||||
def __init__(self, name="gtk", parser=None, **kwargs):
|
||||
def __init__(self, name="gtk", **kwargs):
|
||||
self.__name = name
|
||||
self.ui_args = kwargs.pop("ui_args", None)
|
||||
lang.setup_translations(setup_pygtk=(name == "gtk"))
|
||||
self.__parser = parser if parser else BaseArgParser(**kwargs)
|
||||
self.__parser = BaseArgParser(**kwargs)
|
||||
|
||||
def parse_args(self, args=None):
|
||||
options = self.parser.parse_args(args)
|
||||
def parse_args(self, parser, args=None):
|
||||
options = parser.parse_args(args)
|
||||
if not hasattr(options, "remaining"):
|
||||
options.remaining = []
|
||||
return options
|
||||
|
@ -51,12 +55,11 @@ class UI(object):
|
|||
def options(self):
|
||||
return self.__options
|
||||
|
||||
def start(self, extra_args=None):
|
||||
def start(self, parser=None):
|
||||
args = deluge.common.unicode_argv()[1:]
|
||||
if extra_args:
|
||||
args.extend(extra_args)
|
||||
|
||||
self.__options = self.parse_args(args)
|
||||
if parser is None:
|
||||
parser = self.parser
|
||||
self.__options = self.parse_args(parser, args)
|
||||
|
||||
setproctitle("deluge-%s" % self.__name)
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
|
||||
"""Main starting point for Deluge"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pkg_resources
|
||||
|
@ -32,27 +34,29 @@ def start_ui():
|
|||
"""Entry point for ui script"""
|
||||
lang.setup_translations()
|
||||
|
||||
# Setup the argument parser
|
||||
parser = BaseArgParser()
|
||||
group = parser.add_argument_group(_("UI Options"))
|
||||
|
||||
# Get the registered UI entry points
|
||||
ui_entrypoints = dict([(entrypoint.name, entrypoint.load())
|
||||
for entrypoint in pkg_resources.iter_entry_points("deluge.ui")])
|
||||
ui_titles = sorted(ui_entrypoints.keys())
|
||||
|
||||
cmd_help = [_("The UI that you wish to launch. The UI choices are:")]
|
||||
cmd_help.extend(["%s -- %s" % (k, getattr(v, "cmd_description", "")) for k, v in ui_entrypoints.iteritems()])
|
||||
def add_ui_options_group(_parser):
|
||||
"""Function to enable reuse of UI Options group"""
|
||||
group = _parser.add_argument_group(_("UI Options"))
|
||||
group.add_argument("-s", "--set-default-ui", dest="default_ui", choices=ui_titles,
|
||||
help=_("Set the default UI to be run, when no UI is specified"))
|
||||
return _parser
|
||||
|
||||
group.add_argument("-u", "--ui", action="store",
|
||||
choices=ui_entrypoints.keys(), help="\n * ".join(cmd_help))
|
||||
group.add_argument("-a", "--args", action="store",
|
||||
help=_('Arguments to pass to the UI. Multiple args must be quoted, e.g. -a "--option args"'))
|
||||
group.add_argument("-s", "--set-default-ui", dest="default_ui", choices=ui_entrypoints.keys(),
|
||||
help=_("Sets the default UI to be run when no UI is specified"), action="store")
|
||||
# Setup parser with Common Options and add UI Options group.
|
||||
parser = add_ui_options_group(BaseArgParser())
|
||||
|
||||
options = parser.parse_args(deluge.common.unicode_argv()[1:])
|
||||
ambiguous_args = ["-h", "--help", "-v", "-V", "--version"]
|
||||
# Parse arguments without help/version as this is handled later.
|
||||
args = [a for a in sys.argv if a not in ambiguous_args]
|
||||
options = parser.parse_known_ui_args(args)
|
||||
|
||||
config = deluge.configmanager.ConfigManager("ui.conf", DEFAULT_PREFS)
|
||||
log = logging.getLogger(__name__)
|
||||
log.info("Deluge ui %s", deluge.common.get_version())
|
||||
|
||||
if options.default_ui:
|
||||
config["default_ui"] = options.default_ui
|
||||
|
@ -60,37 +64,54 @@ def start_ui():
|
|||
log.info("The default UI has been changed to %s", options.default_ui)
|
||||
sys.exit(0)
|
||||
|
||||
log.info("Deluge ui %s", deluge.common.get_version())
|
||||
log.debug("options: %s", options)
|
||||
|
||||
selected_ui = options.ui if options.ui else config["default_ui"]
|
||||
|
||||
config.save()
|
||||
default_ui = config["default_ui"]
|
||||
config.save() # Save in case config didn't already exist.
|
||||
del config
|
||||
|
||||
# reconstruct arguments to hand off to child client
|
||||
client_args = []
|
||||
if options.args:
|
||||
import shlex
|
||||
client_args.extend(shlex.split(options.args))
|
||||
# We have parsed and got the config dir needed to get the default UI
|
||||
# Now create a parser for choosing the UI. We reuse the ui option group for
|
||||
# parsing to succeed and the text displayed to user, but result is not used.
|
||||
parser = add_ui_options_group(BaseArgParser(common_help=True))
|
||||
|
||||
# Create subparser for each registered UI. Empty title is used to remove unwanted positional text.
|
||||
subparsers = parser.add_subparsers(dest="selected_ui", metavar="{%s} [UI args]" % ",".join(ui_titles), title=None,
|
||||
help=_("Alternative UI to launch, with optional ui args \n (default UI: *)"))
|
||||
for ui in ui_titles:
|
||||
parser_ui = subparsers.add_parser(ui, common_help=False,
|
||||
help=getattr(ui_entrypoints[ui], "cmd_description", ""))
|
||||
parser_ui.add_argument("ui_args", nargs=argparse.REMAINDER)
|
||||
# If the UI is set as default, indicate this in help by prefixing with a star.
|
||||
subactions = subparsers._get_subactions()
|
||||
prefix = "*" if ui == default_ui else " "
|
||||
subactions[-1].dest = "%s %s" % (prefix, ui)
|
||||
|
||||
# Insert a default UI subcommand unless one of the ambiguous_args are specified
|
||||
parser.set_default_subparser(default_ui, abort_opts=ambiguous_args)
|
||||
|
||||
# Only parse known arguments to leave the UIs to show a help message if parsing fails.
|
||||
options, remaining = parser.parse_known_args()
|
||||
selected_ui = options.selected_ui
|
||||
ui_args = remaining + options.ui_args
|
||||
|
||||
# Remove the UI argument before launching the UI.
|
||||
sys.argv.remove(selected_ui)
|
||||
|
||||
try:
|
||||
ui = ui_entrypoints[selected_ui](parser=parser)
|
||||
ui = ui_entrypoints[selected_ui](prog="%s %s" % (os.path.basename(sys.argv[0]), selected_ui), ui_args=ui_args)
|
||||
except KeyError as ex:
|
||||
log.error("Unable to find the requested UI: '%s'. Please select a different UI with the '-u' option"
|
||||
" or alternatively use the '-s' option to select a different default UI.", selected_ui)
|
||||
log.error("Unable to find chosen UI: '%s'. Please choose a different UI "
|
||||
"or use '--set-default-ui' to change default UI.", selected_ui)
|
||||
except ImportError as ex:
|
||||
import traceback
|
||||
error_type, error_value, tb = sys.exc_info()
|
||||
stack = traceback.extract_tb(tb)
|
||||
last_frame = stack[-1]
|
||||
if last_frame[0] == __file__:
|
||||
log.error("Unable to find the requested UI: %s. Please select a different UI with the '-u' "
|
||||
"option or alternatively use the '-s' option to select a different default UI.", selected_ui)
|
||||
log.error("Unable to find chosen UI: '%s'. Please choose a different UI "
|
||||
"or use '--set-default-ui' to change default UI.", selected_ui)
|
||||
else:
|
||||
log.exception(ex)
|
||||
log.error("There was an error whilst launching the request UI: %s", selected_ui)
|
||||
log.error("Look at the traceback above for more information.")
|
||||
log.error("Encountered an error launching the request UI: %s", selected_ui)
|
||||
sys.exit(1)
|
||||
else:
|
||||
ui.start(client_args)
|
||||
ui.start()
|
||||
|
|
|
@ -42,8 +42,8 @@ class Web(UI):
|
|||
def server(self):
|
||||
return self.__server
|
||||
|
||||
def start(self, args=None):
|
||||
super(Web, self).start(args)
|
||||
def start(self):
|
||||
super(Web, self).start()
|
||||
|
||||
from deluge.ui.web import server
|
||||
self.__server = server.DelugeWeb(options=self.options)
|
||||
|
|
Loading…
Reference in New Issue