diff --git a/docs/api.rst b/docs/api.rst --- a/docs/api.rst +++ b/docs/api.rst @@ -16,6 +16,21 @@ .. automodule:: scap.arg :exclude-members: __dict__,__weakref__ +.. automodule:: scap.config + :exclude-members: __dict__,__weakref__ + :noindex: + +Built-in scap command classes +============================= + +.. automodule:: scap.main + :exclude-members: __dict__,__weakref__ + +Plugins +======= +.. automodule:: scap.plugins + :exclude-members: __dict__,__weakref__ + Command Execution ================= @@ -34,12 +49,24 @@ .. automodule:: scap.deploy :exclude-members: __dict__,__weakref__ +.. automodule:: scap.git + :exclude-members: __dict__,__weakref__ + +.. automodule:: scap.context + :exclude-members: __dict__,__weakref__ + + General Utilities ================= .. automodule:: scap.utils :exclude-members: __dict__,__weakref__ +.. automodule:: scap.ansi + :exclude-members: __dict__,__weakref__ + +.. automodule:: scap.template + :exclude-members: __dict__,__weakref__ Logging and Monitoring ====================== diff --git a/scap/ansi.py b/scap/ansi.py --- a/scap/ansi.py +++ b/scap/ansi.py @@ -4,7 +4,7 @@ ~~~~~~~~~ ANSI escape codes - .. seealso: http://en.wikipedia.org/wiki/ANSI_escape_code + ..seealso:: `https://en.wikipedia.org/wiki/ANSI_escape_code` """ FG_BLACK = 30 @@ -44,7 +44,7 @@ >>> esc(BG_WHITE, FG_RED, BLINK) == r'\x1b[5;31;47m' True - :param *args: ANSI attributes + :param args: ANSI attributes :returns: str """ return '\x1b[%sm' % ';'.join(str(arg) for arg in sorted(args)) @@ -58,7 +58,7 @@ == '\x1b[34;47mblue on white\x1b[0m' True - :param *args: ANSI color codes and strings of text + :param args: ANSI color codes and strings of text :returns: str """ result = "" diff --git a/scap/arg.py b/scap/arg.py --- a/scap/arg.py +++ b/scap/arg.py @@ -257,6 +257,8 @@ \nnot all commands are affected by every global argument." group = parser.add_argument_group(title, desc) + default_loglevel = os.getenv('SCAP_LOG_LEVEL', logging.INFO) + group.add_argument( '-c', '--conf', dest='conf_file', type=argparse.FileType('r'), @@ -273,7 +275,7 @@ metavar=':') group.add_argument( '-v', '--verbose', action='store_const', - const=logging.DEBUG, default=logging.INFO, + const=logging.DEBUG, default=default_loglevel, dest='loglevel', help='Verbose output') group.add_argument( '-e', '--environment', default=None, diff --git a/scap/context.py b/scap/context.py --- a/scap/context.py +++ b/scap/context.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ scap.context - ~~~~~~~~~~~ + ~~~~~~~~~~~~ Management of deployment host/target directories and execution context. """ diff --git a/scap/plugins/__init__.py b/scap/plugins/__init__.py --- a/scap/plugins/__init__.py +++ b/scap/plugins/__init__.py @@ -1,3 +1,57 @@ +# -*- coding: utf-8 -*- +""" + scap.plugins + ~~~~~~~~~~~~ + Scap plugin architecture + + Scap has a very simple and straightforward plugin mechanism which works as + follows: + + #. At runtime, the scap executable scans for any `*.py` files in a + couple of standard locations. Specifically, :func:`find_plugins` will + find plugins in the current project (`./scap/plugins/*.py`) and in the + user's home directory (`~/.scap/plugins/*.py`) + #. Any matching files are loaded by :func:`scap.plugins.load_plugins` and + the code evaluated in the context of the scap.plugins package namespace. + #. Finally, any classes within a plugin module which inherit from + :class:`scap.cli.Application` are registered as scap subcommands + (see :func:`scap.cli.subcommand`). + + So, for example, if you have a file named `scap/plugins/hello.py` relative + to either the current working directory or your user's home directory, then + scap will evaluate hello.py as a module named :mod:`scap.plugins.hello`. + Now if you want it to actually work, then hopefully you have included a + class inside hello.py, defined similarly to the following + + **Usage Example**:: + + import scap.cli + + @cli.command('hello', subcommands=True, + help='prints "hello world" and exits',) + class HelloCommand(cli.Application): + @cli.subcommand('world') + def world_subcommand(extra_args): + print('hello world') + + .. seealso:: + * The core command-line application infrastructure is in :mod:`scap.cli` + * Many of the build-in subcommands are defined in :mod:`scap.main` + + .. function:: find_plugins(plugin_dirs) + + Get a list of all plugins found in in plugin_dirs + + :param list plugin_dirs: directories to search for plugins + :return: list of all plugin commands found in plugin_dirs + .. function:: load_plugins([plugin_dir]) + + load scap plugin modules. + + :type plugin_dir: str or None + :param str plugin_dir: an additional location to search +""" + import importlib import logging import os @@ -12,9 +66,7 @@ def find_plugins(plugin_dirs): """ - Returns a list of all plugin commands found in plugin_dir - - Returns an empty list if no commands are defined. + returns a list of all plugin commands found in plugin_dirs """ plugins = [] @@ -65,17 +117,22 @@ for plugin in plugins: # module path relative to scap.plugins: plugin_module = ".%s" % plugin - mod = importlib.import_module(plugin_module, "scap.plugins") - # find classes in mod which extend scap.cli.Application - for objname in dir(mod): - obj = getattr(mod, objname) - if type(obj) is type and issubclass(obj, Application): - if objname in loaded_plugins: - # duplicate: another plugin already used the same name - msg = 'Duplicate plugin named %s, skipping.' % objname - logging.getLogger().warning(msg) - continue - # copy the class into the scap.plugins namespace - setattr(this_module, objname, obj) - loaded_plugins[objname] = obj - __all__.append(objname) + try: + mod = importlib.import_module(plugin_module, "scap.plugins") + # find classes in mod which extend scap.cli.Application + for objname in dir(mod): + obj = getattr(mod, objname) + if type(obj) is type and issubclass(obj, Application): + if objname in loaded_plugins: + # duplicate: another plugin already used the same name + msg = 'Duplicate plugin named %s, skipping.' + logging.getLogger().warning(msg, objname) + continue + # copy the class into the scap.plugins namespace + setattr(this_module, objname, obj) + loaded_plugins[objname] = obj + __all__.append(objname) + except Exception: + msg = 'Problem loading plugins from module: scap.plugins.%s ' + logger = logging.getLogger() + logger.warning(msg % plugin, exc_info=sys.exc_info()) diff --git a/scap/template.py b/scap/template.py --- a/scap/template.py +++ b/scap/template.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ scap.template - ~~~~~~~~~~ + ~~~~~~~~~~~~~ Module for working with file templates """