summaryrefslogtreecommitdiff
path: root/pecan/commands
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2012-03-14 12:27:54 -0700
committerRyan Petrello <lists@ryanpetrello.com>2012-03-15 14:55:49 -0700
commit74100f273337b282c9b6940bcb204342c5985c90 (patch)
tree6bc2142aa4939cd0938758e33b8f9ab545a3c732 /pecan/commands
parent40913103ffc83a44958e770085b1eca94256ce6e (diff)
downloadpecan-74100f273337b282c9b6940bcb204342c5985c90.tar.gz
Implementing a simple command/plugin library to replace PasteScript, including:
$ pecan serve $ pecan shell
Diffstat (limited to 'pecan/commands')
-rw-r--r--pecan/commands/__init__.py12
-rw-r--r--pecan/commands/base.py127
-rw-r--r--pecan/commands/create.py42
-rw-r--r--pecan/commands/runner.py131
-rw-r--r--pecan/commands/serve.py78
-rw-r--r--pecan/commands/shell.py19
6 files changed, 141 insertions, 268 deletions
diff --git a/pecan/commands/__init__.py b/pecan/commands/__init__.py
index 3148810..dade8a0 100644
--- a/pecan/commands/__init__.py
+++ b/pecan/commands/__init__.py
@@ -1,8 +1,4 @@
-"""
-PasteScript commands for Pecan.
-"""
-
-from runner import CommandRunner # noqa
-from create import CreateCommand # noqa
-from shell import ShellCommand # noqa
-from serve import ServeCommand # noqa
+from base import CommandRunner, BaseCommand
+from serve import ServeCommand
+from shell import ShellCommand
+from create import CreateCommand
diff --git a/pecan/commands/base.py b/pecan/commands/base.py
index f30704c..57c4115 100644
--- a/pecan/commands/base.py
+++ b/pecan/commands/base.py
@@ -1,49 +1,108 @@
-"""
-PasteScript base command for Pecan.
-"""
+import pkg_resources
+import os.path
+import argparse
+import logging
+import sys
+from warnings import warn
from pecan import load_app
-from paste.script import command as paste_command
-import os.path
+log = logging.getLogger(__name__)
+
+
+class CommandManager(object):
+ """ Used to discover `pecan.command` entry points. """
+ def __init__(self):
+ self.commands_ = {}
+ self.load_commands()
-class Command(paste_command.Command):
- """
- Base class for Pecan 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)
+ try:
+ cmd = ep.load()
+ assert hasattr(cmd, 'run')
+ except Exception, e:
+ warn("Unable to load plugin %s: %s" % (ep, e), RuntimeWarning)
+ continue
+ self.add({ep.name: cmd})
- This provides some standard functionality for interacting with Pecan
- applications and handles some of the basic PasteScript command cruft.
+ def add(self, cmd):
+ self.commands_.update(cmd)
- See ``paste.script.command.Command`` for more information.
- """
+ @property
+ def commands(self):
+ return self.commands_
- # command information
- group_name = 'Pecan'
- summary = ''
- # command parser
- parser = paste_command.Command.standard_parser()
+class CommandRunner(object):
+ """ Dispatches `pecan` command execution requests. """
+
+ def __init__(self):
+ self.manager = CommandManager()
+ self.parser = argparse.ArgumentParser(
+ version='Pecan %s' % self.version,
+ add_help=True
+ )
+ self.parse_commands()
+
+ def parse_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()
+ sub.add_argument(arg.pop('command'), **arg)
def run(self, args):
+ ns = self.parser.parse_args(args)
+ self.commands[ns.command_name]().run(ns)
+
+ @classmethod
+ def handle_command_line(cls):
+ runner = CommandRunner()
+ exit_code = runner.run(sys.argv[1:])
+ sys.exit(exit_code)
+
+ @property
+ def version(self):
try:
- return paste_command.Command.run(self, args)
- except paste_command.BadCommand, ex:
- ex.args[0] = self.parser.error(ex.args[0])
- raise
+ dist = pkg_resources.get_distribution('Pecan')
+ if os.path.dirname(os.path.dirname(__file__)) == dist.location:
+ return dist.version
+ else:
+ return '(development)'
+ except:
+ return '(development)'
- def load_app(self):
- return load_app(self.validate_file(self.args))
+ @property
+ def commands(self):
+ return self.manager.commands_
- def logging_file_config(self, config_file):
- if os.path.splitext(config_file)[1].lower() == '.ini':
- paste_command.Command.logging_file_config(self, config_file)
- def validate_file(self, argv):
- if not argv or not os.path.isfile(argv[0]):
- raise paste_command.BadCommand(
- 'This command needs a valid config file.'
- )
- return argv[0]
+class BaseCommand(object):
+ """ Base class for Pecan commands. """
+
+ class __metaclass__(type):
+ @property
+ def summary(cls):
+ return cls.__doc__.strip().splitlines()[0].rstrip('.')
- def command(self):
- pass
+ arguments = ({
+ 'command': 'config_file',
+ 'help': 'a Pecan configuration file'
+ },)
+
+ def run(self, args):
+ self.args = args
+
+ def load_app(self):
+ if not os.path.isfile(self.args.config_file):
+ raise RuntimeError('`%s` is not a file.' % self.args.config_file)
+ return load_app(self.args.config_file)
diff --git a/pecan/commands/create.py b/pecan/commands/create.py
index 5ade3c2..ade6301 100644
--- a/pecan/commands/create.py
+++ b/pecan/commands/create.py
@@ -1,16 +1,14 @@
"""
-PasteScript create command for Pecan.
+Create command for Pecan
"""
-from paste.script.create_distro import CreateDistroCommand
-
-from base import Command
+from pecan.commands import BaseCommand
from pecan.templates import DEFAULT_TEMPLATE
import copy
import sys
-class CreateCommand(CreateDistroCommand, Command):
+class CreateCommand(BaseCommand):
"""
Creates the file layout for a new Pecan distribution.
@@ -19,26 +17,14 @@ class CreateCommand(CreateDistroCommand, Command):
egg plugin for user convenience.
"""
- # command information
- summary = __doc__.strip().splitlines()[0].rstrip('.')
- description = None
-
- def command(self):
- if not self.options.list_templates:
- if not self.options.templates:
- self.options.templates = [DEFAULT_TEMPLATE]
- try:
- return CreateDistroCommand.command(self)
- except LookupError, ex:
- sys.stderr.write('%s\n\n' % ex)
- CreateDistroCommand.list_templates(self)
- return 2
-
- def all_entry_points(self):
- entry_points = []
- for entry in CreateDistroCommand.all_entry_points(self):
- if entry.name.startswith('pecan-'):
- entry = copy.copy(entry)
- entry_points.append(entry)
- entry.name = entry.name[6:]
- return entry_points
+ arguments = ({
+ 'command': 'template_name',
+ 'help': 'a registered Pecan template',
+ 'nargs': '?',
+ 'default': DEFAULT_TEMPLATE
+ },)
+
+ def run(self, args):
+ super(CreateCommand, self).run(args)
+ print "NOT IMPLEMENTED"
+ print args.template_name
diff --git a/pecan/commands/runner.py b/pecan/commands/runner.py
deleted file mode 100644
index 77b18f2..0000000
--- a/pecan/commands/runner.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""
-PasteScript command runner.
-"""
-from paste.script import command as paste_command
-
-import optparse
-import os
-import pkg_resources
-import sys
-import warnings
-
-
-class CommandRunner(object):
- """
- Dispatches command execution requests.
-
- This is a custom PasteScript command runner that is specific to Pecan
- commands. For a command to show up, its name must begin with "pecan-".
- It is also recommended that its group name be set to "Pecan" so that it
- shows up under that group when using ``paster`` directly.
- """
-
- def __init__(self):
-
- # set up the parser
- self.parser = optparse.OptionParser(
- add_help_option=False,
- version='Pecan %s' % self.get_version(),
- usage='%prog [options] COMMAND [command_options]'
- )
- self.parser.disable_interspersed_args()
- self.parser.add_option('-h', '--help',
- action='store_true',
- dest='show_help',
- help='show detailed help message')
-
- # suppress BaseException.message warnings for BadCommand
- if sys.version_info < (2, 7):
- warnings.filterwarnings(
- 'ignore',
- 'BaseException\.message has been deprecated as of Python 2\.6',
- DeprecationWarning,
- paste_command.__name__.replace('.', '\\.'))
-
- # register Pecan as a system plugin when using the custom runner
- paste_command.system_plugins.append('Pecan')
-
- def get_command_template(self, command_names):
- if not command_names:
- max_length = 10
- else:
- max_length = max([len(name) for name in command_names])
- return ' %%-%ds %%s\n' % max_length
-
- def get_commands(self):
- commands = {}
- for name, command in paste_command.get_commands().iteritems():
- if name.startswith('pecan-'):
- commands[name[6:]] = command.load()
- return commands
-
- def get_version(self):
- try:
- dist = pkg_resources.get_distribution('Pecan')
- if os.path.dirname(os.path.dirname(__file__)) == dist.location:
- return dist.version
- else:
- return '(development)'
- except:
- return '(development)'
-
- def print_usage(self, file=sys.stdout):
- self.parser.print_help(file=file)
- file.write('\n')
- command_groups = {}
- commands = self.get_commands()
- if not commands:
- file.write('No commands registered.\n')
- return
- command_template = self.get_command_template(commands.keys())
- for name, command in commands.iteritems():
- group_name = command.group_name
- if group_name.lower() == 'pecan':
- group_name = ''
- command_groups.setdefault(group_name, {})[name] = command
- command_groups = sorted(command_groups.items())
- for i, (group, commands) in enumerate(command_groups):
- file.write('%s:\n' % (group or 'Commands'))
- for name, command in sorted(commands.items()):
- file.write(command_template % (name, command.summary))
- if i + 1 < len(command_groups):
- file.write('\n')
-
- def print_known_commands(self, file=sys.stderr):
- commands = self.get_commands()
- command_names = sorted(commands.keys())
- if not command_names:
- file.write('No commands registered.\n')
- return
- file.write('Known commands:\n')
- command_template = self.get_command_template(command_names)
- for name in command_names:
- file.write(command_template % (name, commands[name].summary))
-
- def run(self, args):
- options, args = self.parser.parse_args(args)
- if not args:
- self.print_usage()
- return 0
- command_name = args.pop(0)
- commands = self.get_commands()
- if command_name not in commands:
- sys.stderr.write('Command %s not known\n\n' % command_name)
- self.print_known_commands()
- return 1
- else:
- command = commands[command_name](command_name)
- if options.show_help:
- return command.run(['-h'])
- else:
- return command.run(args)
-
- @classmethod
- def handle_command_line(cls):
- try:
- runner = CommandRunner()
- exit_code = runner.run(sys.argv[1:])
- except paste_command.BadCommand, ex:
- sys.stderr.write('%s\n' % ex)
- exit_code = ex.exit_code
- sys.exit(exit_code)
diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py
index 95cbabf..3d38ab9 100644
--- a/pecan/commands/serve.py
+++ b/pecan/commands/serve.py
@@ -1,64 +1,36 @@
"""
-PasteScript serve command for Pecan.
+Serve command for Pecan.
"""
-from paste.script.serve import ServeCommand as _ServeCommand
+from pecan.commands import BaseCommand
-from base import Command
-import re
-
-class ServeCommand(_ServeCommand, Command):
+class ServeCommand(BaseCommand):
"""
Serves a Pecan web application.
This command serves a Pecan web application using the provided
configuration file for the server and application.
-
- If start/stop/restart is given, then --daemon is implied, and it will
- start (normal operation), stop (--stop-daemon), or do both.
"""
- # command information
- usage = 'CONFIG_FILE [start|stop|restart|status]'
- summary = __doc__.strip().splitlines()[0].rstrip('.')
- description = '\n'.join(
- map(lambda s: s.rstrip(), __doc__.strip().splitlines()[2:])
- )
-
- # command options/arguments
- max_args = 2
-
- # command parser
- parser = _ServeCommand.parser
- parser.remove_option('-n')
- parser.remove_option('-s')
- parser.remove_option('--server-name')
-
- # configure scheme regex
- _scheme_re = re.compile(r'.*')
-
- def command(self):
-
- # set defaults for removed options
- setattr(self.options, 'app_name', None)
- setattr(self.options, 'server', None)
- setattr(self.options, 'server_name', None)
-
- # run the base command
- _ServeCommand.command(self)
-
- def loadserver(self, server_spec, name, relative_to, **kw):
- return (lambda app: WSGIRefServer(app.config.server.host, app.config.server.port, app))
-
- def loadapp(self, app_spec, name, relative_to, **kw):
- return self.load_app()
-
-
-def WSGIRefServer(host, port, app, **options):
- """
- A very simple approach for a WSGI server.
- """
- from wsgiref.simple_server import make_server
- port = int(port)
- srv = make_server(host, port, app, **options)
- srv.serve_forever()
+ def run(self, args):
+ super(ServeCommand, self).run(args)
+ app = self.load_app()
+ self.serve(app, app.config)
+
+ def serve(self, app, conf):
+ """
+ A very simple approach for a WSGI server.
+ """
+ from wsgiref.simple_server import make_server
+ host, port = conf.server.host, int(conf.server.port)
+ srv = make_server(host, port, app)
+ if host == '0.0.0.0':
+ print 'serving on 0.0.0.0:%s, view at http://127.0.0.1:%s' % \
+ (port, port)
+ else:
+ print "serving on http://%s:%s" % (host, port)
+ try:
+ srv.serve_forever()
+ except KeyboardInterrupt:
+ # allow CTRL+C to shutdown
+ pass
diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py
index 574a490..78039e1 100644
--- a/pecan/commands/shell.py
+++ b/pecan/commands/shell.py
@@ -1,27 +1,18 @@
"""
-PasteScript shell command for Pecan.
+Shell command for Pecan.
"""
+from pecan.commands import BaseCommand
from webtest import TestApp
-
-from base import Command
-
import sys
-class ShellCommand(Command):
+class ShellCommand(BaseCommand):
"""
Open an interactive shell with the Pecan app loaded.
"""
- # command information
- usage = 'CONFIG_NAME'
- summary = __doc__.strip().splitlines()[0].rstrip('.')
-
- # command options/arguments
- min_args = 1
- max_args = 1
-
- def command(self):
+ def run(self, args):
+ super(ShellCommand, self).run(args)
# load the application
app = self.load_app()