chrisjsewell / ipypublish
1
#!/usr/bin/env python
2 3
"""
3
create template
4

5
philosophy is only turn stuff on when we want
6

7
http://nbconvert.readthedocs.io/en/latest/customizing.html#Template-structure
8
http://nbconvert.readthedocs.io/en/latest/api/exporters.html#nbconvert.exporters.TemplateExporter
9

10
"""
11 3
from typing import List, Tuple, Union  # noqa: F401
12 3
import re
13 3
import io
14 3
import logging
15 3
import jsonschema
16

17 3
from six import string_types
18

19
# from ipypublish import __version__
20 3
from ipypublish import schema
21 3
from ipypublish.utils import handle_error, read_file_from_directory, get_module_path
22

23 3
logger = logging.getLogger("template")
24

25 3
_SEGMENT_SCHEMA_FILE = "segment.schema.json"
26 3
_SEGMENT_SCHEMA = None
27

28

29 3
def multireplace(string, replacements):
30
    """
31
    Given a string and a replacement map, it returns the replaced string.
32

33
    From https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729
34

35
    :param str string: string to execute replacements on
36
    :param dict replacements: replacement dictionary {find value: replacement}
37
    :rtype: str
38

39
    """
40 3
    if not replacements:
41 0
        return string
42

43
    # Place longer ones first to keep shorter substrings from matching,
44
    # where the longer ones should take place
45
    # For instance given the replacements {'ab': 'AB', 'abc': 'ABC'}
46
    # against the string 'hey abc', it should produce
47
    # 'hey ABC' and not 'hey ABc'
48 3
    substrs = sorted(replacements, key=len, reverse=True)
49

50
    # Create a big OR regex that matches any of the substrings to replace
51 3
    regexp = re.compile("|".join(map(re.escape, substrs)))
52

53
    # For each match, look up the new string in the replacements
54 3
    return regexp.sub(lambda match: replacements[match.group(0)], string)
55

56

57 3
def _output_to_file(content, outpath):
58 0
    if outpath is not None:
59 0
        with io.open(outpath, "w", encoding="utf8") as f:  # TODO use pathlib
60 0
            f.write(content)
61 0
        return
62

63

64 3
def create_template(outline_template, outline_name, segment_datas, outpath=None):
65
    # type: (dict, Tuple[dict]) -> str
66
    """ build a latex jinja template from;
67

68
    - a jinja(2) template outline,
69
      which may contain segment placeholders,
70
    - and json segment files adhering to the segment.schema.json schema
71

72
    if a segment contains the key "overwrite",
73
    then its value should be a list of keys,
74
    such that these key values overwrite any entries before
75

76
    Parameters
77
    ----------
78
    outline_template: str
79
    segment_datas: tuple or dict
80
    outpath:  None or str
81
        if not None, output to path
82

83
    """
84
    # get the placeholders @ipubreplace{above|below}{name}
85 3
    regex = re.compile("\\@ipubreplace\\{([^\\}]+)\\}\\{([^\\}]+)\\}", re.MULTILINE)
86 3
    placeholder_tuple = regex.findall(outline_template)
87

88 3
    if not placeholder_tuple:
89 3
        if segment_datas:
90 0
            handle_error(
91
                "the segment data is provided, "
92
                + "but the outline template contains no placeholders",
93
                KeyError,
94
                logger,
95
            )
96

97 3
        if outpath:
98 0
            _output_to_file(outline_template, outpath)
99 3
        return outline_template
100

101 3
    placeholders = {name: append for append, name in placeholder_tuple}
102
    # TODO validate that placeholders to not exist multiple times,
103
    # with above and below
104

105 3
    replacements = {key: "" for key in placeholders.keys()}
106 3
    docstrings = ["outline: {}".format(outline_name)]
107

108 3
    if segment_datas:
109 3
        docstrings.append("with segments:")
110
        global _SEGMENT_SCHEMA
111 3
        if _SEGMENT_SCHEMA is None:
112
            # lazy segment schema once
113 3
            _SEGMENT_SCHEMA = read_file_from_directory(
114
                get_module_path(schema),
115
                _SEGMENT_SCHEMA_FILE,
116
                "segment configuration schema",
117
                logger,
118
                interp_ext=True,
119
            )
120

121 3
    for seg_num, segment_data in enumerate(segment_datas):
122

123
        # validate segment
124 3
        try:
125 3
            jsonschema.validate(segment_data, _SEGMENT_SCHEMA)
126 0
        except jsonschema.ValidationError as err:
127 0
            handle_error(
128
                "validation of template segment {} failed: {}".format(
129
                    seg_num, err.message
130
                ),
131
                jsonschema.ValidationError,
132
                logger=logger,
133
            )
134

135
        # get description of segment
136 3
        docstrings.append(
137
            "- {0}: {1}".format(segment_data["identifier"], segment_data["description"])
138
        )
139

140
        # find what key to overwrite
141 3
        overwrite = segment_data.get("overwrite", [])
142 3
        logger.debug("overwrite keys: {}".format(overwrite))
143

144 3
        for key, segtext in segment_data.get("segments").items():
145

146 3
            if key not in placeholders:
147 0
                handle_error(
148
                    "the segment key '{}' ".format(key)
149
                    + "is not contained in the outline template",
150
                    KeyError,
151
                    logger,
152
                )
153

154 3
            if not isinstance(segtext, string_types):
155 3
                segtext = "\n".join(segtext)
156 3
            if key in overwrite:
157 3
                replacements[key] = segtext
158 3
            elif placeholders[key] == "above":
159 3
                replacements[key] = segtext + "\n" + replacements[key]
160 3
            elif placeholders[key] == "below":
161 3
                replacements[key] = replacements[key] + "\n" + segtext
162
            else:
163 0
                handle_error(
164
                    (
165
                        "the placeholder @ipubreplace{{{0}}}{{{1}}} ".format(
166
                            key, placeholders[key]
167
                        )
168
                        + "should specify 'above' or 'below' appending"
169
                    ),
170
                    jsonschema.ValidationError,
171
                    logger=logger,
172
                )
173

174 3
    if "meta_docstring" in placeholders:
175 3
        docstring = "\n".join([s for s in docstrings if s]).replace("'", '"')
176 3
        replacements["meta_docstring"] = docstring
177 3
    if "ipypub_version" in placeholders:
178
        # TODO add option to include ipypub version in output file
179
        # not included by default,
180
        # since tests need to be changed to ignore version number
181 3
        replacements["ipypub_version"] = ""  # str(__version__)
182

183 3
    prefix = "@ipubreplace{"
184 3
    replace_dict = {
185
        prefix + append + "}{" + name + "}": replacements.get(name, "")
186
        for append, name in placeholder_tuple
187
    }
188 3
    outline = multireplace(outline_template, replace_dict)
189

190 3
    if outpath:
191 0
        _output_to_file(outline, outpath)
192

193 3
    return outline

Read our documentation on viewing source code .

Loading