diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py index 27ed9df57519ff..20f03f14c09e77 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractlocalservercommand.py @@ -25,7 +25,7 @@ from optparse import make_option import threading -from webkitpy.tool.multicommandtool import Command +from webkitpy.tool.commands.command import Command class AbstractLocalServerCommand(Command): diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py index 72ba42bf3b2f3d..baab81facc8a42 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py @@ -30,7 +30,7 @@ from webkitpy.common.system.executive import ScriptError from webkitpy.tool.commands.stepsequence import StepSequence -from webkitpy.tool.multicommandtool import Command +from webkitpy.tool.commands.command import Command _log = logging.getLogger(__name__) diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command.py new file mode 100644 index 00000000000000..28ecb9262356a2 --- /dev/null +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command.py @@ -0,0 +1,153 @@ +# Copyright (c) 2016 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import optparse +import logging +import sys + +from webkitpy.tool.grammar import pluralize + +_log = logging.getLogger(__name__) + + +class Command(object): + # These class variables can be overridden in subclasses to set specific command behavior. + name = None + show_in_main_help = False + help_text = None + argument_names = None + long_help = None + + def __init__(self, options=None, requires_local_commits=False): + self.required_arguments = self._parse_required_arguments(self.argument_names) + self.options = options + self.requires_local_commits = requires_local_commits + self._tool = None + # option_parser can be overriden by the tool using set_option_parser + # This default parser will be used for standalone_help printing. + self.option_parser = HelpPrintingOptionParser(usage=optparse.SUPPRESS_USAGE, add_help_option=False, option_list=self.options) + + def _exit(self, code): + sys.exit(code) + + # This design is slightly awkward, but we need the + # the tool to be able to create and modify the option_parser + # before it knows what Command to run. + def set_option_parser(self, option_parser): + self.option_parser = option_parser + self._add_options_to_parser() + + def _add_options_to_parser(self): + options = self.options or [] + for option in options: + self.option_parser.add_option(option) + + # The tool calls bind_to_tool on each Command after adding it to its list. + def bind_to_tool(self, tool): + # Command instances can only be bound to one tool at a time. + if self._tool and tool != self._tool: + raise Exception("Command already bound to tool!") + self._tool = tool + + @staticmethod + def _parse_required_arguments(argument_names): + required_args = [] + if not argument_names: + return required_args + split_args = argument_names.split(" ") + for argument in split_args: + if argument[0] == '[': + # For now our parser is rather dumb. Do some minimal validation that + # we haven't confused it. + if argument[-1] != ']': + raise Exception("Failure to parse argument string %s. Argument %s is missing ending ]" % + (argument_names, argument)) + else: + required_args.append(argument) + return required_args + + def name_with_arguments(self): + usage_string = self.name + if self.options: + usage_string += " [options]" + if self.argument_names: + usage_string += " " + self.argument_names + return usage_string + + def parse_args(self, args): + return self.option_parser.parse_args(args) + + def check_arguments_and_execute(self, options, args, tool=None): + if len(args) < len(self.required_arguments): + _log.error("%s required, %s provided. Provided: %s Required: %s\nSee '%s help %s' for usage." % ( + pluralize("argument", len(self.required_arguments)), + pluralize("argument", len(args)), + "'%s'" % " ".join(args), + " ".join(self.required_arguments), + tool.name(), + self.name)) + return 1 + return self.execute(options, args, tool) or 0 + + def standalone_help(self): + help_text = self.name_with_arguments().ljust(len(self.name_with_arguments()) + 3) + self.help_text + "\n\n" + if self.long_help: + help_text += "%s\n\n" % self.long_help + help_text += self.option_parser.format_option_help(optparse.IndentedHelpFormatter()) + return help_text + + def execute(self, options, args, tool): + raise NotImplementedError("subclasses must implement") + + # main() exists so that Commands can be turned into stand-alone scripts. + # Other parts of the code will likely require modification to work stand-alone. + def main(self, args=sys.argv): + (options, args) = self.parse_args(args) + # Some commands might require a dummy tool + return self.check_arguments_and_execute(options, args) + + +class HelpPrintingOptionParser(optparse.OptionParser): + + def __init__(self, epilog_method=None, *args, **kwargs): + self.epilog_method = epilog_method + optparse.OptionParser.__init__(self, *args, **kwargs) + + def error(self, msg): + self.print_usage(sys.stderr) + error_message = "%s: error: %s\n" % (self.get_prog_name(), msg) + # This method is overriden to add this one line to the output: + error_message += "\nType \"%s --help\" to see usage.\n" % self.get_prog_name() + self.exit(1, error_message) + + # We override format_epilog to avoid the default formatting which would paragraph-wrap the epilog + # and also to allow us to compute the epilog lazily instead of in the constructor (allowing it to be context sensitive). + def format_epilog(self, epilog): + if self.epilog_method: + return "\n%s\n" % self.epilog_method() + return "" diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py new file mode 100644 index 00000000000000..1445210e0ca61b --- /dev/null +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/command_unittest.py @@ -0,0 +1,81 @@ +# Copyright (c) 2016 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import optparse +import unittest + +from webkitpy.common.system.outputcapture import OutputCapture +from webkitpy.tool.commands.command import Command + + +class TrivialCommand(Command): + name = "trivial" + help_text = "help text" + long_help = "trivial command long help text" + show_in_main_help = True + + def __init__(self, **kwargs): + super(TrivialCommand, self).__init__(**kwargs) + + def execute(self, options, args, tool): + pass + + +class DummyTool(object): + def name(self): + return "dummy-tool" + + +class CommandTest(unittest.TestCase): + + def test_name_with_arguments_with_two_args(self): + class TrivialCommandWithArgs(TrivialCommand): + argument_names = "ARG1 ARG2" + command = TrivialCommandWithArgs() + self.assertEqual(command.name_with_arguments(), "trivial ARG1 ARG2") + + def test_name_with_arguments_with_options(self): + command = TrivialCommand(options=[optparse.make_option("--my_option")]) + self.assertEqual(command.name_with_arguments(), "trivial [options]") + + def test_parse_required_arguments(self): + self.assertEqual(Command._parse_required_arguments("ARG1 ARG2"), ["ARG1", "ARG2"]) + self.assertEqual(Command._parse_required_arguments("[ARG1] [ARG2]"), []) + self.assertEqual(Command._parse_required_arguments("[ARG1] ARG2"), ["ARG2"]) + # Note: We might make our arg parsing smarter in the future and allow this type of arguments string. + self.assertRaises(Exception, Command._parse_required_arguments, "[ARG1 ARG2]") + + def test_required_arguments(self): + class TrivialCommandWithRequiredAndOptionalArgs(TrivialCommand): + argument_names = "ARG1 ARG2 [ARG3]" + two_required_arguments = TrivialCommandWithRequiredAndOptionalArgs() + expected_logs = ("2 arguments required, 1 argument provided. Provided: 'foo' Required: ARG1 ARG2\n" + "See 'dummy-tool help trivial' for usage.\n") + exit_code = OutputCapture().assert_outputs(self, two_required_arguments.check_arguments_and_execute, + [None, ["foo"], DummyTool()], expected_logs=expected_logs) + self.assertEqual(exit_code, 1) diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py index 3ea67761420848..e7623091615237 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py @@ -29,11 +29,9 @@ import traceback from webkitpy.common.config.irc import update_wait_seconds -from webkitpy.tool.bot.commitannouncer import CommitAnnouncer, CommitAnnouncerThread -from webkitpy.tool.multicommandtool import Command from webkitpy.tool.bot.commitannouncer import CommitAnnouncer from webkitpy.tool.bot.commitannouncer import CommitAnnouncerThread -from webkitpy.tool.multicommandtool import Command +from webkitpy.tool.commands.command import Command _log = logging.getLogger(__name__) announce_path = "third_party/WebKit" diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py index b884596c3523b0..fda7f9655c52f6 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py @@ -28,12 +28,12 @@ import logging -from webkitpy.tool.multicommandtool import Command +from webkitpy.common.net import sheriff_calendar from webkitpy.layout_tests.layout_package.bot_test_expectations import BotTestExpectationsFactory from webkitpy.layout_tests.models.test_expectations import TestExpectationParser from webkitpy.layout_tests.models.test_expectations import TestExpectations from webkitpy.layout_tests.models.test_expectations import TestExpectationsModel -from webkitpy.common.net import sheriff_calendar +from webkitpy.tool.commands.command import Command _log = logging.getLogger(__name__) diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/helpcommand.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/helpcommand.py new file mode 100644 index 00000000000000..6504e8b671f9fe --- /dev/null +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/helpcommand.py @@ -0,0 +1,80 @@ +# Copyright (c) 2016 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import optparse + +from webkitpy.tool.commands.command import Command + + +class HelpCommand(Command): + name = "help" + help_text = "Display information about this program or its subcommands" + argument_names = "[COMMAND]" + + def __init__(self): + options = [ + optparse.make_option("-a", "--all-commands", action="store_true", dest="show_all_commands", help="Print all available commands"), + ] + super(HelpCommand, self).__init__(options) + # A hack used to pass --all-commands to _help_epilog even though it's called by the OptionParser. + self.show_all_commands = False + + def _help_epilog(self): + # Only show commands which are relevant to this checkout's SCM system. Might this be confusing to some users? + if self.show_all_commands: + epilog = "All %prog commands:\n" + relevant_commands = self._tool.commands[:] + else: + epilog = "Common %prog commands:\n" + relevant_commands = filter(self._tool.should_show_in_main_help, self._tool.commands) + longest_name_length = max(map(lambda command: len(command.name), relevant_commands)) + relevant_commands.sort(lambda a, b: cmp(a.name, b.name)) + command_help_texts = map(lambda command: " %s %s\n" % ( + command.name.ljust(longest_name_length), command.help_text), relevant_commands) + epilog += "%s\n" % "".join(command_help_texts) + epilog += "See '%prog help --all-commands' to list all commands.\n" + epilog += "See '%prog help COMMAND' for more information on a specific command.\n" + return epilog.replace("%prog", self._tool.name()) # Use of %prog here mimics OptionParser.expand_prog_name(). + + # FIXME: This is a hack so that we don't show --all-commands as a global option: + def _remove_help_options(self): + for option in self.options: + self.option_parser.remove_option(option.get_opt_string()) + + def execute(self, options, args, tool): + if args: + command = self._tool.command_by_name(args[0]) + if command: + # TODO(qyearsley): Replace print with _log.info + print command.standalone_help() # pylint: disable=print-statement + return 0 + + self.show_all_commands = options.show_all_commands + self._remove_help_options() + self.option_parser.print_help() + return 0 diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py index 2e7e6732967bfa..7ac5e6a0a85630 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py @@ -35,7 +35,7 @@ from optparse import make_option from webkitpy.common.system.crashlogs import CrashLogs -from webkitpy.tool.multicommandtool import Command +from webkitpy.tool.commands.command import Command from webkitpy.layout_tests.models.test_expectations import TestExpectations from webkitpy.layout_tests.port.factory import platform_options diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py index 18d42a50fc704c..3398c3e7c04c6f 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py @@ -46,7 +46,7 @@ from webkitpy.layout_tests.models.test_expectations import TestExpectations, BASELINE_SUFFIX_LIST, SKIP from webkitpy.layout_tests.port import factory from webkitpy.layout_tests.builders import Builders -from webkitpy.tool.multicommandtool import Command +from webkitpy.tool.commands.command import Command _log = logging.getLogger(__name__) diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool.py index c600f96d148f7b..9525809bbea74b 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool.py @@ -31,192 +31,30 @@ # which are called with the following format: # tool-name [global options] command-name [command options] +import optparse import logging import sys -from optparse import OptionParser, IndentedHelpFormatter, SUPPRESS_USAGE, make_option - -from webkitpy.tool.grammar import pluralize +from webkitpy.tool.commands.command import Command +from webkitpy.tool.commands.command import HelpPrintingOptionParser +from webkitpy.tool.commands.helpcommand import HelpCommand _log = logging.getLogger(__name__) class TryAgain(Exception): + # This is an exception that can be raised in the execute method of a Command, + # to make it retry from the start with the same arguments. + # TODO(qyearsley): Remove this, as it appears unused. pass -# TODO(qyearsley): This class could be moved to a separate file, e.g. tool/commands/command.py. -class Command(object): - # These class variables can be overridden in subclasses to set specific command behavior. - name = None - show_in_main_help = False - help_text = None - argument_names = None - long_help = None - - def __init__(self, options=None, requires_local_commits=False): - self.required_arguments = self._parse_required_arguments(self.argument_names) - self.options = options - self.requires_local_commits = requires_local_commits - self._tool = None - # option_parser can be overriden by the tool using set_option_parser - # This default parser will be used for standalone_help printing. - self.option_parser = HelpPrintingOptionParser(usage=SUPPRESS_USAGE, add_help_option=False, option_list=self.options) - - def _exit(self, code): - sys.exit(code) - - # This design is slightly awkward, but we need the - # the tool to be able to create and modify the option_parser - # before it knows what Command to run. - def set_option_parser(self, option_parser): - self.option_parser = option_parser - self._add_options_to_parser() - - def _add_options_to_parser(self): - options = self.options or [] - for option in options: - self.option_parser.add_option(option) - - # The tool calls bind_to_tool on each Command after adding it to its list. - def bind_to_tool(self, tool): - # Command instances can only be bound to one tool at a time. - if self._tool and tool != self._tool: - raise Exception("Command already bound to tool!") - self._tool = tool - - @staticmethod - def _parse_required_arguments(argument_names): - required_args = [] - if not argument_names: - return required_args - split_args = argument_names.split(" ") - for argument in split_args: - if argument[0] == '[': - # For now our parser is rather dumb. Do some minimal validation that - # we haven't confused it. - if argument[-1] != ']': - raise Exception("Failure to parse argument string %s. Argument %s is missing ending ]" % - (argument_names, argument)) - else: - required_args.append(argument) - return required_args - - def name_with_arguments(self): - usage_string = self.name - if self.options: - usage_string += " [options]" - if self.argument_names: - usage_string += " " + self.argument_names - return usage_string - - def parse_args(self, args): - return self.option_parser.parse_args(args) - - def check_arguments_and_execute(self, options, args, tool=None): - if len(args) < len(self.required_arguments): - _log.error("%s required, %s provided. Provided: %s Required: %s\nSee '%s help %s' for usage." % ( - pluralize("argument", len(self.required_arguments)), - pluralize("argument", len(args)), - "'%s'" % " ".join(args), - " ".join(self.required_arguments), - tool.name(), - self.name)) - return 1 - return self.execute(options, args, tool) or 0 - - def standalone_help(self): - help_text = self.name_with_arguments().ljust(len(self.name_with_arguments()) + 3) + self.help_text + "\n\n" - if self.long_help: - help_text += "%s\n\n" % self.long_help - help_text += self.option_parser.format_option_help(IndentedHelpFormatter()) - return help_text - - def execute(self, options, args, tool): - raise NotImplementedError, "subclasses must implement" - - # main() exists so that Commands can be turned into stand-alone scripts. - # Other parts of the code will likely require modification to work stand-alone. - def main(self, args=sys.argv): - (options, args) = self.parse_args(args) - # Some commands might require a dummy tool - return self.check_arguments_and_execute(options, args) - - -class HelpPrintingOptionParser(OptionParser): - - def __init__(self, epilog_method=None, *args, **kwargs): - self.epilog_method = epilog_method - OptionParser.__init__(self, *args, **kwargs) - - def error(self, msg): - self.print_usage(sys.stderr) - error_message = "%s: error: %s\n" % (self.get_prog_name(), msg) - # This method is overriden to add this one line to the output: - error_message += "\nType \"%s --help\" to see usage.\n" % self.get_prog_name() - self.exit(1, error_message) - - # We override format_epilog to avoid the default formatting which would paragraph-wrap the epilog - # and also to allow us to compute the epilog lazily instead of in the constructor (allowing it to be context sensitive). - def format_epilog(self, epilog): - if self.epilog_method: - return "\n%s\n" % self.epilog_method() - return "" - - -class HelpCommand(Command): - name = "help" - help_text = "Display information about this program or its subcommands" - argument_names = "[COMMAND]" - - def __init__(self): - options = [ - make_option("-a", "--all-commands", action="store_true", dest="show_all_commands", help="Print all available commands"), - ] - super(HelpCommand, self).__init__(options) - # A hack used to pass --all-commands to _help_epilog even though it's called by the OptionParser. - self.show_all_commands = False - - def _help_epilog(self): - # Only show commands which are relevant to this checkout's SCM system. Might this be confusing to some users? - if self.show_all_commands: - epilog = "All %prog commands:\n" - relevant_commands = self._tool.commands[:] - else: - epilog = "Common %prog commands:\n" - relevant_commands = filter(self._tool.should_show_in_main_help, self._tool.commands) - longest_name_length = max(map(lambda command: len(command.name), relevant_commands)) - relevant_commands.sort(lambda a, b: cmp(a.name, b.name)) - command_help_texts = map(lambda command: " %s %s\n" % ( - command.name.ljust(longest_name_length), command.help_text), relevant_commands) - epilog += "%s\n" % "".join(command_help_texts) - epilog += "See '%prog help --all-commands' to list all commands.\n" - epilog += "See '%prog help COMMAND' for more information on a specific command.\n" - return epilog.replace("%prog", self._tool.name()) # Use of %prog here mimics OptionParser.expand_prog_name(). - - # FIXME: This is a hack so that we don't show --all-commands as a global option: - def _remove_help_options(self): - for option in self.options: - self.option_parser.remove_option(option.get_opt_string()) - - def execute(self, options, args, tool): - if args: - command = self._tool.command_by_name(args[0]) - if command: - print command.standalone_help() - return 0 - - self.show_all_commands = options.show_all_commands - self._remove_help_options() - self.option_parser.print_help() - return 0 - - class MultiCommandTool(object): + global_options = None def __init__(self, name=None, commands=None): - self._name = name or OptionParser(prog=name).get_prog_name() # OptionParser has nice logic for fetching the name. + self._name = name or optparse.OptionParser(prog=name).get_prog_name() # OptionParser has nice logic for fetching the name. # Allow the unit tests to disable command auto-discovery. self.commands = commands or [cls() for cls in self._find_all_commands() if cls.name] self.help_command = self.command_by_name(HelpCommand.name) diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py index 3d11f944df5ad2..8988b3388c4b8a 100644 --- a/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py +++ b/third_party/WebKit/Tools/Scripts/webkitpy/tool/multicommandtool_unittest.py @@ -31,7 +31,8 @@ from optparse import make_option from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.multicommandtool import MultiCommandTool, Command, TryAgain +from webkitpy.tool.multicommandtool import MultiCommandTool, TryAgain +from webkitpy.tool.commands import Command class TrivialCommand(Command): @@ -67,36 +68,6 @@ def execute(self, options, args, tool): raise TryAgain() -class CommandTest(unittest.TestCase): - - def test_name_with_arguments_with_two_args(self): - class TrivialCommandWithArgs(TrivialCommand): - argument_names = "ARG1 ARG2" - command = TrivialCommandWithArgs() - self.assertEqual(command.name_with_arguments(), "trivial ARG1 ARG2") - - def test_name_with_arguments_with_options(self): - command = TrivialCommand(options=[make_option("--my_option")]) - self.assertEqual(command.name_with_arguments(), "trivial [options]") - - def test_parse_required_arguments(self): - self.assertEqual(Command._parse_required_arguments("ARG1 ARG2"), ["ARG1", "ARG2"]) - self.assertEqual(Command._parse_required_arguments("[ARG1] [ARG2]"), []) - self.assertEqual(Command._parse_required_arguments("[ARG1] ARG2"), ["ARG2"]) - # Note: We might make our arg parsing smarter in the future and allow this type of arguments string. - self.assertRaises(Exception, Command._parse_required_arguments, "[ARG1 ARG2]") - - def test_required_arguments(self): - class TrivialCommandWithRequiredAndOptionalArgs(TrivialCommand): - argument_names = "ARG1 ARG2 [ARG3]" - two_required_arguments = TrivialCommandWithRequiredAndOptionalArgs() - expected_logs = ("2 arguments required, 1 argument provided. Provided: 'foo' Required: ARG1 ARG2\n" - "See 'trivial-tool help trivial' for usage.\n") - exit_code = OutputCapture().assert_outputs(self, two_required_arguments.check_arguments_and_execute, - [None, ["foo"], TrivialTool()], expected_logs=expected_logs) - self.assertEqual(exit_code, 1) - - class TrivialTool(MultiCommandTool): def __init__(self, commands=None):