# Imports from commonkit import parse_jinja_template, read_file from jinja2.exceptions import TemplateError, TemplateNotFound import logging import os 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), 'name': "template", '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 = os.path.expanduser(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 < %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)