Skip to content

Instantly share code, notes, and snippets.

@nacx
Created May 2, 2012 14:15
Show Gist options
  • Save nacx/2576825 to your computer and use it in GitHub Desktop.
Save nacx/2576825 to your computer and use it in GitHub Desktop.
Pluggable command line tool
import os
# Automatically set the __all__ variable with all
# the available plugins.
plugin_dir = "plugins"
__all__ = []
for filename in os.listdir(plugin_dir):
filename = plugin_dir + "/" + filename
if os.path.isfile(filename):
basename = os.path.basename(filename)
base, extension = os.path.splitext(basename)
if extension == ".py" and not basename.startswith("_"):
__all__.append(base)
#!/usr/bin/env python
class AbsPlugin:
""" Abstract plugin """
def _commands(self):
""" Get the list of commands for the current plugin.
By default all public methods in the plugin implementation
will be used as plugin commands. This method can be overriden
in subclasses to customize the available command list """
attrs = filter(lambda attr: not attr.startswith('_'), dir(self))
commands = {}
for attr in attrs:
method = getattr(self, attr)
commands[attr] = method
return commands
#!/usr/bin/env python
from pluginmanager import PluginManager
import sys
class CLI:
""" Main command line interface """
def __init__(self):
""" Initialize the plugin manager """
self.__pluginmanager = PluginManager()
def parse_input(self):
""" Validates user input and delegates to the plugin manager """
if len(sys.argv) < 2:
print "Usage: cli <plugin> <command> [<options>]"
print "The following plugins are available:\n"
self.__pluginmanager.help_all()
elif len(sys.argv) == 2:
print "Usage: cli <plugin> <command> [<options>]"
# Call the given plugin without command to print
# the help of the plugin
return self.__pluginmanager.call(sys.argv[1], None, None)
else:
# Call the command in the given plugin with the
# remaining arguments
return self.__pluginmanager.call(sys.argv[1],
sys.argv[2], sys.argv[3:])
if __name__ == "__main__":
cli = CLI()
ret = cli.parse_input()
exit(ret) if ret else exit()
#!/usr/bin/env python
from plugins import __all__
class PluginManager:
""" Manages available plugins """
def __init__(self):
""" Initialize the plugin list """
self.__plugins = {}
def load_plugin(self, plugin_name):
""" Loads a single plugin given its name """
if not plugin_name in __all__:
raise KeyError("Plugin " + plugin_name + " not found")
try:
plugin = self.__plugins[plugin_name]
except KeyError:
# Load the plugin only if not loaded yet
module = __import__("plugins." + plugin_name, fromlist=["plugins"])
plugin = module.load()
self.__plugins[plugin_name] = plugin
return plugin
def call(self, plugin_name, command_name, args):
""" Calls the given command on the given plugin """
try:
plugin = self.load_plugin(plugin_name)
if not command_name:
self.help(plugin)
else:
try:
command = plugin._commands()[command_name]
return command(args)
except KeyError:
# Command not found in plugin. Print only plugin help
self.help(plugin)
except KeyError:
# Plugin not found, pring generic help
self.help_all()
def help(self, plugin):
""" Prints the help for the given plugin """
commands = plugin._commands()
plugin_name = plugin.__module__.split('.')[-1]
print "%s" % plugin.__doc__
for command in sorted(commands.iterkeys()):
print " %s %s\t%s" % (plugin_name, command,
commands[command].__doc__)
def help_all(self):
""" Prints the help for all registered plugins """
for name in sorted(__all__):
plugin = self.load_plugin(name)
self.help(plugin)
print
#!/usr/bin/env python
# This is an example plugin that can be used as a
# skeleton for new plugins.
# The documentation string in the plugin class will be used to
# print the help of the plugin.
from abstract import AbsPlugin
class SkeletonPlugin(AbsPlugin):
""" An example plugin that prints dummy messages """
def __init__(self):
pass
# Public methods will be considered plugin commands.
# The name of the command will be the method name.
# The documentation string in command methods will be used to
# print the help of the command.
# The arguments are the options given to the command itself
def dummy(self, args):
""" Prints a dummy message """
print "This is the print_handler in the example plugin"
# Each plugin must provide a load method at module level that will be
# used to instantiate the plugin
def load():
""" Loads the current plugin """
return SkeletonPlugin()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment