aboutsummaryrefslogtreecommitdiffhomepage
path: root/gbp
diff options
context:
space:
mode:
Diffstat (limited to 'gbp')
-rw-r--r--gbp/config.py75
-rw-r--r--gbp/git/repository.py3
-rwxr-xr-xgbp/scripts/buildpackage.py29
-rwxr-xr-xgbp/scripts/clone.py14
-rw-r--r--gbp/scripts/common/pq.py2
-rwxr-xr-xgbp/scripts/config.py129
-rw-r--r--gbp/scripts/create_remote_repo.py61
-rw-r--r--gbp/scripts/dch.py35
-rw-r--r--gbp/scripts/import_dsc.py18
-rw-r--r--gbp/scripts/import_orig.py13
-rwxr-xr-xgbp/scripts/pq.py13
-rwxr-xr-xgbp/scripts/pull.py14
-rw-r--r--gbp/scripts/supercommand.py6
13 files changed, 326 insertions, 86 deletions
diff --git a/gbp/config.py b/gbp/config.py
index e465cede..fc31076e 100644
--- a/gbp/config.py
+++ b/gbp/config.py
@@ -45,6 +45,27 @@ def check_tristate(option, opt, value):
else:
return val
+
+def safe_option(f):
+ def _decorator(self, *args, **kwargs):
+ obj = self
+ option_name = kwargs.get('option_name')
+ if not option_name and len(args):
+ option_name = args[0]
+
+ # We're decorating GbpOption not GbpOptionParser
+ if not hasattr(obj, 'valid_options'):
+ if not hasattr(obj, 'parser'):
+ raise ValueError("Can only decorete GbpOptionParser and GbpOptionGroup not %s" % obj)
+ else:
+ obj = obj.parser
+
+ if option_name and not option_name.startswith('no-'):
+ obj.valid_options.append(option_name)
+ return f(self, *args, **kwargs)
+ return _decorator
+
+
class GbpOption(Option):
TYPES = Option.TYPES + ('path', 'tristate')
TYPE_CHECKER = copy(Option.TYPE_CHECKER)
@@ -305,7 +326,7 @@ class GbpOptionParser(OptionParser):
files = envvar.split(':') if envvar else klass.def_config_files
return [ os.path.expanduser(f) for f in files ]
- def _parse_config_files(self):
+ def parse_config_files(self):
"""
Parse the possible config files and set appropriate values
default values
@@ -335,11 +356,13 @@ class GbpOptionParser(OptionParser):
# Update with command specific settings
if parser.has_section(cmd):
- self.config.update(dict(parser.items(cmd, raw=True)))
+ # Don't use items() until we got rid of the compat sections
+ # since this pulls in the defaults again
+ self.config.update(dict(parser._sections[cmd].items()))
for section in self.sections:
if parser.has_section(section):
- self.config.update(dict(parser.items(section, raw=True)))
+ self.config.update(dict(parser._sections[section].items()))
else:
raise NoSectionError("Mandatory section [%s] does not exist."
% section)
@@ -370,8 +393,15 @@ class GbpOptionParser(OptionParser):
self.prefix = prefix
self.config = {}
self.config_files = self.get_config_files()
- self._parse_config_files()
+ self.parse_config_files()
+ self.valid_options = []
+
+ if self.command.startswith('git-') or self.command.startswith('gbp-'):
+ prog = self.command
+ else:
+ prog = "gbp %s" % self.command
OptionParser.__init__(self, option_class=GbpOption,
+ prog=prog,
usage=usage, version='%s %s' % (self.command,
gbp_version))
@@ -418,15 +448,16 @@ class GbpOptionParser(OptionParser):
default = self.config[option_name]
return default
+ @safe_option
def add_config_file_option(self, option_name, dest, help=None, **kwargs):
"""
set a option for the command line parser, the default is read from the config file
- @param option_name: name of the option
- @type option_name: string
- @param dest: where to store this option
- @type dest: string
- @param help: help text
- @type help: string
+ param option_name: name of the option
+ type option_name: string
+ param dest: where to store this option
+ type dest: string
+ param help: help text
+ type help: string
"""
if not help:
help = self.help[option_name]
@@ -439,17 +470,29 @@ class GbpOptionParser(OptionParser):
neg_help = "negates '--%s%s'" % (self.prefix, option_name)
self.add_config_file_option(option_name="no-%s" % option_name, dest=dest, help=neg_help, action="store_false")
+ def get_config_file_value(self, option_name):
+ """
+ Query a single interpolated config file value.
+
+ @param option_name: the config file option to look up
+ @type option_name: string
+ @returns: The config file option value or C{None} if it doesn't exist
+ @rtype: C{str} or C{None}
+ """
+ return self.config.get(option_name)
+
class GbpOptionGroup(OptionGroup):
+ @safe_option
def add_config_file_option(self, option_name, dest, help=None, **kwargs):
"""
set a option for the command line parser, the default is read from the config file
- @param option_name: name of the option
- @type option_name: string
- @param dest: where to store this option
- @type dest: string
- @param help: help text
- @type help: string
+ param option_name: name of the option
+ type option_name: string
+ param dest: where to store this option
+ type dest: string
+ param help: help text
+ type help: string
"""
if not help:
help = self.parser.help[option_name]
diff --git a/gbp/git/repository.py b/gbp/git/repository.py
index 7e0b3299..10b90308 100644
--- a/gbp/git/repository.py
+++ b/gbp/git/repository.py
@@ -1529,7 +1529,8 @@ class GitRepository(object):
"""
commit_sha1 = self.rev_parse("%s^0" % commitish)
args = GitArgs('--pretty=format:%an%x00%ae%x00%ad%x00%cn%x00%ce%x00%cd%x00%s%x00%f%x00%b%x00',
- '-z', '--date=raw', '--name-status', commit_sha1)
+ '-z', '--date=raw', '--no-renames', '--name-status',
+ commit_sha1)
out, err, ret = self._git_inout('show', args.args)
if ret:
raise GitRepositoryError("Unable to retrieve commit info for %s"
diff --git a/gbp/scripts/buildpackage.py b/gbp/scripts/buildpackage.py
index 753ad64a..c077b9e6 100755
--- a/gbp/scripts/buildpackage.py
+++ b/gbp/scripts/buildpackage.py
@@ -365,20 +365,12 @@ def changes_file_suffix(dpkg_args):
return os.getenv('ARCH', None) or du.get_arch()
-def parse_args(argv, prefix):
- args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == 0 ]
- dpkg_args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == -1 ]
-
- # We handle these although they don't have a --git- prefix
- for arg in [ "--help", "-h", "--version" ]:
- if arg in dpkg_args:
- args.append(arg)
-
+def build_parser(name, prefix=None):
try:
- parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix=prefix)
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix=prefix)
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return None, None, None
+ return None
tag_group = GbpOptionGroup(parser, "tag options", "options related to git tag creation")
branch_group = GbpOptionGroup(parser, "branch options", "branch layout options")
@@ -453,6 +445,21 @@ def parse_args(argv, prefix):
export_group.add_option("--git-dont-purge", action="store_true", dest="dont_purge", default=False,
help="deprecated, use --git-no-purge instead")
export_group.add_boolean_config_file_option(option_name="overlay", dest="overlay")
+ return parser
+
+
+def parse_args(argv, prefix):
+ args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == 0 ]
+ dpkg_args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == -1 ]
+
+ # We handle these although they don't have a --git- prefix
+ for arg in [ "--help", "-h", "--version" ]:
+ if arg in dpkg_args:
+ args.append(arg)
+
+ parser = build_parser(argv[0], prefix=prefix)
+ if not parser:
+ return None, None, None
options, args = parser.parse_args(args)
gbp.log.setup(options.color, options.verbose, options.color_scheme)
diff --git a/gbp/scripts/clone.py b/gbp/scripts/clone.py
index 251cef21..62d0dcc2 100755
--- a/gbp/scripts/clone.py
+++ b/gbp/scripts/clone.py
@@ -29,13 +29,13 @@ from gbp.errors import GbpError
import gbp.log
-def parse_args (argv):
+def build_parser(name):
try:
- parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ parser = GbpOptionParser(command=os.path.basename(name), prefix='',
usage='%prog [options] repository - clone a remote repository')
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return None, None
+ return None
branch_group = GbpOptionGroup(parser, "branch options", "branch tracking and layout options")
parser.add_option_group(branch_group)
@@ -53,10 +53,16 @@ def parse_args (argv):
parser.add_config_file_option(option_name="color", dest="color", type='tristate')
parser.add_config_file_option(option_name="color-scheme",
dest="color_scheme")
+ return parser
+
+
+def parse_args (argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
(options, args) = parser.parse_args(argv)
gbp.log.setup(options.color, options.verbose, options.color_scheme)
-
return (options, args)
diff --git a/gbp/scripts/common/pq.py b/gbp/scripts/common/pq.py
index 8e41d4ae..d3c07d17 100644
--- a/gbp/scripts/common/pq.py
+++ b/gbp/scripts/common/pq.py
@@ -89,7 +89,7 @@ def parse_gbp_commands(info, cmd_tag, noarg_cmds, arg_cmds):
elif noarg_cmds and cmd in noarg_cmds:
commands[cmd] = match.group('args')
else:
- gbp.log.warn("Ignoring unknow gbp-command '%s' in commit %s"
+ gbp.log.warn("Ignoring unknown gbp-command '%s' in commit %s"
% (line, info['id']))
return commands
diff --git a/gbp/scripts/config.py b/gbp/scripts/config.py
new file mode 100755
index 00000000..0ebca130
--- /dev/null
+++ b/gbp/scripts/config.py
@@ -0,0 +1,129 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2014 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""Query and display config file values"""
+
+import ConfigParser
+import sys
+import os, os.path
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.errors import GbpError
+from gbp.scripts.supercommand import import_command
+import gbp.log
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParser(command=os.path.basename(name), prefix='',
+ usage='%prog [options] - display configuration settings')
+ except ConfigParser.ParsingError as err:
+ gbp.log.err(err)
+ return None
+
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+ parser.add_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+ return parser.parse_args(argv)
+
+
+def parse_cmd_config(command):
+ """Make a command parse it's config files"""
+ parser = GbpOptionParser(command)
+ parser.parse_config_files()
+ return parser
+
+
+def print_cmd_single_value(query, printer):
+ """Print a single configuration value of a command
+
+ @param query: the cmd to print the value for
+ @param printer: the printer to output the value
+ """
+ try:
+ cmd, option = query.split('.')
+ except ValueError:
+ return 2
+
+ parser = parse_cmd_config(cmd)
+ value = parser.get_config_file_value(option)
+ printer("%s=%s" % (query, value))
+ return 0 if value else 1
+
+
+def print_cmd_all_values(cmd, printer):
+ """
+ Print all configuration values of a command
+
+ @param cmd: the cmd to print the values for
+ @param printer: the printer to output the values
+ """
+ if not cmd:
+ return 2
+ try:
+ # Populae the parset to get a list of
+ # valid options
+ module = import_command(cmd)
+ parser = module.build_parser(cmd)
+ except (AttributeError, ImportError):
+ return 2
+
+ for option in parser.valid_options:
+ value = parser.get_config_file_value(option)
+ if value != '':
+ printer("%s.%s=%s" % (cmd, option, value))
+ return 0
+
+
+def value_printer(value):
+ if (value):
+ print(value)
+
+
+def main(argv):
+ retval = 1
+
+ (options, args) = parse_args(argv)
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if not args:
+ gbp.log.error("No command given")
+ return 2
+ elif len(args) != 2:
+ gbp.log.error("Can only take a single argument")
+ return 2
+ else:
+ query = args[1]
+
+ if '.' in query:
+ retval = print_cmd_single_value(query, value_printer)
+ else:
+ retval = print_cmd_all_values(query, value_printer)
+ return retval
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/create_remote_repo.py b/gbp/scripts/create_remote_repo.py
index c8c4a36a..f0e680b4 100644
--- a/gbp/scripts/create_remote_repo.py
+++ b/gbp/scripts/create_remote_repo.py
@@ -18,6 +18,7 @@
# Based on the aa-create-git-repo and dom-new-git-repo shell scripts
"""Create a remote repo based on the current one"""
+import ConfigParser
import sys
import os, os.path
import urlparse
@@ -225,30 +226,16 @@ def push_branches(remote, branches):
gitPush([remote['url'], '--tags'])
-def parse_args(argv, sections=[]):
- """
- Parse the command line arguments and config files.
-
- @param argv: the command line arguments
- @type argv: C{list} of C{str}
- @param sections: additional sections to add to the config file parser
- besides the command name
- @type sections: C{list} of C{str}
- """
-
- # We simpley handle the template section as an additional config file
- # section to parse, this makes e.g. --help work as expected:
- for arg in argv:
- if arg.startswith('--remote-config='):
- sections = ['remote-config %s' % arg.split('=',1)[1]]
- break
- else:
- sections = []
+def build_parser(name, sections=[]):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
+ usage='%prog [options] - '
+ 'create a remote repository',
+ sections=sections)
+ except ConfigParser.ParsingError as err:
+ gbp.log.err(err)
+ return None
- parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix='',
- usage='%prog [options] - '
- 'create a remote repository',
- sections=sections)
branch_group = GbpOptionGroup(parser,
"branch options",
"branch layout and tracking options")
@@ -281,10 +268,34 @@ def parse_args(argv, sections=[]):
dest="template_dir")
parser.add_config_file_option(option_name="remote-config",
dest="remote_config")
+ return parser
+
+
+def parse_args(argv, sections=[]):
+ """
+ Parse the command line arguments and config files.
+
+ @param argv: the command line arguments
+ @type argv: C{list} of C{str}
+ @param sections: additional sections to add to the config file parser
+ besides the command name
+ @type sections: C{list} of C{str}
+ """
+
+ # We simpley handle the template section as an additional config file
+ # section to parse, this makes e.g. --help work as expected:
+ for arg in argv:
+ if arg.startswith('--remote-config='):
+ sections = ['remote-config %s' % arg.split('=',1)[1]]
+ break
+ else:
+ sections = []
- (options, args) = parser.parse_args(argv)
+ parser = build_parser(argv[0], sections)
+ if not parser:
+ return None, None
- return options, args
+ return parser.parse_args(argv)
def main(argv):
diff --git a/gbp/scripts/dch.py b/gbp/scripts/dch.py
index a848d6d2..f36f2877 100644
--- a/gbp/scripts/dch.py
+++ b/gbp/scripts/dch.py
@@ -283,20 +283,14 @@ def changelog_commit_msg(options, version):
return options.commit_msg % dict(version=version)
-def main(argv):
- ret = 0
- changelog = 'debian/changelog'
- until = 'HEAD'
- found_snapshot_banner = False
- version_change = {}
- branch = None
-
+def build_parser(name):
try:
- parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix='',
+ parser = GbpOptionParserDebian(command=os.path.basename(name),
usage='%prog [options] paths')
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return 1
+ return None
+
range_group = GbpOptionGroup(parser, "commit range options",
"which commits to add to the changelog")
version_group = GbpOptionGroup(parser, "release & version number options",
@@ -374,10 +368,31 @@ def main(argv):
dest="customization_file",
help=help_msg)
+
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
(options, args) = parser.parse_args(argv[1:])
gbp.log.setup(options.color, options.verbose, options.color_scheme)
dch_options = process_options(options, parser)
editor_cmd = process_editor_option(options)
+ return options, args, dch_options, editor_cmd
+
+def main(argv):
+ ret = 0
+ changelog = 'debian/changelog'
+ until = 'HEAD'
+ found_snapshot_banner = False
+ version_change = {}
+ branch = None
+
+
+ options, args, dch_options, editor_cmd = parse_args(argv)
try:
try:
diff --git a/gbp/scripts/import_dsc.py b/gbp/scripts/import_dsc.py
index d60e0d16..600b394d 100644
--- a/gbp/scripts/import_dsc.py
+++ b/gbp/scripts/import_dsc.py
@@ -204,14 +204,13 @@ def set_bare_repo_options(options):
% (["", " '--no-pristine-tar'"][options.pristine_tar], ))
options.pristine_tar = False
-
-def parse_args(argv):
+def build_parser(name):
try:
- parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix='',
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
usage='%prog [options] /path/to/package.dsc')
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return None, None
+ return None
import_group = GbpOptionGroup(parser, "import options",
"pristine-tar and filtering")
@@ -263,9 +262,15 @@ def parse_args(argv):
dest="author_committer_date")
import_group.add_boolean_config_file_option(option_name="allow-unauthenticated",
dest="allow_unauthenticated")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
(options, args) = parser.parse_args(argv[1:])
- gbp.log.setup(options.color, options.verbose)
gbp.log.setup(options.color, options.verbose, options.color_scheme)
return options, args
@@ -377,7 +382,8 @@ def main(argv):
repo.create_branch(options.upstream_branch, commit)
if options.pristine_tar:
repo.pristine_tar.commit(src.tgz, options.upstream_branch)
- if is_empty and not repo.has_branch(options.debian_branch):
+ if (not repo.has_branch(options.debian_branch)
+ and (is_empty or options.create_missing_branches)):
repo.create_branch(options.debian_branch, commit)
if not src.native:
if src.diff or src.deb_tgz:
diff --git a/gbp/scripts/import_orig.py b/gbp/scripts/import_orig.py
index aae93fab..542896ef 100644
--- a/gbp/scripts/import_orig.py
+++ b/gbp/scripts/import_orig.py
@@ -181,13 +181,13 @@ def set_bare_repo_options(options):
options.merge = False
-def parse_args(argv):
+def build_parser(name):
try:
- parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix='',
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
usage='%prog [options] /path/to/upstream-version.tar.gz | --uscan')
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return None, None
+ return None
import_group = GbpOptionGroup(parser, "import options",
"pristine-tar and filtering")
@@ -241,6 +241,13 @@ def parse_args(argv):
default=False, help="deprecated - don't use.")
parser.add_option("--uscan", dest='uscan', action="store_true",
default=False, help="use uscan(1) to download the new tarball.")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
(options, args) = parser.parse_args(argv[1:])
gbp.log.setup(options.color, options.verbose, options.color_scheme)
diff --git a/gbp/scripts/pq.py b/gbp/scripts/pq.py
index 41d3ddf8..fc205bf2 100755
--- a/gbp/scripts/pq.py
+++ b/gbp/scripts/pq.py
@@ -210,9 +210,9 @@ def switch_pq(repo, current):
switch_to_pq_branch(repo, current)
-def parse_args(argv):
+def build_parser(name):
try:
- parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix='',
+ parser = GbpOptionParserDebian(command=os.path.basename(name),
usage="%prog [options] action - maintain patches on a patch queue branch\n"
"Actions:\n"
" export export the patch queue associated to the current branch\n"
@@ -226,7 +226,7 @@ def parse_args(argv):
" switch switch to patch-queue branch and vice versa")
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return None, None
+ return None
parser.add_boolean_config_file_option(option_name="patch-numbers", dest="patch_numbers")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
@@ -238,6 +238,13 @@ def parse_args(argv):
parser.add_config_file_option(option_name="color", dest="color", type='tristate')
parser.add_config_file_option(option_name="color-scheme",
dest="color_scheme")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
return parser.parse_args(argv)
diff --git a/gbp/scripts/pull.py b/gbp/scripts/pull.py
index 03be9fc7..fb0d8271 100755
--- a/gbp/scripts/pull.py
+++ b/gbp/scripts/pull.py
@@ -68,13 +68,14 @@ def fast_forward_branch(branch, repo, options):
msg="gbp: forward %s to %s" % (branch, remote))
return update
-def parse_args(argv):
+
+def build_parser(name):
try:
- parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ parser = GbpOptionParser(command=os.path.basename(name), prefix='',
usage='%prog [options] - safely update a repository from remote')
except ConfigParser.ParsingError as err:
gbp.log.err(err)
- return None, None
+ return None
branch_group = GbpOptionGroup(parser, "branch options", "branch update and layout options")
parser.add_option_group(branch_group)
@@ -93,6 +94,13 @@ def parse_args(argv):
parser.add_config_file_option(option_name="color", dest="color", type='tristate')
parser.add_config_file_option(option_name="color-scheme",
dest="color_scheme")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
return parser.parse_args(argv)
diff --git a/gbp/scripts/supercommand.py b/gbp/scripts/supercommand.py
index 4f2721f3..2eb64de2 100644
--- a/gbp/scripts/supercommand.py
+++ b/gbp/scripts/supercommand.py
@@ -44,10 +44,11 @@ The most commonly used commands are:
import-dscs - import multiple Debian source packages
"""
-def import_command(modulename):
+def import_command(cmd):
"""
Import the module that implements the given command
"""
+ modulename = sanitize(cmd)
if (not re.match(r'[a-z][a-z0-9_]', modulename) or
modulename in invalid_modules):
raise ImportError('Illegal module name %s' % modulename)
@@ -69,9 +70,8 @@ def supercommand(argv=None):
usage()
return 0
- modulename = sanitize(cmd)
try:
- module = import_command(modulename)
+ module = import_command(cmd)
except ImportError as e:
print >>sys.stderr, "'%s' is not a valid command." % cmd
usage()