This commit is a rewrite of larger parts of the console code. The motivation behind the rewrite is to cleanup the code and reduce code duplication to make it easier to understand and modify, and allow any form of code reuse. Most changes are to the interactive console, but also to how the different modes (BaseMode subclasses) are used and set up. * Address [#2097] - Improve match_torrent search match: Instead of matching e.g. torrent name with name.startswith(pattern) now check for asterix at beginning and end of pattern and search with startswith, endswith or __contains__ according to the pattern. Various smaller fixes: * Add errback handler to connection failed * Fix cmd line console mixing str and unicode input * Fix handling delete backwards with ALT+Backspace * Fix handling resizing of message popups * Fix docs generation warnings * Lets not stop the reactor on exception in basemode.. * Markup for translation arg help strings * Main functionality improvements: - Add support for indentation in formatting code in popup messages (like help) - Add filter sidebar - Add ComboBox and UI language selection - Add columnsview to allow rearranging the torrentlist columns and changing column widths. - Removed Columns pane in preferences as columnsview.py is sufficient - Remove torrent info panel (short cut 'i') as the torrent detail view is sufficient * Cleanups and code restructuring - Made BaseModes subclass of Component - Rewrite of most of basic window/panel to allow easier code reuse - Implemented better handling of multple popups by stacking popups. This makes it easier to return to previous popup when opening multiple popups. * Refactured console code: - modes/ for the different modes - Renamed Legacy mode to CmdLine - Renamed alltorrent.py to torrentlist.py and split the code into - torrentlist/columnsview.py - torrentlist/torrentsview.py - torrentlist/search_mode.py (minor mode) - torrentlist/queue_mode.py (minor mode) - cmdline/ for cmd line commands - utils/ for utility files - widgets/ for reusable GUI widgets - fields.py: Base widgets like TextInput, SelectInput, ComboInput - popup.py: Popup windows - inputpane.py: The BaseInputPane used to manage multiple base widgets in a panel - window.py: The BaseWindow used by all panels needing a curses screen - sidebar.py: The Sidebar panel - statusbars.py: The statusbars - Moved option parsing code from main.py to parser.py
197 lines
5.6 KiB
Python
197 lines
5.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
|
|
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
|
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
|
|
#
|
|
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
|
|
# the additional special exception to link portions of this program with the OpenSSL library.
|
|
# See LICENSE for more details.
|
|
#
|
|
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
import shlex
|
|
|
|
from twisted.internet import defer
|
|
|
|
from deluge.common import windows_check
|
|
from deluge.ui.client import client
|
|
from deluge.ui.console.parser import OptionParser, OptionParserError
|
|
from deluge.ui.console.utils.colors import strip_colors
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Commander(object):
|
|
|
|
def __init__(self, cmds, interactive=False):
|
|
self._commands = cmds
|
|
self.interactive = interactive
|
|
|
|
def write(self, line):
|
|
print(strip_colors(line))
|
|
|
|
def do_command(self, cmd_line):
|
|
"""Run a console command.
|
|
|
|
Args:
|
|
cmd_line (str): Console command.
|
|
|
|
Returns:
|
|
Deferred: A deferred that fires when the command has been executed.
|
|
|
|
"""
|
|
options = self.parse_command(cmd_line)
|
|
if options:
|
|
return self.exec_command(options)
|
|
return defer.succeed(None)
|
|
|
|
def exit(self, status=0, msg=None):
|
|
self._exit = True
|
|
if msg:
|
|
print(msg)
|
|
|
|
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:
|
|
return
|
|
cmd, _, line = cmd_line.partition(" ")
|
|
try:
|
|
parser = self._commands[cmd].create_parser()
|
|
except KeyError:
|
|
self.write("{!error!}Unknown command: %s" % cmd)
|
|
return
|
|
|
|
try:
|
|
args = [cmd] + self._commands[cmd].split(line)
|
|
except ValueError as ex:
|
|
self.write("{!error!}Error parsing command: %s" % ex)
|
|
return
|
|
|
|
# Do a little hack here to print 'command --help' properly
|
|
parser._print_help = parser.print_help
|
|
|
|
def print_help(f=None):
|
|
if self.interactive:
|
|
self.write(parser.format_help())
|
|
else:
|
|
parser._print_help(f)
|
|
parser.print_help = print_help
|
|
|
|
# Only these commands can be run when not connected to a daemon
|
|
not_connected_cmds = ["help", "connect", "quit"]
|
|
aliases = []
|
|
for c in not_connected_cmds:
|
|
aliases.extend(self._commands[c].aliases)
|
|
not_connected_cmds.extend(aliases)
|
|
|
|
if not client.connected() and cmd not in not_connected_cmds:
|
|
self.write("{!error!}Not connected to a daemon, please use the connect command first.")
|
|
return
|
|
|
|
try:
|
|
options = parser.parse_args(args=args)
|
|
options.command = cmd
|
|
except TypeError as ex:
|
|
self.write("{!error!}Error parsing options: %s" % ex)
|
|
import traceback
|
|
self.write("%s" % traceback.format_exc())
|
|
return
|
|
except OptionParserError as ex:
|
|
import traceback
|
|
log.warn("Error parsing command '%s': %s", args, ex)
|
|
self.write("{!error!} %s" % ex)
|
|
parser.print_help()
|
|
return
|
|
|
|
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: # pylint: disable=broad-except
|
|
self.write("{!error!} %s" % ex)
|
|
log.exception(ex)
|
|
import traceback
|
|
self.write("%s" % traceback.format_exc())
|
|
return defer.succeed(True)
|
|
else:
|
|
return ret
|
|
|
|
|
|
class BaseCommand(object):
|
|
|
|
usage = None
|
|
interactive_only = False
|
|
aliases = []
|
|
_name = "base"
|
|
epilog = ""
|
|
|
|
def complete(self, text, *args):
|
|
return []
|
|
|
|
def handle(self, options):
|
|
pass
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
@property
|
|
def name_with_alias(self):
|
|
return "/".join([self._name] + self.aliases)
|
|
|
|
@property
|
|
def description(self):
|
|
return self.__doc__
|
|
|
|
def split(self, text):
|
|
if windows_check():
|
|
text = text.replace("\\", "\\\\")
|
|
result = shlex.split(text)
|
|
for i, s in enumerate(result):
|
|
result[i] = s.replace(r"\ ", " ")
|
|
result = [s for s in result if s != ""]
|
|
return result
|
|
|
|
def create_parser(self):
|
|
opts = {"prog": self.name_with_alias, "description": self.__doc__, "epilog": self.epilog}
|
|
if self.usage:
|
|
opts["usage"] = self.usage
|
|
parser = OptionParser(**opts)
|
|
parser.add_argument(self.name, metavar="")
|
|
parser.base_parser = parser
|
|
self.add_arguments(parser)
|
|
return parser
|
|
|
|
def add_subparser(self, subparsers):
|
|
opts = {"prog": self.name_with_alias, "help": self.__doc__, "description": self.__doc__}
|
|
if self.usage:
|
|
opts["usage"] = self.usage
|
|
parser = subparsers.add_parser(self.name, **opts)
|
|
self.add_arguments(parser)
|
|
|
|
def add_arguments(self, parser):
|
|
pass
|