Skip to content

Commit

Permalink
Add ability to define extra zopectl commands via setuptools entrypoints.
Browse files Browse the repository at this point in the history
  • Loading branch information
wichert committed Sep 9, 2010
1 parent 9277191 commit 2d42f3a
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 0 deletions.
4 changes: 4 additions & 0 deletions doc/CHANGES.rst
Expand Up @@ -11,6 +11,10 @@ http://docs.zope.org/zope2/releases/.
Bugs Fixed
++++++++++

Features Added
++++++++++++++

- Add ability to define extra zopectl commands via setuptools entrypoints.


2.12.11 (2010-09-09)
Expand Down
37 changes: 37 additions & 0 deletions doc/operation.rst
Expand Up @@ -153,3 +153,40 @@ Troubleshooting
should already be available.

- See the :doc:`CHANGES` for important notes on this version of Zope.



Adding extra commands to Zope
-----------------------------

It is possible to add extra commands to ``zopectl`` by defining *entry points*
in ``setup.py``. Commands have to be put in the ``zopectl.command`` group:

.. code-block:: python
setup(name="MyPackage",
....
entry_points="""
[zopectl.command]
init_app = mypackage.commands:init_application
""")
.. note::

Due to an implementation detail of ``zopectl`` you can not use a minus
character (``-``) in the command name.

This adds a ``init_app`` command that can be used directly from the commandline::

bin\zopectl init_app

The command must be implemented as a python callable. It will be called with
two parameters: the Zope2 application and a tuple with all commandline
arguments. Here is a basic example:

.. code-block:: python
def init_application(app, args):
print 'Initialisating the application'
60 changes: 60 additions & 0 deletions src/Zope2/Startup/zopectl.py
Expand Up @@ -36,10 +36,13 @@
action "help" to find out about available actions.
"""

import csv
import os
import sys
import signal

import pkg_resources

import zdaemon
import Zope2.Startup

Expand Down Expand Up @@ -317,6 +320,63 @@ def help_debug(self):
print "debug -- run the Zope debugger to inspect your database"
print " manually using a Python interactive shell"

def __getattr__(self, name):
"""Getter to check if an unknown command is implement by an entry point."""
if not name.startswith("do_"):
raise AttributeError(name)
data=list(pkg_resources.iter_entry_points("zopectl.command", name=name[3:]))
if not data:
raise AttributeError(name)
if len(data)>1:
print >>sys.stderr, "Warning: multiple entry points found for command"
return
func=data[0].load()
if not callable(func):
print >>sys.stderr, "Error: %s is not a callable method" % name
return

return self.run_entrypoint(data[0])


def run_entrypoint(self, entry_point):
def go(arg):
# If the command line was something like
# """bin/instance run "one two" three"""
# cmd.parseline will have converted it so
# that arg == 'one two three'. This is going to
# foul up any quoted command with embedded spaces.
# So we have to return to self.options.args,
# which is a tuple of command line args,
# throwing away the "run" command at the beginning.
#
# Further complications: if self.options.args has come
# via subprocess, it may look like
# ['run "arg 1" "arg2"'] rather than ['run','arg 1','arg2'].
# If that's the case, we'll use csv to do the parsing
# so that we can split on spaces while respecting quotes.
if len(self.options.args) == 1:
tup = csv.reader(self.options.args, delimiter=' ').next()

# Remove -c and add command name as sys.argv[0]
cmd = [ 'import sys',
'sys.argv.pop()',
'sys.argv.append(r\'%s\')' % entry_point.name
]
if len(tup) > 1:
argv = tup[1:]
cmd.append('[sys.argv.append(x) for x in %s]; ' % argv)
cmd.extend([
'import pkg_resources',
'import Zope2',
'func=pkg_resources.EntryPoint.parse(\'%s\').load(False)' % entry_point,
'app=Zope2.app()',
'func(app)',
])
cmdline = self.get_startup_cmd(self.options.python, ' ; '.join(cmd))
self._exitstatus = os.system(cmdline)
return go


def do_run(self, args):
if not args:
print "usage: run <script> [args]"
Expand Down

0 comments on commit 2d42f3a

Please sign in to comment.