Files
deb-python-pecan/pecan/commands/base.py
Ryan Petrello 087ea4f699 Improve pecan documentation and correct intersphinx references.
Change-Id: Iac6229a2727a3c662d3fe9e83e1aa02ef648f025
2014-01-19 17:49:11 -05:00

167 lines
4.6 KiB
Python

import pkg_resources
import argparse
import logging
import sys
from warnings import warn
import six
log = logging.getLogger(__name__)
class HelpfulArgumentParser(argparse.ArgumentParser):
def error(self, message): # pragma: nocover
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
If you override this in a subclass, it should not return -- it
should either exit or raise an exception.
"""
self.print_help(sys.stderr)
self._print_message('\n')
self.exit(2, '%s: %s\n' % (self.prog, message))
class CommandManager(object):
""" Used to discover `pecan.command` entry points. """
def __init__(self):
self.commands = {}
self.load_commands()
def load_commands(self):
for ep in pkg_resources.iter_entry_points('pecan.command'):
log.debug('%s loading plugin %s', self.__class__.__name__, ep)
if ep.name in self.commands:
warn(
"Duplicate entry points found on `%s` - ignoring %s" % (
ep.name,
ep
),
RuntimeWarning
)
continue
try:
cmd = ep.load()
assert hasattr(cmd, 'run')
except Exception as e: # pragma: nocover
warn("Unable to load plugin %s: %s" % (ep, e), RuntimeWarning)
continue
self.add({ep.name: cmd})
def add(self, cmd):
self.commands.update(cmd)
class CommandRunner(object):
""" Dispatches `pecan` command execution requests. """
def __init__(self):
self.manager = CommandManager()
self.parser = HelpfulArgumentParser(add_help=True)
self.parser.add_argument(
'--version',
action='version',
version='Pecan %s' % self.version
)
self.parse_sub_commands()
def parse_sub_commands(self):
subparsers = self.parser.add_subparsers(
dest='command_name',
metavar='command'
)
for name, cmd in self.commands.items():
sub = subparsers.add_parser(
name,
help=cmd.summary
)
for arg in getattr(cmd, 'arguments', tuple()):
arg = arg.copy()
if isinstance(arg.get('name'), six.string_types):
sub.add_argument(arg.pop('name'), **arg)
elif isinstance(arg.get('name'), list):
sub.add_argument(*arg.pop('name'), **arg)
def run(self, args):
ns = self.parser.parse_args(args)
self.commands[ns.command_name]().run(ns)
@classmethod
def handle_command_line(cls): # pragma: nocover
runner = CommandRunner()
runner.run(sys.argv[1:])
@property
def version(self):
return pkg_resources.get_distribution('pecan').version
@property
def commands(self):
return self.manager.commands
class BaseCommandMeta(type):
@property
def summary(cls):
"""
This is used to populate the --help argument on the command line.
This provides a default behavior which takes the first sentence of the
command's docstring and uses it.
"""
return cls.__doc__.strip().splitlines()[0].rstrip('.')
class BaseCommandParent(object):
"""
A base interface for Pecan commands.
Can be extended to support ``pecan`` command extensions in individual Pecan
projects, e.g.,
$ ``pecan my-custom-command config.py``
::
# myapp/myapp/custom_command.py
class CustomCommand(pecan.commands.base.BaseCommand):
'''
(First) line of the docstring is used to summarize the command.
'''
arguments = ({
'name': '--extra_arg',
'help': 'an extra command line argument',
'optional': True
})
def run(self, args):
super(SomeCommand, self).run(args)
if args.extra_arg:
pass
"""
arguments = ({
'name': 'config_file',
'help': 'a Pecan configuration file',
'nargs': '?',
'default': None,
},)
def run(self, args):
"""To be implemented by subclasses."""
self.args = args
def load_app(self):
from pecan import load_app
return load_app(self.args.config_file)
BaseCommand = BaseCommandMeta('BaseCommand', (BaseCommandParent,), {
'__doc__': BaseCommandParent.__doc__
})