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 os
import logging
import jsonschema

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

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( os.path.dirname(os.path.realpath(__file__)), _SEGMENT_SCHEMA_FILE, "segment configuration schema", logger, as_json=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, val 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) valstring = "\n".join(val) if key in overwrite: replacements[key] = valstring elif placeholders[key] == "above": replacements[key] = valstring + '\n' + replacements[key] elif placeholders[key] == "below": replacements[key] = replacements[key] + '\n' + valstring 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(docstrings).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__) replace_dict = { "@ipubreplace{{{0}}}{{{1}}}".format(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