diff --git a/VERSION.txt b/VERSION.txt index 8fc3e14..7be101b 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -6.8.3 \ No newline at end of file +6.8.8 \ No newline at end of file diff --git a/scripttease/constants.py b/scripttease/constants.py index ae45aae..75b4ead 100644 --- a/scripttease/constants.py +++ b/scripttease/constants.py @@ -1,12 +1,22 @@ EXCLUDED_KWARGS = [ "cd", "comment", + "condition", "environments", + "name", "prefix", "register", "shell", "stop", + "sudo", "tags", ] LOGGER_NAME = "script-tease" + + +class PROFILE: + """Supported operating system profiles.""" + + CENTOS = "centos" + UBUNTU = "ubuntu" diff --git a/scripttease/lib/commands/base.py b/scripttease/lib/commands/base.py index 6083a7b..0c3fdef 100644 --- a/scripttease/lib/commands/base.py +++ b/scripttease/lib/commands/base.py @@ -10,28 +10,12 @@ log = logging.getLogger(__name__) # Exports __all__ = ( - "EXCLUDED_KWARGS", "Command", "ItemizedCommand", "Sudo", "Template", ) -# Constants - -EXCLUDED_KWARGS = [ - "cd", - "comment", - "condition", - "environment", - "name", - "prefix", - "register", - "stop", - "sudo", - "tags", -] - # Classes @@ -194,12 +178,13 @@ class ItemizedCommand(object): comment = kwargs.pop("comment", "execute multiple commands") a = list() - # a.append("# %s" % comment) + + if include_comment: + a.append("# %s" % comment) commands = self.get_commands() for c in commands: a.append(c.get_statement(cd=cd, include_comment=False, include_register=include_register, include_stop=include_stop)) - a.append("") return "\n".join(a) diff --git a/scripttease/lib/commands/django.py b/scripttease/lib/commands/django.py index 0577a79..4d240ce 100644 --- a/scripttease/lib/commands/django.py +++ b/scripttease/lib/commands/django.py @@ -18,7 +18,8 @@ django: static django: createsuperuser root """ -from .base import EXCLUDED_KWARGS, Command +from ...constants import EXCLUDED_KWARGS +from .base import Command def django(management_command, *args, excluded_kwargs=None, **kwargs): @@ -62,7 +63,7 @@ def django_check(**kwargs): def django_dump(target, path=None, **kwargs): - kwargs.setdefault("comment", "dump app/model data") + kwargs.setdefault("comment", "dump app/model data for %s" % target) kwargs.setdefault("format", "json") kwargs.setdefault("indent", 4) @@ -73,7 +74,7 @@ def django_dump(target, path=None, **kwargs): def django_load(target, path=None, **kwargs): - kwargs.setdefault("comment", "load app/model data") + kwargs.setdefault("comment", "load app/model data from %s" % target) input_format = kwargs.pop("format", "json") if path is None: path = "../deploy/fixtures/%s.%s" % (target, input_format) @@ -97,6 +98,6 @@ DJANGO_MAPPINGS = { 'django.check': django_check, 'django.dump': django_dump, 'django.load': django_load, - 'django.migration': django_migrate, + 'django.migrate': django_migrate, 'django.static': django_static, } diff --git a/scripttease/lib/commands/mysql.py b/scripttease/lib/commands/mysql.py index 60d5f09..0668131 100644 --- a/scripttease/lib/commands/mysql.py +++ b/scripttease/lib/commands/mysql.py @@ -1,6 +1,10 @@ +# Imports + +from ...constants import EXCLUDED_KWARGS from ...exceptions import InvalidInput -from .base import EXCLUDED_KWARGS, Command +from .base import Command +# Exports __all__ = ( "MYSQL_MAPPINGS", diff --git a/scripttease/lib/commands/pgsql.py b/scripttease/lib/commands/pgsql.py index 2457a46..e5c41a5 100644 --- a/scripttease/lib/commands/pgsql.py +++ b/scripttease/lib/commands/pgsql.py @@ -1,8 +1,9 @@ """ """ +from ...constants import EXCLUDED_KWARGS from ...exceptions import InvalidInput -from .base import EXCLUDED_KWARGS, Command +from .base import Command __all__ = ( diff --git a/scripttease/lib/factories.py b/scripttease/lib/factories.py index 64dbe90..15a68e5 100644 --- a/scripttease/lib/factories.py +++ b/scripttease/lib/factories.py @@ -1,4 +1,5 @@ import logging +from ..constants import EXCLUDED_KWARGS, PROFILE from .commands.base import Command, ItemizedCommand, Template from .commands.centos import CENTOS_MAPPINGS from .commands.ubuntu import UBUNTU_MAPPINGS @@ -6,26 +7,54 @@ from .commands.ubuntu import UBUNTU_MAPPINGS log = logging.getLogger(__name__) -def command_exists(mappings, name): - """Indicates whether a given command exists in this overlay. +def command_factory(loader, excluded_kwargs=None, mappings=None, profile=PROFILE.UBUNTU): + """Get command instances. - :param mappings: A dictionary of command names and command functions. + :param loader: The loader instance used to generate commands. + :type loader: BaseType[scripttease.lib.loaders.BaseLoader] + + :param excluded_kwargs: For commands that support ad hoc sub-commands (like Django), this is a list of keyword + argument names that must be removed. Defaults to the names of common command attributes. + If your implementation requires custom but otherwise standard command attributes, you'll + need to import the ``EXCLUDED_KWARGS`` constant and add your attribute names before + calling the command factory. + :type excluded_kwargs: list[str] + + :param mappings: Additional command mappings which may be used to override or extend those provided by the selected + profile. This is a dictionary of the command name and the callback. :type mappings: dict - :param name: The name of the command. - :type name: str + :param profile: The operating system profile to use for finding commands. + :type profile: str - :rtype: bool + :returns: A list of instances that may be Command, ItemizedCommand, or Template. """ - return name in mappings + # Identify the command mappings to be used. + if profile == "centos": + _mappings = CENTOS_MAPPINGS + elif profile == "ubuntu": + _mappings = UBUNTU_MAPPINGS + else: + log.error("Unsupported or unrecognized profile: %s" % profile) + return None + + # Update mappings if custom mappings have been supplied. + if mappings is not None: + _mappings.update(mappings) + # Support custom exclusion of kwargs when instantiating a command instance. This is specific to the implementation + # and is not used by scripttease CLI. Most callbacks will ignore this; however, those that support subcommands may + # use this to identify keyword arguments that are standard to scripttease versus those that are custom. + _excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS -def command_factory(loader, profile): + # Generate the command instances. commands = list() number = 1 for command_name, args, kwargs in loader.commands: - command = get_command(command_name, profile, *args, **kwargs) + kwargs['excluded_kwargs'] = _excluded_kwargs + + command = get_command(_mappings, command_name, *args, locations=loader.locations, **kwargs) if command is not None: command.number = number commands.append(command) @@ -35,44 +64,44 @@ def command_factory(loader, profile): return commands -def get_command(name, profile, *args, **kwargs): +def get_command(mappings, name, *args, locations=None, **kwargs): """Get a command instance. + :param mappings: The command mappings. + :type mappings: dict + :param name: The name of the command. :type name: str - :param profile: The operating system profile name. - :type profile: str + :param locations: A list of paths where templates may be found. + :type locations: list[str] - args and kwargs are passed to the command function. + args and kwargs are passed to the callback. :rtype: scripttease.lib.commands.base.Command | scripttease.lib.commands.base.ItemizedCommand | scripttease.lib.commands.base.Template """ - if profile == "centos": - mappings = CENTOS_MAPPINGS - elif profile == "ubuntu": - mappings = UBUNTU_MAPPINGS - else: - log.error("Unsupported or unrecognized profile: %s" % profile) - return None - + # Args need to be mutable. _args = list(args) + # Handle templates special. if name == "template": source = _args.pop(0) target = _args.pop(0) - return Template(source, target, **kwargs) + return Template(source, target, locations=locations, **kwargs) - if not command_exists(mappings, name): + # Command is not recognized, is spelled wrong, etc. + if name not in mappings: log.warning("Command does not exist: %s" % name) return None callback = mappings[name] + # Itemization wraps the callback. if "items" in kwargs: items = kwargs.pop("items") return ItemizedCommand(callback, items, *args, **kwargs) + # The callback generates the Command instance. return callback(*args, **kwargs) diff --git a/scripttease/lib/loaders/base.py b/scripttease/lib/loaders/base.py index ddf9ed8..6711e05 100644 --- a/scripttease/lib/loaders/base.py +++ b/scripttease/lib/loaders/base.py @@ -120,8 +120,7 @@ def load_variables(path, env=None): class BaseLoader(File): """Base class for loading a command file.""" - def __init__(self, path, context=None, excluded_kwargs=None, locations=None, mappings=None, profile="ubuntu", - **kwargs): + def __init__(self, path, context=None, locations=None, **kwargs): """Initialize the loader. :param path: The path to the command file. @@ -131,13 +130,6 @@ class BaseLoader(File): converted to a ``dict`` when passed to a Snippet or Template. :type context: scripttease.lib.contexts.Context - :param excluded_kwargs: For commands that support ad hoc sub-commands (like Django), this is a list of keyword - argument names that must be removed. Defaults to the names of common command attributes. - If your implementation requires custom but otherwise standard command attributes, you'll - need to import the ``EXCLUDED_KWARGS`` constant and add your attribute names before - passing it to the loader. - :type excluded_kwargs: list[str] - :param locations: A list of paths where templates and other external files may be found. The ``templates/`` directory in which the command file exists is added automatically. :type locations: list[str] @@ -155,13 +147,9 @@ class BaseLoader(File): """ self.commands = list() self.context = context - self.excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS self.is_loaded = False self.locations = locations or list() - self.mappings = mappings or MAPPINGS self.options = kwargs - self.profile = profile - self.snippets = list() super().__init__(path) diff --git a/scripttease/lib/loaders/yaml.py b/scripttease/lib/loaders/yaml.py index fc69aac..5d1c382 100644 --- a/scripttease/lib/loaders/yaml.py +++ b/scripttease/lib/loaders/yaml.py @@ -77,7 +77,7 @@ class YMLLoader(BaseLoader): count += 1 - self.snippets.append((command_name, args, kwargs)) + self.commands.append((command_name, args, kwargs)) self.is_loaded = True