Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor subcommands (ext.) #32

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
118 changes: 10 additions & 108 deletions twisted/plugins/warp_plugin.py
@@ -1,136 +1,38 @@
from zope.interface import implements

import sys

from twisted.python import usage, reflect
from twisted.plugin import IPlugin
from twisted.application.service import IServiceMaker
from twisted.application import internet
from twisted.web.server import Site
from twisted.python.filepath import FilePath

from warp.webserver import resource, site
from warp.common import store, translate
from warp.iwarp import IWarpService
from warp import runtime


class SkeletonOptions(usage.Options):
optParameters = (
("siteDir", "d", ".", "Base directory of the warp site"),
)

class NodeOptions(usage.Options):
def parseArgs(self, name):
self['name'] = name

class CrudOptions(usage.Options):
def parseArgs(self, name, model):
self['name'] = name
self['model'] = model


class CommandOptions(usage.Options):
def parseArgs(self, fqn):
self['fqn'] = fqn

class Options(usage.Options):
optParameters = (
("siteDir", "d", ".", "Base directory of the warp site"),
("config", "w", "warpconfig", "Config filename"),
)

subCommands = (
("skeleton", None, SkeletonOptions, "Copy Warp site skeleton into current directory"),
("node", None, NodeOptions, "Create a new node"),
("crud", None, CrudOptions, "Create a new CRUD node"),
("adduser", None, usage.Options, "Add a user (interactive)"),
("console", None, usage.Options, "Python console with Warp runtime available"),
("command", "c", CommandOptions, "Run a site-specific command"),
)

from warp import runtime, command

class WarpServiceMaker(object):
implements(IServiceMaker, IPlugin, IWarpService)
tapname = "warp"
description = "Warp webserver"
options = Options
options = command.Options

def makeService(self, options):

siteDir = FilePath(options['siteDir'])

sys.path.insert(0, siteDir.path)

if options.subCommand == "skeleton":
print "Creating skeleton..."
from warp.tools import skeleton
skeleton.createSkeleton(siteDir)
raise SystemExit

configModule = reflect.namedModule(options['config'])
config = configModule.config
runtime.config.update(config)
runtime.config['siteDir'] = siteDir
runtime.config['warpDir'] = FilePath(runtime.__file__).parent()
store.setupStore()
translate.loadMessages()

if options.subCommand == "node":
nodes = siteDir.child("nodes")
if not nodes.exists():
print "Please run this from a Warp site directory"
raise SystemExit

from warp.tools import skeleton
skeleton.createNode(nodes, options.subOptions['name'])
raise SystemExit

elif options.subCommand == 'crud':
nodes = siteDir.child("nodes")
if not nodes.exists():
print "Please run this from a Warp site directory"
raise SystemExit

from warp.tools import autocrud
autocrud.autocrud(nodes, options.subOptions['name'], options.subOptions['model'])
raise SystemExit

elif options.subCommand == 'adduser':
from warp.tools import adduser
adduser.addUser()
raise SystemExit


factory = site.WarpSite(resource.WarpResourceWrapper())
runtime.config['warpSite'] = factory

if hasattr(configModule, 'startup'):
configModule.startup()
command.maybeRun(options)

if options.subCommand == "console":
import code
locals = {'store': runtime.store}
c = code.InteractiveConsole(locals)
c.interact()
raise SystemExit

if options.subCommand == 'command':
obj = reflect.namedObject(options.subOptions['fqn'])
obj()
raise SystemExit

configModule = command.loadConfig(options)
config = runtime.config
port = config['port']
factory = config['warpSite']

