def __init__(self, template_file, strip_outputs=True, write_outputs=False): """template_file - location of jinja template to use for export strip_outputs - whether to remove output cells from the output """ self.exporter = MarkdownExporter() self.exporter.register_filter('string2json', self.string2json) self.exporter.register_filter('create_input_codeblock', self.create_input_codeblock) self.exporter.register_filter('create_output_codeblock', self.create_output_codeblock) self.exporter.register_filter('create_output_block', self.create_output_block) self.exporter.register_filter('create_attributes', self.create_attributes) self.exporter.register_filter('dequote', self.dequote) self.exporter.register_filter('data2uri', self.data2uri) self.load_template(template_file) self.strip_outputs = strip_outputs self.write_outputs = write_outputs self.output_dir = './figures/'
class MarkdownWriter(NotebookWriter): """Write a notebook into markdown.""" def __init__(self, template_file, strip_outputs=True, write_outputs=False): """template_file - location of jinja template to use for export strip_outputs - whether to remove output cells from the output """ self.exporter = MarkdownExporter() self.exporter.register_filter('string2json', self.string2json) self.exporter.register_filter('create_input_codeblock', self.create_input_codeblock) self.exporter.register_filter('create_output_codeblock', self.create_output_codeblock) self.exporter.register_filter('create_output_block', self.create_output_block) self.exporter.register_filter('create_attributes', self.create_attributes) self.exporter.register_filter('dequote', self.dequote) self.exporter.register_filter('data2uri', self.data2uri) self.load_template(template_file) self.strip_outputs = strip_outputs self.write_outputs = write_outputs self.output_dir = './figures/' def load_template(self, template_file): """IPython cannot load a template from an absolute path. If we want to include templates in our package they will be placed on an absolute path. Here we create a temporary file on a relative path and read from there after copying the template to it. """ tmp = tempfile.NamedTemporaryFile(dir='./', mode='w+') tmp_path = os.path.relpath(tmp.name) with io.open(template_file, encoding='utf-8') as orig: tmp.file.write(orig.read()) tmp.file.flush() self.exporter.template_file = tmp_path self.exporter._load_template() tmp.close() def write_from_json(self, notebook_json): notebook = v4.reads_json(notebook_json) return self.write(notebook) def writes(self, notebook): body, resources = self.exporter.from_notebook_node(notebook) self.resources = resources if self.write_outputs: self.write_resources(resources) # remove any blank lines added at start and end by template text = re.sub(r'\A\s*\n|^\s*\Z', '', body) if not py3compat.PY3 and not isinstance(text, unicode_type): # this branch is likely only taken for JSON on Python 2 text = py3compat.str_to_unicode(text) return text def write_resources(self, resources): """Write the output data in resources returned by exporter to files. """ for filename, data in list(resources.get('outputs', {}).items()): # Determine where to write the file to dest = os.path.join(self.output_dir, filename) path = os.path.dirname(dest) if path and not os.path.isdir(path): os.makedirs(path) # Write file with open(dest, 'wb') as f: f.write(data) # --- filter functions to be used in the output template --- # def string2json(self, string): """Convert json into its string representation. Used for writing outputs to markdown.""" kwargs = { 'cls': BytesEncoder, # use the IPython bytes encoder 'indent': 1, 'sort_keys': True, 'separators': (',', ': '), } return py3compat.str_to_unicode(json.dumps(string, **kwargs), 'utf-8') def create_input_codeblock(self, cell): codeblock = ('{fence}{attributes}\n' '{cell.source}\n' '{fence}') attrs = self.create_attributes(cell, cell_type='input') return codeblock.format(attributes=attrs, fence='```', cell=cell) def create_output_block(self, cell): if self.strip_outputs: return '' else: return self.create_output_codeblock(cell) def create_output_codeblock(self, cell): codeblock = ('{fence}{{.json .output n={execution_count}}}\n' '{contents}\n' '{fence}') return codeblock.format(fence='```', execution_count=cell.execution_count, contents=self.string2json(cell.outputs)) def create_attributes(self, cell, cell_type=None): """Turn the attribute dict into an attribute string for the code block. """ if self.strip_outputs or not hasattr(cell, 'execution_count'): return 'python' attrs = cell.metadata.get('attributes') attr = PandocAttributes(attrs, 'dict') if 'python' in attr.classes: attr.classes.remove('python') if 'input' in attr.classes: attr.classes.remove('input') if cell_type == 'figure': attr.kvs.pop('caption', '') attr.classes.append('figure') attr.classes.append('output') return attr.to_html() elif cell_type == 'input': # ensure python goes first so that github highlights it attr.classes.insert(0, 'python') attr.classes.insert(1, 'input') if cell.execution_count: attr.kvs['n'] = cell.execution_count return attr.to_markdown(format='{classes} {id} {kvs}') else: return attr.to_markdown() @staticmethod def dequote(s): """Remove excess quotes from a string.""" if len(s) < 2: return s elif (s[0] == s[-1]) and s.startswith(('"', "'")): return s[1: -1] else: return s @staticmethod def data2uri(data, data_type): """Convert base64 data into a data uri with the given data_type.""" MIME_MAP = { 'image/jpeg': 'jpeg', 'image/png': 'png', 'text/plain': 'text', 'text/html': 'html', 'text/latex': 'latex', 'application/javascript': 'html', 'image/svg+xml': 'svg', } inverse_map = {v: k for k, v in list(MIME_MAP.items())} mime_type = inverse_map[data_type] uri = r"data:{mime};base64,{data}" return uri.format(mime=mime_type, data=data[mime_type].replace('\n', ''))