parent
2dfcf26571
commit
e5b7b3dbe0
15 changed files with 400 additions and 21 deletions
@ -1 +1 @@ |
|||||||
6.0.1-d |
6.3.0-d |
@ -1,2 +1,3 @@ |
|||||||
from .base import Command, ItemizedCommand |
from .base import Command, ItemizedCommand |
||||||
|
from .templates import Template |
||||||
# from .factory import command_factory |
# from .factory import command_factory |
||||||
|
@ -0,0 +1,196 @@ |
|||||||
|
# Imports |
||||||
|
|
||||||
|
from jinja2.exceptions import TemplateError, TemplateNotFound |
||||||
|
import logging |
||||||
|
import os |
||||||
|
from superpython.utils import parse_jinja_template, read_file |
||||||
|
from ...constants import LOGGER_NAME |
||||||
|
from .base import Command |
||||||
|
|
||||||
|
log = logging.getLogger(LOGGER_NAME) |
||||||
|
|
||||||
|
# Exports |
||||||
|
|
||||||
|
__all__ = ( |
||||||
|
"Template", |
||||||
|
) |
||||||
|
|
||||||
|
# Classes |
||||||
|
|
||||||
|
|
||||||
|
class Template(Command): |
||||||
|
"""Parse a template.""" |
||||||
|
|
||||||
|
PARSER_JINJA = "jinja2" |
||||||
|
PARSER_SIMPLE = "simple" |
||||||
|
|
||||||
|
def __init__(self, source, target, backup=True, lines=False, parser=PARSER_JINJA, pythonic=False, **kwargs): |
||||||
|
"""Initialize the command. |
||||||
|
|
||||||
|
:param source: The template source file. |
||||||
|
:type source: str |
||||||
|
|
||||||
|
:param target: The path to the output file. |
||||||
|
:type target: str |
||||||
|
|
||||||
|
:param backup: Indicates a copy of an existing file should be madee. |
||||||
|
:type backup: bool |
||||||
|
|
||||||
|
:param parser: The parser to use. |
||||||
|
:type parser: str |
||||||
|
|
||||||
|
:param pythonic: Use a Python one-liner to write the file. Requires Python installation, obviously. This is |
||||||
|
useful when the content of the file cannot be handled with a cat command; for example, shell |
||||||
|
script templates. |
||||||
|
:type pythonic: bool |
||||||
|
|
||||||
|
""" |
||||||
|
# Base parameters need to be captured, because all others are assumed to be switches for the management command. |
||||||
|
self._kwargs = { |
||||||
|
'comment': kwargs.pop("comment", None), |
||||||
|
'cd': kwargs.pop("cd", None), |
||||||
|
'environments': kwargs.pop("environments", None), |
||||||
|
'function': kwargs.pop("function", None), |
||||||
|
# 'local': kwargs.pop("local", False), |
||||||
|
'prefix': kwargs.pop("prefix", None), |
||||||
|
'shell': kwargs.pop("shell", "/bin/bash"), |
||||||
|
'stop': kwargs.pop("stop", False), |
||||||
|
'sudo': kwargs.pop('sudo', False), |
||||||
|
'tags': kwargs.pop("tags", None), |
||||||
|
} |
||||||
|
|
||||||
|
self.backup_enabled = backup |
||||||
|
self.context = kwargs.pop("context", dict()) |
||||||
|
self.parser = parser or self.PARSER_JINJA |
||||||
|
self.pythonic = pythonic |
||||||
|
self.line_by_line = lines |
||||||
|
self.locations = kwargs.pop("locations", list()) |
||||||
|
self.source = source |
||||||
|
self.target = target |
||||||
|
|
||||||
|
# Remaining kwargs are added to the context. |
||||||
|
# print(_kwargs['comment'], kwargs) |
||||||
|
self.context.update(kwargs) |
||||||
|
|
||||||
|
super().__init__("# template: %s" % source, **self._kwargs) |
||||||
|
|
||||||
|
def get_content(self): |
||||||
|
"""Parse the template. |
||||||
|
|
||||||
|
:rtype: str | None |
||||||
|
|
||||||
|
""" |
||||||
|
template = self.get_template() |
||||||
|
|
||||||
|
if self.parser == self.PARSER_SIMPLE: |
||||||
|
content = read_file(template) |
||||||
|
for key, value in self.context.items(): |
||||||
|
replace = "$%s$" % key |
||||||
|
content = content.replace(replace, str(value)) |
||||||
|
|
||||||
|
return content |
||||||
|
|
||||||
|
try: |
||||||
|
return parse_jinja_template(template, self.context) |
||||||
|
except TemplateNotFound: |
||||||
|
log.error("Template not found: %s" % template) |
||||||
|
return None |
||||||
|
except TemplateError as e: |
||||||
|
log.error("Could not parse %s template: %s" % (template, e)) |
||||||
|
return None |
||||||
|
|
||||||
|
def get_statement(self, cd=False, suppress_comment=False): |
||||||
|
"""Override to get the statement based on the parser.""" |
||||||
|
if self.parser == self.PARSER_JINJA: |
||||||
|
return self._get_jinja2_statement(cd=cd).statement |
||||||
|
elif self.parser == self.PARSER_SIMPLE: |
||||||
|
return self._get_simple_statement(cd=cd).statement |
||||||
|
else: |
||||||
|
log.error("Unknown or unsupported template parser: %s" % self.parser) |
||||||
|
return None |
||||||
|
|
||||||
|
def get_template(self): |
||||||
|
"""Get the template path. |
||||||
|
|
||||||
|
:rtype: str |
||||||
|
|
||||||
|
""" |
||||||
|
source = self.source |
||||||
|
for location in self.locations: |
||||||
|
_source = os.path.join(location, self.source) |
||||||
|
if os.path.exists(_source): |
||||||
|
return _source |
||||||
|
|
||||||
|
return source |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
return Command(statement, **self._kwargs) |
||||||
|
|
||||||
|
# # BUG: This still does not seem to work, possibly because a shell script includes EOF? The work around is to use |
||||||
|
# # get_content(), self.target, and write the file manually. |
||||||
|
# if self.line_by_line: |
||||||
|
# a = list() |
||||||
|
# a.append('touch %s' % self.target) |
||||||
|
# for i in content.split("\n"): |
||||||
|
# i = i.replace('"', r'\"') |
||||||
|
# a.append('echo "%s" >> %s' % (i, self.target)) |
||||||
|
# |
||||||
|
# output.append("\n".join(a)) |
||||||
|
# elif self.pythonic: |
||||||
|
# target_file = File(self.target) |
||||||
|
# script_file = "write_%s_template.py" % target_file.name.replace("-", "_") |
||||||
|
# |
||||||
|
# a = list() |
||||||
|
# a.append('content = """%s' % content) |
||||||
|
# a.append('"""') |
||||||
|
# a.append("") |
||||||
|
# a.append('with open("%s", "w") as f:' % self.target) |
||||||
|
# a.append(' f.write(content)') |
||||||
|
# a.append(' f.close()') |
||||||
|
# a.append('') |
||||||
|
# output.append('cat > %s <<EOF' % script_file) |
||||||
|
# output.append("\n".join(a)) |
||||||
|
# output.append('EOF') |
||||||
|
# output.append("") |
||||||
|
# output.append("rm %s" % script_file) |
||||||
|
# else: |
||||||
|
# output.append("cat > %s << EOF" % self.target) |
||||||
|
# output.append(content) |
||||||
|
# output.append("EOF") |
||||||
|
# |
||||||
|
# statement = "\n".join(output) |
||||||
|
# return Command(statement, **self._kwargs) |
||||||
|
|
||||||
|
# noinspection PyUnusedLocal |
||||||
|
def _get_jinja2_statement(self, cd=False): |
||||||
|
"""Parse a Jinja2 template.""" |
||||||
|
content = self.get_content() |
||||||
|
return self._get_command(content) |
||||||
|
|
||||||
|
# noinspection PyUnusedLocal |
||||||
|
def _get_simple_statement(self, cd=False): |
||||||
|
"""Parse a "simple" template.""" |
||||||
|
content = self.get_content() |
||||||
|
|
||||||
|
return self._get_command(content) |
@ -0,0 +1 @@ |
|||||||
|
{% if testing %}{{ testing }} |
@ -0,0 +1,3 @@ |
|||||||
|
I am testing? {{ testing }} |
||||||
|
|
||||||
|
How many times? {{ times }} |
@ -0,0 +1,5 @@ |
|||||||
|
#! /usr/bin/bash |
||||||
|
|
||||||
|
echo "I am testing? $testing$"; |
||||||
|
|
||||||
|
echo "How many times? $times$"; |
@ -0,0 +1,3 @@ |
|||||||
|
I am testing? $testing$ |
||||||
|
|
||||||
|
How many times? $times$ |
@ -0,0 +1,113 @@ |
|||||||
|
from scripttease.library.commands.base import Command, ItemizedCommand, Sudo |
||||||
|
from scripttease.library.commands.templates import Template |
||||||
|
|
||||||
|
|
||||||
|
class TestTemplate(object): |
||||||
|
|
||||||
|
def test_get_content(self): |
||||||
|
context = { |
||||||
|
'testing': "yes", |
||||||
|
'times': 123, |
||||||
|
} |
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/simple.txt", |
||||||
|
"tests/tmp/simple.txt", |
||||||
|
backup=False, |
||||||
|
context=context, |
||||||
|
parser=Template.PARSER_SIMPLE |
||||||
|
) |
||||||
|
content = t.get_content() |
||||||
|
assert "I am testing? yes" in content |
||||||
|
assert "How many times? 123" in content |
||||||
|
|
||||||
|
context = { |
||||||
|
'testing': "yes", |
||||||
|
'times': 123, |
||||||
|
} |
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/simple.sh.txt", |
||||||
|
"tests/tmp/simple.sh", |
||||||
|
backup=False, |
||||||
|
context=context, |
||||||
|
parser=Template.PARSER_SIMPLE |
||||||
|
) |
||||||
|
content = t.get_content() |
||||||
|
assert "I am testing? yes" in content |
||||||
|
assert "How many times? 123" in content |
||||||
|
|
||||||
|
context = { |
||||||
|
'testing': "yes", |
||||||
|
'times': 123, |
||||||
|
} |
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/good.j2.txt", |
||||||
|
"tests/tmp/good.txt", |
||||||
|
backup=False, |
||||||
|
context=context |
||||||
|
) |
||||||
|
content = t.get_content() |
||||||
|
assert "I am testing? yes" in content |
||||||
|
assert "How many times? 123" in content |
||||||
|
|
||||||
|
t = Template("tests/examples/templates/nonexistent.j2.txt", "test/tmp/nonexistent.txt") |
||||||
|
assert t.get_content() is None |
||||||
|
|
||||||
|
t = Template("tests/examples/templates/bad.j2.txt", "test/tmp/nonexistent.txt") |
||||||
|
assert t.get_content() is None |
||||||
|
|
||||||
|
def test_get_statement(self): |
||||||
|
context = { |
||||||
|
'testing': "yes", |
||||||
|
'times': 123, |
||||||
|
} |
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/simple.txt", |
||||||
|
"tests/tmp/simple.txt", |
||||||
|
context=context, |
||||||
|
parser=Template.PARSER_SIMPLE |
||||||
|
) |
||||||
|
s = t.get_statement() |
||||||
|
assert "I am testing? yes" in s |
||||||
|
assert "How many times? 123" in s |
||||||
|
|
||||||
|
context = { |
||||||
|
'testing': "yes", |
||||||
|
'times': 123, |
||||||
|
} |
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/simple.sh.txt", |
||||||
|
"tests/tmp/simple.txt", |
||||||
|
context=context, |
||||||
|
parser=Template.PARSER_SIMPLE |
||||||
|
) |
||||||
|
s = t.get_statement() |
||||||
|
assert "I am testing? yes" in s |
||||||
|
assert "How many times? 123" in s |
||||||
|
|
||||||
|
context = { |
||||||
|
'testing': "yes", |
||||||
|
'times': 123, |
||||||
|
} |
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/good.j2.txt", |
||||||
|
"tests/tmp/good.txt", |
||||||
|
context=context |
||||||
|
) |
||||||
|
s = t.get_statement() |
||||||
|
assert "I am testing? yes" in s |
||||||
|
assert "How many times? 123" in s |
||||||
|
|
||||||
|
t = Template( |
||||||
|
"tests/examples/templates/simple.txt", |
||||||
|
"tests/tmp/simple.txt", |
||||||
|
parser="nonexistent" |
||||||
|
) |
||||||
|
assert t.get_statement() is None |
||||||
|
|
||||||
|
def test_get_template(self): |
||||||
|
t = Template( |
||||||
|
"simple.txt", |
||||||
|
"tests/tmp/simple.txt", |
||||||
|
locations=["tests/examples/templates"] |
||||||
|
) |
||||||
|
assert t.get_template() == "tests/examples/templates/simple.txt" |
Loading…
Reference in new issue