Source code for ipypublish.templates.create_template

#!/usr/bin/env python
"""
create template

philosophy is only turn stuff on when we want

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

"""
from typing import List, Tuple, Union  # noqa: F401
import re
import io
import logging
import jsonschema

from six import string_types

# from ipypublish import __version__
from ipypublish import schema
from ipypublish.utils import handle_error, read_file_from_directory, get_module_path

logger = logging.getLogger("template")

_SEGMENT_SCHEMA_FILE = "segment.schema.json"
_SEGMENT_SCHEMA = None


[docs]def multireplace(string, replacements): """ Given a string and a replacement map, it returns the replaced string. From https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729 :param str string: string to execute replacements on :param dict replacements: replacement dictionary {find value: replacement} :rtype: str """ if not replacements: return string # Place longer ones first to keep shorter substrings from matching, # where the longer ones should take place # For instance given the replacements {'ab': 'AB', 'abc': 'ABC'} # against the string 'hey abc', it should produce # 'hey ABC' and not 'hey ABc' substrs = sorted(replacements, key=len, reverse=True) # Create a big OR regex that matches any of the substrings to replace regexp = re.compile("|".join(map(re.escape, substrs))) # For each match, look up the new string in the replacements return regexp.sub(lambda match: replacements[match.group(0)], string)
def _output_to_file(content, outpath): if outpath is not None: with io.open(outpath, "w", encoding="utf8") as f: # TODO use pathlib f.write(content) return
[docs]def create_template(outline_template, outline_name, segment_datas, outpath=None): # type: (dict, Tuple[dict]) -> str """ build a latex jinja template from; - a jinja(2) template outline, which may contain segment placeholders, - and json segment files adhering to the segment.schema.json schema if a segment contains the key "overwrite", then its value should be a list of keys, such that these key values overwrite any entries before Parameters ---------- outline_template: str segment_datas: tuple or dict outpath: None or str if not None, output to path """ # get the placeholders @ipubreplace{above|below}{name} regex = re.compile("\\@ipubreplace\\{([^\\}]+)\\}\\{([^\\}]+)\\}", re.MULTILINE) placeholder_tuple = regex.findall(outline_template) if not placeholder_tuple: if segment_datas: handle_error( "the segment data is provided, " + "but the outline template contains no placeholders", KeyError, logger, ) if outpath: _output_to_file(outline_template, outpath) return outline_template placeholders = {name: append for append, name in placeholder_tuple} # TODO validate that placeholders to not exist multiple times, # with above and below replacements = {key: "" for key in placeholders.keys()} docstrings = ["outline: {}".format(outline_name)] if segment_datas: docstrings.append("with segments:") global _SEGMENT_SCHEMA if _SEGMENT_SCHEMA is None: # lazy segment schema once _SEGMENT_SCHEMA = read_file_from_directory( get_module_path(schema), _SEGMENT_SCHEMA_FILE, "segment configuration schema", logger, interp_ext=True, ) for seg_num, segment_data in enumerate(segment_datas): # validate segment try: jsonschema.validate(segment_data, _SEGMENT_SCHEMA) except jsonschema.ValidationError as err: handle_error( "validation of template segment {} failed: {}".format( seg_num, err.message ), jsonschema.ValidationError, logger=logger, ) # get description of segment docstrings.append( "- {0}: {1}".format(segment_data["identifier"], segment_data["description"]) ) # find what key to overwrite overwrite = segment_data.get("overwrite", []) logger.debug("overwrite keys: {}".format(overwrite)) for key, segtext in segment_data.get("segments").items(): if key not in placeholders: handle_error( "the segment key '{}' ".format(key) + "is not contained in the outline template", KeyError, logger, ) if not isinstance(segtext, string_types): segtext = "\n".join(segtext) if key in overwrite: replacements[key] = segtext elif placeholders[key] == "above": replacements[key] = segtext + "\n" + replacements[key] elif placeholders[key] == "below": replacements[key] = replacements[key] + "\n" + segtext else: handle_error( ( "the placeholder @ipubreplace{{{0}}}{{{1}}} ".format( key, placeholders[key] ) + "should specify 'above' or 'below' appending" ), jsonschema.ValidationError, logger=logger, ) if "meta_docstring" in placeholders: docstring = "\n".join([s for s in docstrings if s]).replace("'", '"') replacements["meta_docstring"] = docstring if "ipypub_version" in placeholders: # TODO add option to include ipypub version in output file # not included by default, # since tests need to be changed to ignore version number replacements["ipypub_version"] = "" # str(__version__) prefix = "@ipubreplace{" replace_dict = { prefix + append + "}{" + name + "}": replacements.get(name, "") for append, name in placeholder_tuple } outline = multireplace(outline_template, replace_dict) if outpath: _output_to_file(outline, outpath) return outline