Tweaks to loaders and snippets.

development
Shawn Davis 3 years ago
parent 35915063d5
commit 6562f2f3df
  1. 2
      scripttease/library/snippets/centos.py
  2. 30
      scripttease/library/snippets/django.py
  3. 8
      scripttease/library/snippets/mappings.py
  4. 4
      scripttease/library/snippets/ubuntu.py
  5. 229
      scripttease/loaders/base.py
  6. 3
      scripttease/loaders/ini.py

@ -9,6 +9,7 @@ centos = {
'install': "yum install -y {{ args[0] }}", 'install': "yum install -y {{ args[0] }}",
'reload': "systemctl reload {{ args[0] }}", 'reload': "systemctl reload {{ args[0] }}",
'restart': "systemctl restart {{ args[0] }}", 'restart': "systemctl restart {{ args[0] }}",
'run': "{{ args[0] }}",
'start': "systemctl start {{ args[0] }}", 'start': "systemctl start {{ args[0] }}",
'stop': "systemctl stop {{ args[0] }}", 'stop': "systemctl stop {{ args[0] }}",
'system': { 'system': {
@ -17,6 +18,7 @@ centos = {
'upgrade': "yum update -y", 'upgrade': "yum update -y",
}, },
'uninstall': "yum remove -y {{ args[0] }}", 'uninstall': "yum remove -y {{ args[0] }}",
'upgrade': "yum upgrade -y {{ args[0] }}",
'user': { 'user': {
'create': [ 'create': [
"adduser {{ args[0] }}", "adduser {{ args[0] }}",

@ -1,20 +1,26 @@
from commonkit import parse_jinja_string from commonkit import parse_jinja_string
DJANGO_EXCLUDED_KWARGS = [
def django_command_parser(snippet, args=None): "cd",
"comment",
"environments",
"prefix",
"register",
"shell",
"stop",
"tags",
# "venv", # ?
]
def django_command_parser(snippet, args=None, excluded_kwargs=None):
_excluded_kwargs = excluded_kwargs or DJANGO_EXCLUDED_KWARGS
# We need to remove the common options so any remaining keyword arguments are converted to switches for the # We need to remove the common options so any remaining keyword arguments are converted to switches for the
# management command. # management command.
_kwargs = snippet.kwargs.copy() _kwargs = snippet.kwargs.copy()
_kwargs.pop("comment") for name in _excluded_kwargs:
_kwargs.pop("environments", None) _kwargs.pop(name, None)
_kwargs.pop("prefix", None)
_kwargs.pop("cd", None)
_kwargs.pop("register", None)
_kwargs.pop("shell", None)
_kwargs.pop("stop", None)
_kwargs.pop("tags", None)
_kwargs.pop("venv", None)
# We need to remove some parameters for dumpdata and loaddata. Otherwise they end up as switches. # We need to remove some parameters for dumpdata and loaddata. Otherwise they end up as switches.
if snippet.name in ("django.dumpdata", "django.loaddata"): if snippet.name in ("django.dumpdata", "django.loaddata"):
@ -94,7 +100,7 @@ django = {
'command': "./manage.py {{ command_name }} {% if args %}{{ ' '.join(args) }}{% endif %} {{ switches }}", 'command': "./manage.py {{ command_name }} {% if args %}{{ ' '.join(args) }}{% endif %} {{ switches }}",
'dumpdata': [ 'dumpdata': [
"./manage.py dumpdata {{ app }}{% if model %}.{{ model }}{% endif %}", "./manage.py dumpdata {{ app }}{% if model %}.{{ model }}{% endif %}",
"--indent=4", # "--indent=4",
"{{ switches }}", "{{ switches }}",
'> {{ path }}', '> {{ path }}',
], ],

@ -11,6 +11,12 @@ from .ubuntu import ubuntu
# Exports # Exports
__all__ = (
"MAPPINGS",
"merge",
"merge_dictionaries",
)
# Functions # Functions
@ -50,6 +56,6 @@ def merge_dictionaries(first: dict, second: dict) -> dict:
MAPPINGS = { MAPPINGS = {
'centos': merge(centos, django, messages, mysql, pgsql, posix, py), 'centos': merge(centos, django, messages, mysql, pgsql, posix, python),
'ubuntu': merge(ubuntu, django, messages, mysql, pgsql, posix, python), 'ubuntu': merge(ubuntu, django, messages, mysql, pgsql, posix, python),
} }

@ -13,13 +13,11 @@ ubuntu = {
'test': "apachectl configtest", 'test': "apachectl configtest",
}, },
'install': "apt-get install -y {{ args[0] }}", 'install': "apt-get install -y {{ args[0] }}",
'run': "{{ args[0] }}",
'service': {
'reload': "service {{ args[0] }} reload", 'reload': "service {{ args[0] }} reload",
'restart': "service {{ args[0] }} restart", 'restart': "service {{ args[0] }} restart",
'run': "{{ args[0] }}",
'start': "service {{ args[0] }} start", 'start': "service {{ args[0] }} start",
'stop': "service {{ args[0] }} stop", 'stop': "service {{ args[0] }} stop",
},
'system': { 'system': {
'reboot': "reboot", 'reboot': "reboot",
'update': "apt-get update -y", 'update': "apt-get update -y",

@ -20,6 +20,28 @@ __all__ = (
class BaseLoader(File): class BaseLoader(File):
def __init__(self, path, context=None, locations=None, mappings=None, profile="ubuntu", **kwargs): def __init__(self, path, context=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.
:type context: dict
:param locations: A list of paths where templates and other external files may be found. The 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``.
: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.context = context or dict() self.context = context or dict()
self.is_loaded = False self.is_loaded = False
self.locations = locations or list() self.locations = locations or list()
@ -28,10 +50,11 @@ class BaseLoader(File):
self.profile = profile self.profile = profile
self.snippets = list() self.snippets = list()
super().__init__(path)
# Always include the path to the current file in locations. # Always include the path to the current file in locations.
self.locations.insert(0, self.directory) self.locations.insert(0, self.directory)
# command.locations.append(os.path.join(self.directory, "templates"))
super().__init__(path)
def get_snippets(self): def get_snippets(self):
a = list() a = list()
@ -131,37 +154,6 @@ class BaseLoader(File):
return read_file(self.path) return read_file(self.path)
# def _get_command(self, name, *args, **kwargs):
# args = list(args)
#
# if name not in self.mappings:
# return None
#
# if type(self.mappings[name]) is dict:
# sub = args.pop(0)
# subs = self.mappings[name]
# if sub not in subs:
# return None
#
# _command = subs[sub]
# else:
# _command = self.mappings[name]
#
# context = self.context.copy()
# context['args'] = args
# context.update(kwargs)
#
# if type(_command) in (list, tuple):
# # print(" ".join(_command))
# a = list()
# for i in _command:
# i = parse_jinja_string(i, context)
# a.append(i)
#
# return " ".join(a)
#
# return parse_jinja_string(_command, context)
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
def _get_key_value(self, key, value): def _get_key_value(self, key, value):
"""Process a key/value pair from an INI section. """Process a key/value pair from an INI section.
@ -204,6 +196,13 @@ class BaseLoader(File):
class Snippet(object): class Snippet(object):
"""A snippet is a pseudo-command which collects the content of the snippet as well as the parameters that may be
used to create an executable statement.
The purpose of a snippet is *not* to provide command execution, but to capture the results of a command requested in
a command configuration file.
"""
def __init__(self, name, args=None, content=None, context=None, kwargs=None, parser=None): def __init__(self, name, args=None, content=None, context=None, kwargs=None, parser=None):
"""Initialize a snippet. """Initialize a snippet.
@ -252,11 +251,37 @@ class Snippet(object):
return str(self.name) return str(self.name)
def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True): def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True):
"""Get the command statement represented by the snippet.
:param cd: Indicates whether the change directory option should be included in the output. The ``cd`` option
must also be provided for the command in the configuration file.
:type cd: bool
:param include_comment: Indicates whether the command comment should be included in the output.
:type include_comment: bool
:param include_register: Indicates whether an additional statement to capture the result of the command should
be included in the output. The register option must also be defined for the command in
the configuration file.
:type include_register: bool
:param include_stop: Indicates whether an additional statement to exit on failure of the command should be
included in the output. The stop option must also be defined for the command in the
configuration file.
:type include_stop: bool
:rtype: str
.. note::
The boolean options allow implementers to exercise control over the output of the statement, so that the
snippet may be used in ways appropriate to the implementation.
"""
lines = list() lines = list()
if self.comment and include_comment: if self.comment and include_comment:
lines.append("# %s" % self.comment) lines.append("# %s" % self.comment)
# Handle command itemization. Note that register and stop options are ignored. # Handle snippet itemization. Note that register and stop options are ignored.
if self.is_itemized: if self.is_itemized:
for item in self.items: for item in self.items:
args = list() args = list()
@ -287,7 +312,7 @@ class Snippet(object):
return "\n".join(lines) return "\n".join(lines)
# Handle normal (not itemized) comands. # Handle normal (not itemized) snippets.
a = list() a = list()
if cd and self.cd is not None: if cd and self.cd is not None:
a.append("( cd %s &&" % self.cd) a.append("( cd %s &&" % self.cd)
@ -327,53 +352,26 @@ class Snippet(object):
@property @property
def is_itemized(self): def is_itemized(self):
"""Indicates whether the snippet includes multiple occurrences of the same command.
:rtype: bool
"""
s = " ".join(self.args) s = " ".join(self.args)
return "$item" in s return "$item" in s
@property @property
def is_valid(self): def is_valid(self):
return self.content is not None """Indicates whether the snippet is valid.
:rtype: bool
# def _get_statement(self): .. note::
# if self.is_itemized: This is done by determining if snippet content is not ``None``. The content is found during the loading
# a = list() process when the Snippet instance is created.
# for item in self.items:
# args = list() """
# for arg in self.args: return self.content is not None
# args.append(arg.replace("$item", item))
#
# context = self.context.copy()
# context['args'] = args
# context.update(self.kwargs)
#
# if type(self.content) is list:
# b = list()
# for i in self.content:
# i = parse_jinja_string(i, context)
# b.append(i)
#
# a.append(" ".join(b))
# else:
# a.append(parse_jinja_string(self.content, context))
#
# return "\n".join(a)
#
# context = self.context.copy()
# context['args'] = self.args
# context.update(self.kwargs)
#
# a = list()
# if type(self.content) is list:
# b = list()
# for i in self.content:
# i = parse_jinja_string(i, context)
# b.append(i)
#
# a.append(" ".join(b))
# else:
# a.append(parse_jinja_string(self.content, context))
#
# return " ".join(a)
def _parse(self, args=None, kwargs=None): def _parse(self, args=None, kwargs=None):
"""Build the command statement from snippet content. """Build the command statement from snippet content.
@ -477,13 +475,43 @@ class Template(object):
log.error("Could not parse %s template: %s" % (template, e)) log.error("Could not parse %s template: %s" % (template, e))
return None return None
# noinspection PyUnusedLocal
def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True): def get_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True):
if self.parser == self.PARSER_SIMPLE: lines = list()
return self._get_simple_statement(cd=cd, include_comment=include_comment, include_register=include_register, if include_comment and self.comment is not None:
include_stop=include_stop) lines.append("# %s" % self.comment)
# TODO: Backing up a template's target is currently specific to bash.
if self.backup_enabled:
lines.append('if [[ -f "%s" ]]; then mv %s %s.b; fi;' % (self.target, self.target, self.target))
# Get the content; e.g. parse the template.
content = self.get_content()
# Templates that are bash scripts will fail to write because of the shebang.
if content.startswith("#!"):
_content = content.split("\n")
first_line = _content.pop(0)
lines.append('echo "%s" > %s' % (first_line, self.target))
lines.append("cat >> %s << EOF" % self.target)
lines.append("\n".join(_content))
lines.append("EOF")
else: else:
return self._get_jinja2_statement(cd=cd, include_comment=include_comment, include_register=include_register, lines.append("cat > %s << EOF" % self.target)
include_stop=include_stop) lines.append(content)
lines.append("EOF")
if include_register and self.register is not None:
lines.append("%s=$?;" % self.register)
if include_stop and self.stop:
lines.append("if [[ $%s -gt 0 ]]; exit 1; fi;" % self.register)
elif include_stop and self.stop:
lines.append("if [[ $? -gt 0 ]]; exit 1; fi;")
else:
pass
return "\n".join(lines)
def get_template(self): def get_template(self):
"""Get the template path. """Get the template path.
@ -501,44 +529,9 @@ class Template(object):
@property @property
def is_itemized(self): def is_itemized(self):
return "$item" in self.target # return "$item" in self.target
return False
@property @property
def is_valid(self): def is_valid(self):
return True return True
def _get_command(self, content):
"""Get the cat command."""
output = list()
# TODO: Template backup is not system safe, but is specific to bash.
if self.backup_enabled:
output.append('if [[ -f "%s" ]]; then mv %s %s.b; fi;' % (self.target, self.target, self.target))
if content.startswith("#!"):
_content = content.split("\n")
first_line = _content.pop(0)
output.append('echo "%s" > %s' % (first_line, self.target))
output.append("cat >> %s << EOF" % self.target)
output.append("\n".join(_content))
output.append("EOF")
else:
output.append("cat > %s << EOF" % self.target)
output.append(content)
output.append("EOF")
statement = "\n".join(output)
# noinspection PyUnusedLocal
def _get_jinja2_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True):
"""Parse a Jinja2 template."""
content = self.get_content()
return self._get_command(content)
# noinspection PyUnusedLocal
def _get_simple_statement(self, cd=True, include_comment=True, include_register=True, include_stop=True):
"""Parse a "simple" template."""
content = self.get_content()
return self._get_command(content)

@ -46,6 +46,9 @@ class INILoader(BaseLoader):
kwargs['comment'] = comment kwargs['comment'] = comment
for key, value in ini.items(comment): for key, value in ini.items(comment):
if key.startswith("_"):
continue
# The first key/value pair is the command name and arguments. # The first key/value pair is the command name and arguments.
if count == 0: if count == 0:
command_name = key command_name = key

Loading…
Cancel
Save