示例#1
0
def load_export_config(export_config_path):
    """load the export configuration"""
    if isinstance(export_config_path, string_types):
        export_config_path = pathlib.Path(export_config_path)

    data = read_file_from_directory(
        export_config_path.parent,
        export_config_path.name,
        "export configuration",
        logger,
        interp_ext=True,
    )

    # validate against schema
    global _EXPORT_SCHEMA
    if _EXPORT_SCHEMA is None:
        # lazy load schema once
        _EXPORT_SCHEMA = read_file_from_directory(
            get_module_path(schema),
            _EXPORT_SCHEMA_FILE,
            "export configuration schema",
            logger,
            interp_ext=True,
        )
    try:
        jsonschema.validate(data, _EXPORT_SCHEMA)
    except jsonschema.ValidationError as err:
        handle_error(
            "validation of export config {} failed against {}: {}".format(
                export_config_path, _EXPORT_SCHEMA_FILE, err.message),
            jsonschema.ValidationError,
            logger=logger,
        )

    return data
示例#2
0
def make_conf(overwrite=False, **kwargs):
    """ create the contents of a sphinx config.py
    with default values compliant with ipypublish.

    Parameters
    ----------
    overwrite: bool
        if True, default arguments are overwritten by key-word arguments
    kwargs:
        key-word arguments are included as <key> = <val>
        note docstring is a special key, inserted at the top of the file

    Returns
    -------
    conf_content: str

    """

    # load local config.yaml
    path = os.path.join(os.path.dirname(os.path.realpath(__file__)))
    data = read_file_from_directory(path,
                                    "config.yaml",
                                    "sphinx config",
                                    logger,
                                    interp_ext=True)

    conf_str = []

    docstring = None
    if "docstring" in data:
        docstring = data.pop("docstring")

    if "docstring" in kwargs:
        kwdocstring = data.pop("docstring")
        if overwrite or not docstring:
            docstring = kwdocstring

    if docstring:
        conf_str.append('"""')
        conf_str.append(str(docstring))
        conf_str.append('"""')

    for key, val in data.items():
        if key in kwargs and overwrite:
            continue
        conf_str.append("")
        if isinstance(val, string_types):
            val = '"{}"'.format(val)
        conf_str.append("{0} = {1}".format(key, val))

    for key, val in kwargs.items():
        if key in data and not overwrite:
            continue
        conf_str.append("")
        if isinstance(val, string_types):
            val = '"{}"'.format(val)
        conf_str.append("{0} = {1}".format(key, val))

    return "\n".join(conf_str) + "\n"
def load_template(template_key, template_dict):

    if template_dict is None:
        return None

    if "directory" in template_dict["outline"]:
        outline_template = read_file_from_directory(
            template_dict["outline"]["directory"],
            template_dict["outline"]["file"],
            "template outline", logger, interp_ext=False)
        outline_name = os.path.join(template_dict["outline"]["directory"],
                                    template_dict["outline"]["file"])
    else:
        outline_template = read_file_from_module(
            template_dict["outline"]["module"],
            template_dict["outline"]["file"],
            "template outline", logger, interp_ext=False)
        outline_name = os.path.join(template_dict["outline"]["module"],
                                    template_dict["outline"]["file"])

    segments = []
    for snum, segment in enumerate(template_dict.get("segments", [])):

        if "file" not in segment:
            handle_error(
                "'file' expected in segment {}".format(snum),
                KeyError, logger)

        if "directory" in segment:
            seg_data = read_file_from_directory(
                segment["directory"],
                segment["file"], "template segment", logger, interp_ext=True)
        elif "module" in segment:
            seg_data = read_file_from_module(
                segment["module"],
                segment["file"], "template segment", logger, interp_ext=True)
        else:
            handle_error(
                "'directory' or 'module' expected in segment {}".format(snum),
                KeyError, logger)

        segments.append(seg_data)

    template_str = create_template(outline_template, outline_name, segments)

    return str_to_jinja(template_str, template_key)
