1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4#
5# Copyright 2017, Data61
6# Commonwealth Scientific and Industrial Research Organisation (CSIRO)
7# ABN 41 687 119 230.
8#
9# This software may be distributed and modified according to the terms of
10# the BSD 2-Clause license. Note that NO WARRANTY is provided.
11# See "LICENSE_BSD2.txt" for details.
12#
13# @TAG(DATA61_BSD)
14#
15
16'''Brief wrapper around the jinja2 templating engine.'''
17
18from __future__ import absolute_import, division, print_function, \
19    unicode_literals
20from camkes.internal.seven import cmp, filter, map, zip
21
22from .Context import new_context
23from camkes.templates import TemplateError
24
25import jinja2
26import os
27import platform
28import six
29import sys
30
31# Jinja is setup by default for HTML templating. We tweak the delimiters to
32# make it more suitable for C.
33START_BLOCK = '/*-'
34END_BLOCK = '-*/'
35START_VARIABLE = '/*?'
36END_VARIABLE = '?*/'
37START_COMMENT = '/*#'
38END_COMMENT = '#*/'
39
40
41def get_leaves(d):
42    '''Generator that yields the leaves of a hierarchical dictionary. See usage
43    below.'''
44    assert isinstance(d, dict)
45    for v in d.values():
46        if isinstance(v, dict):
47            # We're at an intermediate node. Yield all leaves below it.
48            for x in get_leaves(v):
49                yield x
50        else:
51            # We're at a leaf node.
52            yield v
53
54
55class FileSystemLoaderWithLog(jinja2.FileSystemLoader):
56
57    def __init__(self, path):
58        super(FileSystemLoaderWithLog, self).__init__(path)
59        self.files = set()
60
61    def get_source(self, environment, template):
62        (source, filename, uptodate) = super(
63            FileSystemLoaderWithLog, self).get_source(environment, template)
64        self.files.add(filename)
65        return (source, filename, uptodate)
66
67
68class Renderer(object):
69    def __init__(self, templates):
70        # This function constructs a Jinja environment for our templates.
71
72        self.loaders = []
73
74        # Source templates.
75        self.loaders.extend(FileSystemLoaderWithLog(os.path.abspath(x)) for x in
76                            templates)
77
78        self.env = jinja2.Environment(
79            loader=jinja2.ChoiceLoader(self.loaders),
80            extensions=["jinja2.ext.do", "jinja2.ext.loopcontrols"],
81            block_start_string=START_BLOCK,
82            block_end_string=END_BLOCK,
83            variable_start_string=START_VARIABLE,
84            variable_end_string=END_VARIABLE,
85            comment_start_string=START_COMMENT,
86            comment_end_string=END_COMMENT,
87            auto_reload=False,
88            undefined=jinja2.StrictUndefined)
89
90    def render(self, me, assembly, template, render_state, state_key, outfile_name,
91               **kwargs):
92        context = new_context(me, assembly, render_state, state_key, outfile_name,
93                              **kwargs)
94
95        t = self.env.get_template(template)
96        try:
97            return t.render(context)
98        except TemplateError:
99            raise
100        except Exception as e:
101            # Catch and re-cast any other exceptions to allow the runner to
102            # handle them as usual and prevent us barfing stack traces when
103            # exceptions aren't our fault.
104            six.reraise(TemplateError, TemplateError('unhandled exception in '
105                                                     'template %s: %s' % (template, e)), sys.exc_info()[2])
106
107    def get_files_used(self):
108        files = set()
109        for x in self.loaders:
110            files |= x.files
111        return files
112