Source code for ipypublish.port_api.plugin_to_json

""" a module to convert between the old (Python script) plugin format,
and the new (JSON) one
from typing import Dict, Tuple  # noqa: F401
import ast
import json

[docs]def assess_syntax(path): with open(path) as file_obj: content = syntax_tree = ast.parse(content) docstring = "" # docstring = ast.get_docstring(syntaxTree) unknowns = [] imported = {} assignments = {} for i, child in enumerate(ast.iter_child_nodes(syntax_tree)): if (i == 0 and isinstance(child, ast.Expr) and isinstance(child.value, ast.Str)): docstring = child.value.s elif isinstance(child, ast.ImportFrom): module = child.module for n in child.names: importPath = module + "." + imported[ if n.asname is None else n.asname] = importPath elif isinstance(child, ast.Assign): targets = child.targets if len(targets) > 1: raise IOError( "cannot handle expansion assignments " "(e.g. `a, b = [1, 2]`)") target = child.targets[0] # type: ast.Name assignments[] = child.value else: unknowns.append(child) if unknowns: print("Warning this script can only handle 'ImportFrom' and 'Assign' " "syntax, found additional items: {}".format(unknowns)) return docstring, imported, assignments
[docs]def ast_to_json(item, imported, assignments): """recursively convert ast items to json friendly values""" value = None if item in ['True', 'False', 'None']: # python 2.7 value = {'True': True, 'False': False, 'None': None}[item] elif hasattr(ast, "NameConstant") and isinstance(item, ast.NameConstant): value = item.value elif isinstance(item, ast.Str): value = item.s elif isinstance(item, ast.Num): value = item.n elif isinstance(item, ast.Name): if in imported: value = imported[] elif in assignments: value = ast_to_json(assignments[], imported, assignments) elif in ['True', 'False', 'None']: # python 2.7 value = {'True': True, 'False': False, 'None': None}[] else: raise ValueError( "could not find assignment '{}' in config".format( elif isinstance(item, (ast.List, ast.Tuple, ast.Set)): value = [ast_to_json(i, imported, assignments) for i in item.elts] elif isinstance(item, ast.Dict): value = convert_dict(item, imported, assignments) else: raise ValueError("could not handle ast item: {}".format(item)) return value
[docs]def convert_dict(dct, imported, assignments): # type: (ast.Dict, Dict[str, str], dict) -> dict """recurse through and replace keys""" outDict = {} for key, val in zip(dct.keys, dct.values): if not isinstance(key, ast.Str): raise ValueError( "expected key to be a Str; {}".format(key)) outDict[key.s] = ast_to_json(val, imported, assignments) return outDict
[docs]def convert_oformat(oformat): if oformat == "Notebook": outline = None # TODO do notebooks need template (they have currently) exporter = 'nbconvert.exporters.NotebookExporter' elif oformat == "Latex": exporter = 'nbconvert.exporters.LatexExporter' outline = { "module": "ipypublish.templates.outline_schemas", "file": "latex_tplx_schema.json" } elif oformat == "HTML": exporter = 'nbconvert.exporters.HTMLExporter' outline = { "module": "ipypublish.templates.outline_schemas", "file": "html_tpl_schema.json" } elif oformat == "Slides": exporter = 'nbconvert.exporters.SlidesExporter' outline = { "module": "ipypublish.templates.outline_schemas", "file": "html_tpl_schema.json" } else: raise ValueError("expected oformat to be: " "'Notebook', 'Latex', 'HTML' or 'Slides'") return exporter, outline
[docs]def convert_config(config, exporter_class, allow_other): # type: (dict, str) -> dict """convert config into required exporter format""" filters = {} preprocs = {} other = {} # first parse for key, val in config.items(): # TODO Exporter.filters and TemplateExporter.filters always the same? if key in ["Exporter.filters", "TemplateExporter.filters"]: filters.update(config[key]) if key in ["Exporter.preprocessors", "TemplateExporter.preprocessors"]: if preprocs: raise ValueError( "'config' contains both Exporter.preprocessors and " "TemplateExporter.preprocessors") for p in val: pname = p.split(".")[-1] preprocs[pname] = {"class": p, "args": {}} # TODO move these special cases to seperate input/function if pname in ["LatexDocLinks", "LatexDocHTML"]: preprocs[pname]["args"]["metapath"] = "${meta_path}" preprocs[pname]["args"]["filesfolder"] = "${files_path}" # second parse for key, val in config.items(): if key in ["Exporter.filters", "TemplateExporter.filters", "Exporter.preprocessors", "TemplateExporter.preprocessors"]: continue if key.split(".")[0] in preprocs: preprocs[key.split(".")[0]]["args"][".".join( key.split(".")[1:])] = val else: other[key] = val if other and not allow_other: print("Warning: ignoring other args: {}".format(other)) other = {} output = { "class": exporter_class, "filters": filters, "preprocessors": list(preprocs.values()), "other_args": other } return output
[docs]def replace_template_path(path): """ replace original template path with new dict """ segments = path.split(".") module = ".".join(segments[0:-1]) name = segments[-1] if module == "ipypublish.html.ipypublish": return { "module": "ipypublish.templates.segments", "file": "ipy-{0}.html-tplx.json".format(name) } elif module == "ipypublish.html.standard": return { "module": "ipypublish.templates.segments", "file": "std-{0}.html-tplx.json".format(name) } elif module == "ipypublish.latex.standard": return { "module": "ipypublish.templates.segments", "file": "std-{0}.latex-tpl.json".format(name) } elif module == "ipypublish.latex.ipypublish": return { "module": "ipypublish.templates.segments", "file": "ipy-{0}.latex-tpl.json".format(name) } else: print("Warning: unknown template path: {}".format(path)) return { "module": module, "file": "{0}.json".format(name) }
[docs]def create_json(docstring, imported, assignments, allow_other=True): # type: (str, Dict[str, str], dict, bool) -> dict """Set docstring here. Parameters ---------- docstring: str the doc string of the module imported: dict imported classes assignments: dict assigned values (i.e. 'a = b') allow_other: bool whether to allow arguments in config, which do not relate to preprocessors Returns ------- """ oformat = None config = None template = None for value, expr in assignments.items(): if value == 'oformat': if not isinstance(expr, ast.Str): raise ValueError( "expected 'oformat' to be a Str; {}".format(expr)) oformat = expr.s elif value == "config": if not isinstance(expr, ast.Dict): raise ValueError( "expected 'config' to be a Dict; {}".format(expr)) config = convert_dict(expr, imported, assignments) elif value == "template": if not isinstance(expr, ast.Call): raise ValueError( "expected 'config' to be a call to create_tpl(x)") # func = expr.func # TODO make sure func name is create_tpl/tplx args = expr.args keywords = expr.keywords if len(args) != 1 or len(keywords) > 0: raise ValueError("expected create_tpl(x) to have one argument") segList = args[0] if isinstance(segList, ast.ListComp): segList = segList.generators[0].iter if not isinstance(segList, ast.List): raise ValueError( "expected create_tpl(x) arg to be a List; {}".format( segList)) segments = [] for seg in segList.elts: if isinstance(seg, ast.Attribute): segName = elif isinstance(seg, ast.Name): segName = else: raise ValueError( "expected seg in template to be an Attribute; " + "{1}".format(seg)) if segName not in imported: raise ValueError("segment '{}' not found".format(segName)) segments.append(imported[segName]) template = segments if oformat is None: raise ValueError("could not find 'oformat' assignment") if config is None: raise ValueError("could not find 'config' assignment") if template is None: raise ValueError("could not find 'template' assignment") exporter_class, outline = convert_oformat(oformat) exporter = convert_config(config, exporter_class, allow_other) if any(["biblio_natbib" in s for s in template]): exporter["filters"]["strip_ext"] = ( "ipypublish.filters.filters.strip_ext") return { "description": docstring.splitlines(), "exporter": exporter, "template": None if outline is None else { "outline": outline, "segments": [replace_template_path(s) for s in template] } }
[docs]def convert_to_json(path, outpath=None, ignore_other=False): """Set docstring here. Parameters ---------- path: str input module path outpath=None: str or None if set, output json to this path ignore_other: bool whether to ignore arguments in config, which do not relate to preprocessors Returns ------- """ _docstring, _imported, _assignments = assess_syntax(path) # print(_docstring) # print() # print(_imported) # print() # print(_assignments) output = create_json(_docstring, _imported, _assignments, not ignore_other) if outpath: with open(outpath, "w") as file_obj: json.dump(output, file_obj, indent=2) return json.dumps(output, indent=2)
if __name__ == "__main__": if False: import glob import os for path in glob.glob( "/Users/cjs14/GitHub/ipypublish" "/ipypublish/export_plugins/*.py"): dirname = os.path.dirname(path) name = os.path.splitext(os.path.basename(path))[0] try: convert_to_json(path, os.path.join(dirname, name + ".json"), ignore_other=True) except ValueError as err: print("{0} failed: {1}".format(path, err)) convert_to_json( "/Users/cjs14/GitHub/ipypublish" "/ipypublish_plugins/", "/Users/cjs14/GitHub/ipypublish" "/ipypublish_plugins/example_new_plugin.json")