# Imports from commonkit import any_list_item, parse_jinja_string, parse_jinja_template, pick, read_file, smart_cast, split_csv, \ File from configparser import ParsingError, RawConfigParser from jinja2.exceptions import TemplateError, TemplateNotFound import logging import os from ...constants import EXCLUDED_KWARGS from ..contexts import Variable from ..snippets.mappings import MAPPINGS log = logging.getLogger(__name__) # Exports __all__ = ( # "filter_snippets", "load_variables", "BaseLoader", ) # Functions # def filter_commands(commands, key, value): # """Filter commands based on the given criteria. # # :param commands: The commands to be filtered. # :type commands: list[scripttease.lib.commands.base.Command] # # :param key: The attribute name to be matched. # :type key: str # # :param value: The value of the attribute. # # """ # filtered = list() # for command in commands: # try: # values = getattr(command, key) # except AttributeError: # continue # # if not any_list_item(values, key): # continue # # if not any_list_item() # if environments is not None and len(snippet.environments) > 0: # if not any_list_item(environments, snippet.environments): # continue # # if tags is not None: # if not any_list_item(tags, snippet.tags): # continue # # filtered.append(snippet) # # return filtered def load_variables(path, env=None): """Load variables from an INI file. :param path: The path to the INI file. :type path: str :param env: The environment name of variables to return. :type env: str :rtype: list[scripttease.lib.contexts.Variable] """ if not os.path.exists(path): log.warning("Variables file does not exist: %s" % path) return list() ini = RawConfigParser() try: ini.read(path) except ParsingError as e: log.warning("Failed to parse %s variables file: %s" % (path, str(e))) return list() variables = list() for variable_name in ini.sections(): if ":" in variable_name: variable_name, _environment = variable_name.split(":") else: _environment = None variable_name = variable_name kwargs = { 'environment': _environment, } _value = None for key, value in ini.items(variable_name): if key == "value": _value = smart_cast(value) continue kwargs[key] = smart_cast(value) variables.append(Variable(variable_name, _value, **kwargs)) if env is not None: filtered_variables = list() for var in variables: if var.environment and var.environment == env or var.environment is None: filtered_variables.append(var) return filtered_variables return variables # Classes 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): """Initialize the loader. :param path: The path to the command file. :type path: str :param context: Global context that may be used when to parse the command file, snippets, and templates. This is 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] :param mappings: A mapping of canonical command names and their snippets, organized by ``profile``. The profile is typically an operating system such as ``centos`` or ``ubuntu``. :type mappings: dict :param profile: The profile (operating system or platform) to be used. :type profile: str kwargs are stored as ``options`` and may include any of the common options for command configuration. These may be supplied as defaults for snippet processing. """ 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) # Always include the path to the current file in locations. self.locations.insert(0, os.path.join(self.directory, "templates")) def get_context(self): """Get the context for parsing command files. :rtype: dict """ d = self.options.copy() if self.context is not None: d.update(self.context.mapping().copy()) return d def load(self): """Load the command file. :rtype: bool """ raise NotImplementedError() def read_file(self): """Get the content of the command file. :rtype: str | None """ if self.context is not None: try: return parse_jinja_template(self.path, self.get_context()) except Exception as e: log.error("Failed to process %s file as template: %s" % (self.path, e)) return None return read_file(self.path) # noinspection PyMethodMayBeStatic def _get_key_value(self, key, value): """Process a key/value pair. :param key: The key to be processed. :type key: str :param value: The value to be processed. :rtype: tuple :returns: The key and value, both of which may be modified from the originals. This handles special names in the following manner: - ``environments``, ``environs``, ``envs``, and ``env`` are treated as a CSV list of environment names if provided as a string. These are normalized to the keyword ``environments``. - ``func`` and ``function`` are normalized to the keyword ``function``. The value is the name of the function to be defined. - ``groups`` is assumed to be a CSV list of groups if provided as a string. - ``items`` is assumed to be a CSV list if provided as a string. These are used to create an "itemized" command. - ``tags`` is assumed to be a CSV list oif provided as a string. All other keys are used as is. Values provided as a CSV list are smart cast to a Python value. """ if key in ("environments", "environs", "envs", "env"): _key = "environments" if type(value) in (list, tuple): _value = value else: _value = split_csv(value) elif key in ("func", "function"): _key = "function" _value = value elif key == "groups": _key = "groups" if type(value) in (list, tuple): _value = value else: _value = split_csv(value) elif key == "items": _key = "items" if type(value) in (list, tuple): _value = value else: _value = split_csv(value) elif key == "tags": _key = "tags" if type(value) in (list, tuple): _value = value else: _value = split_csv(value) else: _key = key _value = smart_cast(value) return _key, _value