154 lines
4.3 KiB
Python
154 lines
4.3 KiB
Python
import sys
|
|
import pathlib
|
|
from urllib.parse import urlparse
|
|
|
|
import kdl
|
|
import click
|
|
from bs4 import BeautifulSoup
|
|
from bs4.element import Tag
|
|
from jinja2 import Environment
|
|
|
|
class CuddlyMaybeList(object):
|
|
"""
|
|
If the given list is only one item behave like a proxy to that item.
|
|
"""
|
|
def __init__(self, inner):
|
|
self.inner = inner
|
|
|
|
def __getattr__(self, name):
|
|
if len(self.inner) == 1:
|
|
return getattr(self.inner[0], name)
|
|
else:
|
|
return getattr(self.inner, name)
|
|
|
|
def __iter__(self):
|
|
return iter(self.inner)
|
|
|
|
def __len__(self):
|
|
return len(self.inner)
|
|
|
|
def __str__(self):
|
|
if len(self.inner) == 1:
|
|
return str(self.inner[0])
|
|
else:
|
|
return str(self.inner)
|
|
|
|
def __getitem__(self, key):
|
|
return self.inner[key]
|
|
|
|
class CuddlyNode(object):
|
|
"""
|
|
Common interface to Document and Node.
|
|
Provide helper method to render to HTMl.
|
|
Allow access to children nodes like object properties.
|
|
"""
|
|
def __init__(self, inner):
|
|
self.inner = inner
|
|
self.children = {}
|
|
for node in self.inner.nodes:
|
|
self.children.setdefault(node.name, [])
|
|
self.children[node.name].append(CuddlyNode(node))
|
|
|
|
self.children = {name: CuddlyMaybeList(nodes) for name, nodes in self.children.items()}
|
|
|
|
@property
|
|
def value(self):
|
|
if args := getattr(self.inner, 'args', None):
|
|
return args[0]
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def values(self):
|
|
return getattr(self.inner, 'args', [])
|
|
|
|
@property
|
|
def attributes(self):
|
|
return getattr(self.inner, 'props', {})
|
|
|
|
@property
|
|
def name(self):
|
|
return getattr(self.inner, 'name', None)
|
|
|
|
def __getattr__(self, name):
|
|
children = self.children[name]
|
|
return children
|
|
|
|
def __str__(self):
|
|
return str(self.value)
|
|
|
|
def render(self, template):
|
|
variables = {
|
|
'values': self.values,
|
|
'value': self.value,
|
|
'children': self.children,
|
|
'attributes': self.attributes,
|
|
}
|
|
|
|
variables.update(self.children)
|
|
|
|
return template.render(variables)
|
|
|
|
|
|
class RenderNode(object):
|
|
def __init__(self, components, env):
|
|
self.components = components
|
|
self.env = env
|
|
|
|
def __call__(self, node):
|
|
if isinstance(node, CuddlyMaybeList) and len(node) > 1:
|
|
raise Exception(f'Render filter must called be on single node, not list, got {type(node)}')
|
|
|
|
if node.name in self.components:
|
|
template = self.env.from_string(self.components[node.name])
|
|
return node.render(template)
|
|
else:
|
|
raise Exception(f'No template to render node type `{node.name}`')
|
|
|
|
|
|
def stripscheme(url):
|
|
parsed_url = urlparse(url)
|
|
scheme_len = len(parsed_url.scheme) + 1
|
|
|
|
if parsed_url.netloc:
|
|
scheme_len += 2
|
|
return url[scheme_len:]
|
|
|
|
@click.command()
|
|
@click.option('--template', help="Path to a template file")
|
|
@click.option('--input', help="Path to a KDL file")
|
|
@click.option('--output', help="Output file")
|
|
def main(template, input, output):
|
|
"""
|
|
Template a file using a KDL input
|
|
"""
|
|
template_path = template
|
|
with open(template_path) as template_file:
|
|
template_html = BeautifulSoup(template_file.read(), features="html.parser")
|
|
components = template_html.components.extract()
|
|
components = [element for element in components.children if isinstance(element, Tag)]
|
|
template_string = template_html.decode()
|
|
|
|
components = {
|
|
component.name: component.decode_contents().strip()
|
|
for component in components
|
|
}
|
|
|
|
with open(input) as input_file:
|
|
input_node = CuddlyNode(kdl.parse(input_file.read()))
|
|
|
|
env = Environment()
|
|
env.filters['render'] = RenderNode(components, env)
|
|
env.filters['stripscheme'] = stripscheme
|
|
template = env.from_string(template_string)
|
|
|
|
output_html = input_node.render(template)
|
|
|
|
if output.endswith('.pdf'):
|
|
from weasyprint import HTML, default_url_fetcher
|
|
base_dir = pathlib.Path(template_path).absolute().parent
|
|
HTML(string=output_html, base_url=f'file://{base_dir}/').write_pdf(output, pdf_variant='pdf/ua-1')
|
|
|
|
else:
|
|
with open(output, 'w') as output_file:
|
|
output_file.write(output_html)
|