From patchwork Thu Mar 22 17:28:13 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Zygmunt Krynicki X-Patchwork-Id: 7415 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 3816823E13 for ; Thu, 22 Mar 2012 17:28:19 +0000 (UTC) Received: from mail-iy0-f180.google.com (mail-iy0-f180.google.com [209.85.210.180]) by fiordland.canonical.com (Postfix) with ESMTP id A8B2EA181FA for ; Thu, 22 Mar 2012 17:28:18 +0000 (UTC) Received: by iage36 with SMTP id e36so4467077iag.11 for ; Thu, 22 Mar 2012 10:28:17 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf :content-type:mime-version:x-launchpad-project:x-launchpad-branch :x-launchpad-message-rationale:x-launchpad-branch-revision-number :x-launchpad-notification-type:to:from:subject:message-id:date :reply-to:sender:errors-to:precedence:x-generated-by :x-launchpad-hash:x-gm-message-state; bh=az0j5avRxfCsFewdK3YOnVYiMhApwyYXSkjC/h+DkHg=; b=n1+v8pHmaxro2vPhSasy9dKqspLJU+Z1PHvbrsy6CAVSZpOoFhxvVRA4c3cMprZx4q yoDob+kCRoAuf4qQW9j0cpr0N/4ysogcNYBgb5Cq/xKynQv729gqHHx5lz5uD986ESfA 3hWDNmRwodovVgQwphMR5dFFs2nsr54uoL/VoKcnhL7Texqlt1AnRzLtsPl5C9zf4GMp k1eNFF+qAXIxNpJB4SwBg56D066vYM5r2rTKtYboO3gwFdFPWdTQpntJrsos20+mSs4F kbXE583mrU88AvMnTZHbLIjaJDLGwGGvjk1ioEauzxmtbPM/jQa5pIR8G3ANAjest08w 9OiQ== Received: by 10.50.155.229 with SMTP id vz5mr2649748igb.12.1332437297582; Thu, 22 Mar 2012 10:28:17 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.231.203.79 with SMTP id fh15csp1970ibb; Thu, 22 Mar 2012 10:28:15 -0700 (PDT) Received: by 10.180.79.135 with SMTP id j7mr6932552wix.19.1332437294653; Thu, 22 Mar 2012 10:28:14 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id y20si5914300wec.88.2012.03.22.10.28.13 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 22 Mar 2012 10:28:14 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1SAlo5-0008OO-EW for ; Thu, 22 Mar 2012 17:28:13 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 61BF7E00BF for ; Thu, 22 Mar 2012 17:28:13 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-tool X-Launchpad-Branch: ~linaro-validation/lava-tool/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 170 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-tool/trunk] Rev 170: Merge less-lava-tool branch Message-Id: <20120322172813.4834.52293.launchpad@ackee.canonical.com> Date: Thu, 22 Mar 2012 17:28:13 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="14981"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: bb85f27e6a4132b7a90e12050bdb7fb740b86826 X-Gm-Message-State: ALoCoQnvfCkTGaklceEW98YkFyc3PL2d3PlT46iAqK9KTS0RkNHaZQt/7NdZ/ls0ui1A3E63a66D Merge authors: Zygmunt Krynicki (zkrynicki) Related merge proposals: https://code.launchpad.net/~zkrynicki/lava-tool/less-lava-tool/+merge/97287 proposed by: Zygmunt Krynicki (zkrynicki) review: Approve - Michael Hudson-Doyle (mwhudson) ------------------------------------------------------------ revno: 170 [merge] committer: Zygmunt Krynicki branch nick: release timestamp: Thu 2012-03-22 18:25:31 +0100 message: Merge less-lava-tool branch This branch transitions part of the code in lava-tool to lava.tool while maintaining compatibility with anyone depending on the old interface. removed: lava_tool/commands/misc.py added: lava/ lava/__init__.py lava/tool/ lava/tool/__init__.py lava/tool/command.py lava/tool/commands/ lava/tool/commands/__init__.py lava/tool/commands/help.py lava/tool/dispatcher.py lava/tool/errors.py lava/tool/main.py modified: lava_tool/__init__.py lava_tool/commands/__init__.py lava_tool/dispatcher.py lava_tool/interface.py setup.py --- lp:lava-tool https://code.launchpad.net/~linaro-validation/lava-tool/trunk You are subscribed to branch lp:lava-tool. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-tool/trunk/+edit-subscription === added directory 'lava' === added file 'lava/__init__.py' --- lava/__init__.py 1970-01-01 00:00:00 +0000 +++ lava/__init__.py 2012-03-13 15:34:36 +0000 @@ -0,0 +1,3 @@ +__import__('pkg_resources').declare_namespace(__name__) +# DO NOT ADD ANYTHING TO THIS FILE! +# IT MUST STAY AS IS (empty apart from the two lines above) === added directory 'lava/tool' === added file 'lava/tool/__init__.py' --- lava/tool/__init__.py 1970-01-01 00:00:00 +0000 +++ lava/tool/__init__.py 2012-03-13 15:40:30 +0000 @@ -0,0 +1,27 @@ +# Copyright (C) 2010, 2011 Linaro Limited +# +# Author: Zygmunt Krynicki +# Author: Michael Hudson-Doyle +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +lava.tool +========= + +Generic code for command line utilities for LAVA +""" + +__version__ = (0, 4, 0, "dev", 0) === added file 'lava/tool/command.py' --- lava/tool/command.py 1970-01-01 00:00:00 +0000 +++ lava/tool/command.py 2012-03-13 16:30:24 +0000 @@ -0,0 +1,157 @@ +# Copyright (C) 2010, 2011 Linaro Limited +# +# Author: Zygmunt Krynicki +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +Interface for all lava-tool commands +""" + +import inspect + + +class Command(object): + """ + Base class for all command line tool sub-commands. + """ + + def __init__(self, parser, args): + """ + Prepare instance for executing commands. + + This method is called immediately after all arguments are parsed and + results are available. This gives subclasses a chance to configure + themselves. The provided parser is an instance of + argparse.ArgumentParser but it may not be the top-level parser (it will + be a parser specific for this command) + + The default implementation stores both arguments as instance + attributes. + """ + self.parser = parser + self.args = args + + def invoke(self): + """ + Invoke command action. + """ + raise NotImplementedError() + + def reparse_arguments(self, parser, raw_args): + """ + Re-parse raw arguments into normal arguments + + Parser is the same as in register_arguments (a sub-parser) The true, + topmost parser is in self.parser. + + This method is only needed for specific commands that need to peek at + the arguments before being able to truly redefine the parser and + re-parse the raw arguments again. + """ + raise NotImplementedError() + + @classmethod + def get_name(cls): + """ + Return the name of this command. + + The default implementation strips any leading underscores and replaces + all other underscores with dashes. + """ + return cls.__name__.lstrip("_").replace("_", "-") + + @classmethod + def get_help(cls): + """ + Return the help message of this command + """ + doc = inspect.getdoc(cls) + if doc is not None and " " in doc: + doc = doc[:doc.index(" ")].rstrip() + return doc + + @classmethod + def get_epilog(cls): + """ + Return the epilog of the help message + """ + doc = inspect.getdoc(cls) + if doc is not None and " " in doc: + doc = doc[doc.index(" ") + 1:].lstrip() + else: + doc = None + return doc + + @classmethod + def register_arguments(cls, parser): + """ + Register arguments if required. + + Subclasses can override this to add any arguments that will be + exposed to the command line interface. + """ + pass + + +class SubCommand(Command): + """ + Base class for all command sub-command hubs. + + This class is needed when one wants to get a custom level of command + options that can be freely extended, just like the top-level lava-tool + command. + + For example, a SubCommand 'actions' will load additional commands from a + the 'lava.actions' namespace. For the end user it will be available as:: + + $ lava-tool foo actions xxx + + Where xxx is one of the Commands that is declared to live in the namespace + provided by 'foo actions'. + """ + + namespace = None + + @classmethod + def get_namespace(cls): + """ + Return the pkg-resources entry point namespace name from which + sub-commands will be loaded. + """ + return cls.namespace + + @classmethod + def register_subcommands(cls, parser): + """ + Register sub commands. + + This method is called around the same time as register_arguments() + would be called for the plain command classes. It loads commands from + the entry point namespace returned by get_namespace() and registeres + them with a BaseDispatcher class. The parsers used by that dispatcher + are linked to the calling dispatcher parser so the new commands enrich + the top-level parser tree. + + In addition, the provided parser stores a dispatcher instance in its + defaults. This is useful when one wants to access it later. To a final + command instance it shall be available as self.args.dispatcher. + """ + from lava.tool.dispatcher import BaseDispatcher + dispatcher = BaseDispatcher(parser, name=cls.get_name()) + namespace = cls.get_namespace() + if namespace is not None: + dispatcher.import_commands(namespace) + parser.set_defaults(dispatcher=dispatcher) === added directory 'lava/tool/commands' === added file 'lava/tool/commands/__init__.py' --- lava/tool/commands/__init__.py 1970-01-01 00:00:00 +0000 +++ lava/tool/commands/__init__.py 2012-03-13 18:17:45 +0000 @@ -0,0 +1,83 @@ +# Copyright (C) 2010 Linaro Limited +# +# Author: Zygmunt Krynicki +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +Package with command line commands +""" + +import argparse +import re + + +class ExperimentalNoticeAction(argparse.Action): + """ + Argparse action that implements the --experimental-notice + """ + + message = """ + Some lc-tool sub-commands are marked as EXPERIMENTAL. Those commands are + not guaranteed to work identically, or have identical interface between + subsequent lc-tool releases. + + We do that to make it possible to provide good user interface and + server-side API when working on new features. Once a feature is stabilized + the UI will be frozen and all subsequent changes will retain backwards + compatibility. + """ + message = message.lstrip() + message = re.sub(re.compile("[ \t]+", re.M), " ", message) + message = re.sub(re.compile("^ ", re.M), "", message) + + def __init__(self, + option_strings, dest, default=None, required=False, + help=None): + super(ExperimentalNoticeAction, self).__init__( + option_strings=option_strings, dest=dest, default=default, nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.exit(message=self.message) + + +class ExperimentalCommandMixIn(object): + """ + Experimental command. + + Prints a warning message on each call to invoke() + """ + + def invoke(self): + self.print_experimental_notice() + return super(ExperimentalCommandMixIn, self).invoke() + + @classmethod + def register_arguments(cls, parser): + retval = super(ExperimentalCommandMixIn, + cls).register_arguments(parser) + parser.register("action", "experimental_notice", + ExperimentalNoticeAction) + group = parser.add_argument_group("experimental commands") + group.add_argument("--experimental-notice", + action="experimental_notice", + default=argparse.SUPPRESS, + help="Explain the nature of experimental commands") + return retval + + def print_experimental_notice(self): + print ("EXPERIMENTAL - SUBJECT TO CHANGE" + " (See --experimental-notice for more info)") === added file 'lava/tool/commands/help.py' --- lava/tool/commands/help.py 1970-01-01 00:00:00 +0000 +++ lava/tool/commands/help.py 2012-03-13 18:21:16 +0000 @@ -0,0 +1,35 @@ +# Copyright (C) 2010 Linaro Limited +# +# Author: Zygmunt Krynicki +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +lava.tool.commands.help +======================= + +Implementation of `lava help` +""" + +from lava.tool.command import Command + + +class help(Command): + """ + Show a summary of all available commands + """ + + def invoke(self): + self.parser.print_help() === added file 'lava/tool/dispatcher.py' --- lava/tool/dispatcher.py 1970-01-01 00:00:00 +0000 +++ lava/tool/dispatcher.py 2012-03-13 18:22:16 +0000 @@ -0,0 +1,128 @@ +# Copyright (C) 2010, 2011, 2012 Linaro Limited +# +# Author: Zygmunt Krynicki +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +Module with LavaDispatcher - the command dispatcher +""" + +import argparse +import logging +import pkg_resources +import sys + +from lava.tool.errors import CommandError + + +class Dispatcher(object): + """ + Class implementing command line interface for launch control + """ + + description = None + epilog = None + + def __init__(self, parser=None, name=None): + self.parser = parser or self.construct_parser() + self.subparsers = self.parser.add_subparsers( + title="Sub-command to invoke") + self.name = name + + def __repr__(self): + return "%r(name=%r)" % (self.__class__.__name__, self.name) + + @classmethod + def construct_parser(cls): + """ + Construct a parser for this dispatcher. + + This is only used if the parser is not provided by the parent + dispatcher instance. + """ + parser_args = dict(add_help=True) + if cls.description is not None: + parser_args['description'] = cls.description + if cls.epilog is not None: + parser_args['epilog'] = cls.epilog + return argparse.ArgumentParser(**parser_args) + + def import_commands(self, entrypoint_name): + """ + Import commands from given entry point namespace + """ + logging.debug("Loading commands in entry point %r", entrypoint_name) + for entrypoint in pkg_resources.iter_entry_points(entrypoint_name): + self.add_command_cls(entrypoint.load()) + + def add_command_cls(self, command_cls): + """ + Add a new command class to this dispatcher. + + The command must be a subclass of Command or SubCommand. + """ + logging.debug("Loading command class %r", command_cls) + # Create a sub-parser where the command/sub-command can register + # things. + sub_parser = self.subparsers.add_parser( + command_cls.get_name(), + help=command_cls.get_help(), + epilog=command_cls.get_epilog()) + from lava.tool.command import SubCommand + if issubclass(command_cls, SubCommand): + # Handle SubCommand somewhat different. Instead of calling + # register_arguments we call register_subcommands + command_cls.register_subcommands(sub_parser) + else: + # Handle plain commands easily by recording their commands in the + # dedicated sub-parser we've crated for them. + command_cls.register_arguments(sub_parser) + # In addition, since we don't want to require all sub-classes of + # Command to super-call register_arguments (everyone would forget + # this anyway) we manually register the command class for that + # sub-parser so that dispatch() can look it up later. + sub_parser.set_defaults( + command_cls=command_cls, + parser=sub_parser) + + def dispatch(self, raw_args=None): + """ + Dispatch a command with the specified arguments. + + If arguments are left out they are looked up in sys.argv automatically + """ + # First parse whatever input arguments we've got + args = self.parser.parse_args(raw_args) + # Then look up the command class and construct it with the parser it + # belongs to and the parsed arguments. + command = args.command_cls(args.parser, args) + try: + # Give the command a chance to re-parse command line arguments + command.reparse_arguments(args.parser, raw_args) + except NotImplementedError: + pass + try: + return command.invoke() + except CommandError as ex: + print >> sys.stderr, "ERROR: %s" % (ex,) + return 1 + + @classmethod + def run(cls): + """ + Dispatch commandsd and exit + """ + raise SystemExit(cls().dispatch()) === added file 'lava/tool/errors.py' --- lava/tool/errors.py 1970-01-01 00:00:00 +0000 +++ lava/tool/errors.py 2012-03-13 16:17:50 +0000 @@ -0,0 +1,31 @@ +# Copyright (C) 2010, 2011, 2012 Linaro Limited +# +# Author: Zygmunt Krynicki +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +lava.tool.errors +================ + +Error classes for LAVA Tool. +""" + +class CommandError(Exception): + """ + Raise this from a Command's invoke() method to display an error nicely. + + lava-tool will exit with a status of 1 if this is raised. + """ === added file 'lava/tool/main.py' --- lava/tool/main.py 1970-01-01 00:00:00 +0000 +++ lava/tool/main.py 2012-03-13 18:22:16 +0000 @@ -0,0 +1,41 @@ +# Copyright (C) 2010, 2011 Linaro Limited +# +# Author: Zygmunt Krynicki +# +# This file is part of lava-tool. +# +# lava-tool is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation +# +# lava-tool is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with lava-tool. If not, see . + +""" +lava.tool.main +============== + +Implementation of the `lava` shell command. +""" + +from lava.tool.dispatcher import Dispatcher + + +class LavaDispatcher(Dispatcher): + """ + Dispatcher implementing the `lava` shell command + + This dispatcher imports plugins from `lava.commands` pkg_resources + namespace. Additional plugins can be registered as either + :class:`lava.command.Command` or :class:`lava.command.SubCommand` + sub-classes. + """ + + def __init__(self): + super(LavaDispatcher, self).__init__() + self.import_commands('lava.commands') === modified file 'lava_tool/__init__.py' --- lava_tool/__init__.py 2011-12-17 00:58:38 +0000 +++ lava_tool/__init__.py 2012-03-13 15:40:30 +0000 @@ -18,7 +18,7 @@ # along with lava-tool. If not, see . """ -Lava Tool package +Deprecated lava_tool package """ -__version__ = (0, 4, 0, "dev", 0) +from lava.tool import __version__ === modified file 'lava_tool/commands/__init__.py' --- lava_tool/commands/__init__.py 2011-06-17 13:33:45 +0000 +++ lava_tool/commands/__init__.py 2012-03-13 18:17:45 +0000 @@ -20,64 +20,5 @@ Package with command line commands """ -import argparse -import re - - -class ExperimentalNoticeAction(argparse.Action): - """ - Argparse action that implements the --experimental-notice - """ - - message = """ - Some lc-tool sub-commands are marked as EXPERIMENTAL. Those commands are - not guaranteed to work identically, or have identical interface between - subsequent lc-tool releases. - - We do that to make it possible to provide good user interface and - server-side API when working on new features. Once a feature is stabilized - the UI will be frozen and all subsequent changes will retain backwards - compatibility. - """ - message = message.lstrip() - message = re.sub(re.compile("[ \t]+", re.M), " ", message) - message = re.sub(re.compile("^ ", re.M), "", message) - - def __init__(self, - option_strings, dest, default=None, required=False, - help=None): - super(ExperimentalNoticeAction, self).__init__( - option_strings=option_strings, dest=dest, default=default, nargs=0, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - parser.exit(message=self.message) - - -class ExperimentalCommandMixIn(object): - """ - Experimental command. - - Prints a warning message on each call to invoke() - """ - - def invoke(self): - self.print_experimental_notice() - return super(ExperimentalCommandMixIn, self).invoke() - - @classmethod - def register_arguments(cls, parser): - retval = super(ExperimentalCommandMixIn, - cls).register_arguments(parser) - parser.register("action", "experimental_notice", - ExperimentalNoticeAction) - group = parser.add_argument_group("experimental commands") - group.add_argument("--experimental-notice", - action="experimental_notice", - default=argparse.SUPPRESS, - help="Explain the nature of experimental commands") - return retval - - def print_experimental_notice(self): - print ("EXPERIMENTAL - SUBJECT TO CHANGE" - " (See --experimental-notice for more info)") + +from lava.tool.commands import ExperimentalNoticeAction, ExperimentalCommandMixIn === removed file 'lava_tool/commands/misc.py' --- lava_tool/commands/misc.py 2011-05-04 11:57:26 +0000 +++ lava_tool/commands/misc.py 1970-01-01 00:00:00 +0000 @@ -1,31 +0,0 @@ -# Copyright (C) 2010 Linaro Limited -# -# Author: Zygmunt Krynicki -# -# This file is part of lava-tool. -# -# lava-tool is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License version 3 -# as published by the Free Software Foundation -# -# lava-tool is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with lava-tool. If not, see . - -""" -Module with miscellaneous commands (such as help and version) -""" - -from lava_tool.interface import Command - - -class help(Command): - """ - Show a summary of all available commands - """ - def invoke(self): - self.parser.print_help() === modified file 'lava_tool/dispatcher.py' --- lava_tool/dispatcher.py 2011-12-13 16:19:52 +0000 +++ lava_tool/dispatcher.py 2012-03-13 18:22:16 +0000 @@ -20,10 +20,12 @@ Module with LavaDispatcher - the command dispatcher """ -from lava_tool.interface import LavaCommandError, BaseDispatcher - - -class LavaDispatcher(BaseDispatcher): +from lava.tool.dispatcher import Dispatcher +from lava.tool.main import LavaDispatcher as LavaNonLegacyDispatcher +from lava_tool.interface import LavaCommandError + + +class LavaDispatcher(Dispatcher): """ Class implementing command line interface for launch control """ @@ -39,26 +41,9 @@ self.import_commands("%s.commands" % prefix) -class LavaNonLegacyDispatcher(BaseDispatcher): - """ - A dispatcher that wants to load only top-level commands, - not everything-and-the-kitchen-sink into one flat namespace - - Available as `lava` command from shell - """ - - def __init__(self): - super(LavaNonLegacyDispatcher, self).__init__() - self.import_commands('lava.commands') - - def run_with_dispatcher_class(cls): - raise SystemExit(cls().dispatch()) + raise cls.run() def main(): - run_with_dispatcher_class(LavaDispatcher) - - -def main_nonlegacy(): - run_with_dispatcher_class(LavaNonLegacyDispatcher) + LavaDispatcher.run() === modified file 'lava_tool/interface.py' --- lava_tool/interface.py 2011-12-13 16:20:05 +0000 +++ lava_tool/interface.py 2012-03-13 16:30:24 +0000 @@ -20,239 +20,5 @@ Interface for all lava-tool commands """ -import argparse -import inspect -import logging -import pkg_resources -import sys - - -class LavaCommandError(Exception): - """ - Raise this from a Command's invoke() method to display an error nicely. - - lava-tool will exit with a status of 1 if this is raised. - """ - - -class Command(object): - """ - Base class for all command line tool sub-commands. - """ - - def __init__(self, parser, args): - """ - Prepare instance for executing commands. - - This method is called immediately after all arguments are parsed and - results are available. This gives subclasses a chance to configure - themselves. The provided parser is an instance of - argparse.ArgumentParser but it may not be the top-level parser (it will - be a parser specific for this command) - - The default implementation stores both arguments as instance attributes. - """ - self.parser = parser - self.args = args - - def invoke(self): - """ - Invoke command action. - """ - raise NotImplementedError() - - def reparse_arguments(self, parser, raw_args): - """ - Re-parse raw arguments into normal arguments - - Parser is the same as in register_arguments (a sub-parser) The true, - topmost parser is in self.parser. - - This method is only needed for specific commands that need to peek at - the arguments before being able to truly redefine the parser and - re-parse the raw arguments again. - """ - raise NotImplementedError() - - @classmethod - def get_name(cls): - """ - Return the name of this command. - - The default implementation strips any leading underscores and replaces - all other underscores with dashes. - """ - return cls.__name__.lstrip("_").replace("_", "-") - - @classmethod - def get_help(cls): - """ - Return the help message of this command - """ - doc = inspect.getdoc(cls) - if doc is not None and " " in doc: - doc = doc[:doc.index(" ")].rstrip() - return doc - - @classmethod - def get_epilog(cls): - """ - Return the epilog of the help message - """ - doc = inspect.getdoc(cls) - if doc is not None and " " in doc: - doc = doc[doc.index(" ") + 1:].lstrip() - else: - doc = None - return doc - - @classmethod - def register_arguments(cls, parser): - """ - Register arguments if required. - - Subclasses can override this to add any arguments that will be - exposed to the command line interface. - """ - pass - - -class SubCommand(Command): - """ - Base class for all command sub-command hubs. - - This class is needed when one wants to get a custom level of command - options that can be freely extended, just like the top-level lava-tool - command. - - For example, a SubCommand 'actions' will load additional commands from a - the 'lava.actions' namespace. For the end user it will be available as:: - - $ lava-tool foo actions xxx - - Where xxx is one of the Commands that is declared to live in the namespace - provided by 'foo actions'. - """ - - namespace = None - - @classmethod - def get_namespace(cls): - """ - Return the pkg-resources entry point namespace name from which - sub-commands will be loaded. - """ - return cls.namespace - - @classmethod - def register_subcommands(cls, parser): - """ - Register sub commands. - - This method is called around the same time as register_arguments() - would be called for the plain command classes. It loads commands from - the entry point namespace returned by get_namespace() and registeres - them with a BaseDispatcher class. The parsers used by that dispatcher - are linked to the calling dispatcher parser so the new commands enrich - the top-level parser tree. - - In addition, the provided parser stores a dispatcher instance in its - defaults. This is useful when one wants to access it later. To a final - command instance it shall be available as self.args.dispatcher. - """ - dispatcher = BaseDispatcher(parser, name=cls.get_name()) - namespace = cls.get_namespace() - if namespace is not None: - dispatcher.import_commands(namespace) - parser.set_defaults(dispatcher=dispatcher) - - -class BaseDispatcher(object): - """ - Class implementing command line interface for launch control - """ - - description = None - epilog = None - - def __init__(self, parser=None, name=None): - self.parser = parser or self.construct_parser() - self.subparsers = self.parser.add_subparsers( - title="Sub-command to invoke") - self.name = name - - def __repr__(self): - return "%r(name=%r)" % (self.__class__.__name__, self.name) - - @classmethod - def construct_parser(cls): - """ - Construct a parser for this dispatcher. - - This is only used if the parser is not provided by the parent - dispatcher instance. - """ - parser_args = dict(add_help=True) - if cls.description is not None: - parser_args['description'] = cls.description - if cls.epilog is not None: - parser_args['epilog'] = cls.epilog - return argparse.ArgumentParser(**parser_args) - - def import_commands(self, entrypoint_name): - """ - Import commands from given entry point namespace - """ - logging.debug("Loading commands in entry point %r", entrypoint_name) - for entrypoint in pkg_resources.iter_entry_points(entrypoint_name): - self.add_command_cls(entrypoint.load()) - - def add_command_cls(self, command_cls): - """ - Add a new command class to this dispatcher. - - The command must be a subclass of Command or SubCommand. - """ - logging.debug("Loading command class %r", command_cls) - # Create a sub-parser where the command/sub-command can register things. - sub_parser = self.subparsers.add_parser( - command_cls.get_name(), - help=command_cls.get_help(), - epilog=command_cls.get_epilog()) - if issubclass(command_cls, SubCommand): - # Handle SubCommand somewhat different. Instead of calling - # register_arguments we call register_subcommands - command_cls.register_subcommands(sub_parser) - else: - # Handle plain commands easily by recording their commands in the - # dedicated sub-parser we've crated for them. - command_cls.register_arguments(sub_parser) - # In addition, since we don't want to require all sub-classes of - # Command to super-call register_arguments (everyone would forget - # this anyway) we manually register the command class for that - # sub-parser so that dispatch() can look it up later. - sub_parser.set_defaults( - command_cls=command_cls, - parser=sub_parser) - - def dispatch(self, raw_args=None): - """ - Dispatch a command with the specified arguments. - - If arguments are left out they are looked up in sys.argv automatically - """ - # First parse whatever input arguments we've got - args = self.parser.parse_args(raw_args) - # Then look up the command class and construct it with the parser it - # belongs to and the parsed arguments. - command = args.command_cls(args.parser, args) - try: - # Give the command a chance to re-parse command line arguments - command.reparse_arguments(args.parser, raw_args) - except NotImplementedError: - pass - try: - return command.invoke() - except LavaCommandError as ex: - print >> sys.stderr, "ERROR: %s" % (ex,) - return 1 +from lava.tool.errors import CommandError as LavaCommandError +from lava.tool.command import Command, SubCommand === modified file 'setup.py' --- setup.py 2011-12-13 16:18:55 +0000 +++ setup.py 2012-03-13 18:22:16 +0000 @@ -23,9 +23,10 @@ setup( name='lava-tool', - version=":versiontools:lava_tool:__version__", + version=":versiontools:lava.tool:__version__", author="Zygmunt Krynicki", author_email="zygmunt.krynicki@linaro.org", + namespace_packages=['lava'], packages=find_packages(), description="Command line utility for Linaro validation services", url='https://launchpad.net/lava-tool', @@ -34,11 +35,11 @@ entry_points=""" [console_scripts] lava-tool = lava_tool.dispatcher:main - lava = lava_tool.dispatcher:main_nonlegacy + lava = lava.tool.main:LavaDispatcher.run [lava.commands] - help = lava_tool.commands.misc:help + help = lava.tool.commands.help:help [lava_tool.commands] - help = lava_tool.commands.misc:help + help = lava.tool.commands.help:help auth-add = lava_tool.commands.auth:auth_add """, classifiers=[