if config.get('ssl'):
from warp.webserver import sslcontext
service = internet.SSLServer(config['port'], factory,
service = internet.SSLServer(port, factory,
sslcontext.ServerContextFactory())
else:
service = internet.TCPServer(config["port"], factory)
service = internet.TCPServer(port, factory)

if hasattr(configModule, 'mungeService'):
service = configModule.mungeService(service)

command.doStartup(options)
return service


Expand Down
172 changes: 172 additions & 0 deletions warp/command.py
@@ -0,0 +1,172 @@
import sys

from inspect import getargspec

from twisted.python import usage, reflect
from twisted.python.filepath import FilePath

from warp.webserver import resource, site
from warp.common import store, translate
from warp import runtime


class Options(usage.Options):
optParameters = (
("siteDir", "d", ".", "Base directory of the warp site"),
("config", "w", "warpconfig", "Config filename"),
)

subCommands = []


_commands = {}

# NTA XXX: This is not usable by app, because when command-line
# options are parsed app-specific information is not available yet.
# A hacky workaround would be some code in twisted.warp_plugin to
# import a "magic" app-defined module
def register(shortName=None, skipConfig=False, needStartup=False, optionsParser=None):
"""Decorator to register functions as commands. Functions must
accept options map as the first parameter.

Usage:

@register(*params)
def foo(options, arg1):
pass
"""
def decorator(fn):
name = fn.__name__
doc = fn.__doc__ or ""

if optionsParser is None:
class CmdOptions(usage.Options):
def parseArgs(self, *args):
spec = getargspec(fn)
if spec.defaults:
raise usage.UsageError("Custom command cannot have arguments with default values")
if spec.varargs:
raise usage.UsageError("Custom command cannot take variable number of arguments")
if spec.keywords:
raise usage.UsageError("Custom command cannot take keyword arguments")

cmd_args = spec.args[1:]
count = len(cmd_args)
if len(args) != count:
raise usage.UsageError(
"Wrong number of arguments, %d expected:\n twistd warp %s %s"
% (count, name, " ".join(["<%s>" % arg for arg in cmd_args])))
self["args"] = args
klass = CmdOptions
else:
klass = optionsParser

Options.subCommands.append((name, shortName, klass, doc))

def wrapped(options):
if not skipConfig:
loadConfig(options)
if needStartup:
doStartup(options)
fn(options, *options.subOptions.get("args", ()))

_commands[name] = wrapped
return wrapped
return decorator


def maybeRun(options):
subCommand = options.subCommand

if subCommand:
command = _commands[subCommand]
command(options)
raise SystemExit


def getSiteDir(options):
"""Utility function to get the `siteDir` out of `options`"""
return FilePath(options['siteDir'])


def doStartup(options):
"""Utility function to execute the startup function"""
configModule = reflect.namedModule(options['config'])
if hasattr(configModule, 'startup'):
configModule.startup()


def loadConfig(options):
"""Load the Warp config"""
siteDir = FilePath(options['siteDir'])
sys.path.insert(0, siteDir.path)

configModule = reflect.namedModule(options['config'])
config = configModule.config
runtime.config.update(config)
runtime.config['siteDir'] = siteDir
runtime.config['warpDir'] = FilePath(runtime.__file__).parent()
store.setupStore()
translate.loadMessages()

factory = site.WarpSite(resource.WarpResourceWrapper())
runtime.config['warpSite'] = factory

return configModule


# Pre-defined commands -----------------------------------------------


@register(skipConfig = True)
def skeleton(options):
"Copy Warp site skeleton into current directory"
from warp.tools import skeleton
print 'Creating skeleton...'
siteDir = getSiteDir(options)
skeleton.createSkeleton(siteDir)


@register()
def node(options, name):
"Create a new node"
from warp.tools import skeleton
nodes = getSiteDir(options).child('nodes')
if not nodes.exists():
print 'Please run this from a Warp site directory'
return
skeleton.createNode(nodes, name)


@register()
def crud(options, name, model):
"Create a new CRUD node"
from warp.tools import autocrud
nodes = getSiteDir(options).child('nodes')
if not nodes.exists():
print 'Please run this from a Warp site directory'
return
autocrud.autocrud(nodes, name, model)


@register()
def adduser(options):
"Add a user (interactive)"
from warp.tools import adduser
adduser.addUser()


@register(needStartup = True)
def console(options):
"Python console with Warp runtime available"
import code
locals = {'store': runtime.store}
c = code.InteractiveConsole(locals)
c.interact()


@register(needStartup = True, shortName = "c")
def command(options, function):
"Run a site-specific command"
obj = reflect.namedObject(function)
obj()