示例#4
0
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
示例#5
0
    def publish(self, ipynb_path, nb_node=None):
        """ convert one or more Jupyter notebooks to a published format

        paths can be string of an existing file or folder,
        or a pathlib.Path like object

        all files linked in the documents are placed into a single files_folder

        Parameters
        ----------
        ipynb_path: str or pathlib.Path
            notebook file or directory
        nb_node: None or nbformat.NotebookNode
            a pre-converted notebook

        Returns
        --------
        outdata: dict
            containing keys;
            "outpath", "exporter", "stream", "main_filepath", "resources"

        """
        # setup the input and output paths
        if isinstance(ipynb_path, string_types):
            ipynb_path = pathlib.Path(ipynb_path)
        ipynb_name, ipynb_ext = os.path.splitext(ipynb_path.name)
        outdir = (os.path.join(os.getcwd(), "converted")
                  if self.outpath is None else str(self.outpath))

        with self._log_handlers(ipynb_name, outdir):

            if not ipynb_path.exists() and not nb_node:
                handle_error(
                    "the notebook path does not exist: {}".format(ipynb_path),
                    IOError,
                    self.logger,
                )

            # log start of conversion
            self.logger.info("started ipypublish v{0} at {1}".format(
                ipypublish.__version__, time.strftime("%c")))
            self.logger.info("logging to: {}".format(
                os.path.join(outdir, ipynb_name + ".nbpub.log")))
            self.logger.info("running for ipynb(s) at: {0}".format(ipynb_path))
            self.logger.info("with conversion configuration: {0}".format(
                self.conversion))

            if nb_node is None and ipynb_ext in self.pre_conversion_funcs:
                func = self.pre_conversion_funcs[ipynb_ext]
                self.logger.info("running pre-conversion with: {}".format(
                    inspect.getmodule(func)))
                try:
                    nb_node = func(ipynb_path)
                except Exception as err:
                    handle_error(
                        "pre-conversion failed for {}: {}".format(
                            ipynb_path, err),
                        err,
                        self.logger,
                    )

            # doesn't work with folders
            # if (ipynb_ext != ".ipynb" and nb_node is None):
            #     handle_error(
            #         'the file extension is not associated with any '
            #         'pre-converter: {}'.format(ipynb_ext),
            # TypeError, self.logger)

            if nb_node is None:
                # merge all notebooks
                # TODO allow notebooks to remain separate
                # (would require creating a main.tex with the preamble in etc )
                # Could make everything a 'PyProcess',
                # with support for multiple streams
                final_nb, meta_path = merge_notebooks(
                    ipynb_path, ignore_prefix=self.ignore_prefix)
            else:
                final_nb, meta_path = (nb_node, ipynb_path)

            # validate the notebook metadata against the schema
            if self.validate_nb_metadata:
                nb_metadata_schema = read_file_from_directory(
                    get_module_path(schema),
                    "doc_metadata.schema.json",
                    "doc_metadata.schema",
                    self.logger,
                    interp_ext=True,
                )
                try:
                    jsonschema.validate(final_nb.metadata, nb_metadata_schema)
                except jsonschema.ValidationError as err:
                    handle_error(
                        "validation of notebook level metadata failed: {}\n"
                        "see the doc_metadata.schema.json for full spec".
                        format(err.message),
                        jsonschema.ValidationError,
                        logger=self.logger,
                    )

            # set text replacements for export configuration
            replacements = {
                self.meta_path_placeholder:
                str(meta_path),
                self.files_folder_placeholder:
                "{}{}".format(get_valid_filename(ipynb_name),
                              self.folder_suffix),
            }

            self.logger.debug("notebooks meta path: {}".format(meta_path))

            # load configuration file
            (
                exporter_cls,
                jinja_template,
                econfig,
                pprocs,
                pconfig,
            ) = self._load_config_file(replacements)

            # run nbconvert
            self.logger.info("running nbconvert")
            exporter, stream, resources = self.export_notebook(
                final_nb, exporter_cls, econfig, jinja_template)

            # postprocess results
            main_filepath = os.path.join(outdir,
                                         ipynb_name + exporter.file_extension)

            for post_proc_name in pprocs:
                proc_class = find_entry_point(
                    post_proc_name,
                    "ipypublish.postprocessors",
                    self.logger,
                    "ipypublish",
                )
                proc = proc_class(pconfig)
                stream, main_filepath, resources = proc.postprocess(
                    stream, exporter.output_mimetype, main_filepath, resources)

            self.logger.info("process finished successfully")

        return {
            "outpath": outdir,
            "exporter": exporter,
            "stream": stream,
            "main_filepath": main_filepath,
            "resources": resources,
        }