diff options
author | Ethel Morgan <eth@ethulhu.co.uk> | 2020-07-06 18:23:10 +0100 |
---|---|---|
committer | Ethel Morgan <eth@ethulhu.co.uk> | 2020-07-06 18:23:10 +0100 |
commit | b1e6491f77421ae4623391a7f53af7f3e6c13f34 (patch) | |
tree | acc4ce7ae214b92dbf2c269c70e94b68dac1d640 /thrust | |
parent | 04be5845dbaa6f8dec45a80dbe199861608b96f2 (diff) |
import website from previous repo
Diffstat (limited to 'thrust')
-rwxr-xr-x | thrust | 133 |
1 files changed, 133 insertions, 0 deletions
@@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Thrust is a static site generator.""" + +import sys +import textwrap + +from typing import Any, Dict + +import jinja2 +import jinja2.ext +import jinja2.nodes +import markdown +import yaml + + +# cribbed from https://jinja.palletsprojects.com/en/2.11.x/extensions/#cache. +class MarkdownExtension(jinja2.ext.Extension): + """MarkdownExtension provides {% markdown %} and {% endmarkdown %}.""" + # a set of names that trigger the extension. + tags = {'markdown'} + + def __init__(self, environment): + super(MarkdownExtension, self).__init__(environment) + + # add the defaults to the environment + environment.extend(markdown=markdown.Markdown()) + + def parse(self, parser): + # the first token is the token that started the tag. + # we only listen to `markdown`, so it will always be that. + # we keep the line number for the nodes we create later on. + lineno = next(parser.stream).lineno + + # parse the body of the `{% markdown %}` up to `{% endmarkdown %}`. + body = parser.parse_statements(['name:endmarkdown'], drop_needle=True) + + # return a `CallBlock` node that calls self._markdown_support. + return jinja2.nodes.CallBlock( + self.call_method('_markdown_support'), [], [], body).set_lineno(lineno) + + def _markdown_support(self, caller): + """Helper callback.""" + block = caller() + block = textwrap.dedent(block) + return self.environment.markdown.convert(block) + + +class Thrust: + """Thrust is a document templater.""" + + def __init__(self, template: str, data: Dict[str, Any]): + self.template = template + self.data = data + + @classmethod + def from_path(cls, path: str) -> 'Thrust': + with open(path) as f: + return cls.parse(f.read()) + + @classmethod + def parse(cls, src: str) -> 'Thrust': + """Parse a source file to maybe return a valid Thrust object.""" + lines = src.split('\n') + + parts = [] + for line in lines: + if line == '---': + parts.append([]) + else: + parts[-1].append(line) + if len(parts) != 2: + raise ValueError( + f'file needs 2 "---" deliniated parts, found {len(parts)}') + + data = yaml.safe_load('\n'.join(parts[0]).replace('\t', ' ')) + if data is None: + data = {} + + template = '\n'.join(parts[1]) + return cls(template, data) + + @property + def _environment(self) -> jinja2.Environment: + environment = jinja2.Environment( + loader=jinja2.FileSystemLoader('.'), + extensions=[MarkdownExtension], + trim_blocks=True, + ) + environment.markdown = markdown.Markdown( + extensions=['attr_list', 'extra', 'toc']) + + def loadthrust_filter(path: str) -> str: + return Thrust.from_path(path) + environment.filters['loadthrust'] = loadthrust_filter + + def markdown_filter(src: str) -> str: + return environment.markdown.convert(src) + environment.filters['markdown'] = markdown_filter + + def template_filter(tmpl: str) -> str: + return environment.from_string(tmpl).render(self.data) + environment.filters['template'] = template_filter + + def prefix_filter(s: str, prefix: str, first=True) -> str: + if not first: + lines = s.splitlines() + return '\n'.join(lines[0:1] + [f'{prefix}{l}' for l in lines[1:]]) + return '\n'.join([f'{prefix}{l}' for l in s.splitlines()]) + environment.filters['prefix'] = prefix_filter + + return environment + + def render(self) -> str: + """Render the data in the template.""" + return self._environment.from_string(self.template).render(self.data) + + +if __name__ == '__main__': + if len(sys.argv) != 2: + print(f'usage: {sys.argv[0]} <in-path>') + sys.exit(1) + + in_path = sys.argv[1] + + try: + thrust = Thrust.from_path(in_path) + print(thrust.render()) + except ValueError as error: + print(f'{in_path}: {error}', file=sys.stderr) + sys.exit(1) + except jinja2.exceptions.TemplateError as error: + print(error, file=sys.stderr) + sys.exit(1) |