Coverage for C:\leo.repo\leo-editor\leo\plugins\writers\ipynb.py : 19%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#@+leo-ver=5-thin
2#@+node:ekr.20160412101901.1: * @file ../plugins/writers/ipynb.py
3"""The @auto write code for jupyter (.ipynb) files."""
4import json
5import re
6import sys
7from leo.core import leoGlobals as g
8import leo.plugins.writers.basewriter as basewriter
10#@+others
11#@+node:ekr.20160412101845.2: ** class Export_IPYNB
12class Export_IPYNB(basewriter.BaseWriter):
13 """A class to export outlines to .ipynb files."""
15 def __init__(self, c):
16 """Ctor for Import_IPYNB class."""
17 super().__init__(c)
18 self.c = c
19 # Commander of present outline.
20 self.root = None
21 # The root of the outline.
23 #@+others
24 #@+node:ekr.20160412114852.1: *3* ipy_w.Entries
25 #@+node:ekr.20160412101845.4: *4* ipy_w.export_outline
26 def export_outline(self, root, fn=None):
27 """
28 Entry point for export-jupyter-notebook
29 Export the given .ipynb file.
30 """
31 self.root = root
32 if not fn:
33 fn = self.get_file_name()
34 if not fn:
35 return False
36 try:
37 nb = self.make_notebook()
38 s = self.convert_notebook(nb)
39 except Exception:
40 g.es_exception()
41 return False
42 if not s:
43 return False
44 s = g.toEncodedString(s, encoding='utf-8', reportErrors=True)
45 try:
46 with open(fn, 'wb') as f:
47 f.write(s)
48 g.es_print('wrote: %s' % fn)
49 except IOError:
50 g.es_print('can not open: %s' % fn)
51 return True
52 #@+node:ekr.20160412114239.1: *4* ipy_w.write: @auto entry
53 def write(self, root):
54 """
55 Export_IPYNB: entry point for @auto writes.
56 Signature must match signature of BaseWriter.write().
57 """
58 if not root:
59 g.trace('can not happen: no root')
60 return False
61 self.root = root
62 try:
63 nb = self.make_notebook()
64 s = self.convert_notebook(nb)
65 except Exception:
66 g.es_exception()
67 return False
68 if not s:
69 return False
70 self.put(g.toUnicode(s))
71 return True
72 #@+node:ekr.20180409081735.1: *3* ipy_w.cell_type
73 def cell_type(self, p):
74 """Return the Jupyter cell type of p.b, honoring ancestor directives."""
75 c = self.c
76 language = g.getLanguageAtPosition(c, p)
77 return 'code' if language == 'python' else 'markdown'
78 #@+node:ekr.20180410045324.1: *3* ipy_w.clean_headline
79 def clean_headline(self, s):
80 """
81 Return a cleaned version of a headline.
83 Used to clean section names and the [ ] part of markdown links.
84 """
85 aList = [ch for ch in s if ch in '-: ' or ch.isalnum()]
86 return ''.join(aList).rstrip('-').strip()
87 #@+node:ekr.20180407191227.1: *3* ipy_w.convert_notebook
88 def convert_notebook(self, nb):
89 """Convert the notebook to a string."""
90 # Do *not* catch exceptions here.
91 s = json.dumps(nb,
92 sort_keys=True,
93 indent=4, separators=(',', ': '))
94 return g.toUnicode(s)
95 #@+node:ekr.20160412101845.21: *3* ipy_w.default_metadata
96 def default_metadata(self):
97 """Return the default top-level metadata."""
98 n1, n2 = sys.version_info[0], sys.version_info[1]
99 version = n1
100 long_version = '%s.%s' % (n1, n2)
101 return {
102 "metadata": {
103 "kernelspec": {
104 "display_name": "Python %s" % version,
105 "language": "python",
106 "name": "python%s" % version,
107 },
108 "language_info": {
109 "codemirror_mode": {
110 "name": "ipython",
111 "version": version,
112 },
113 "file_extension": ".py",
114 "mimetype": "text/x-python",
115 "name": "python",
116 "nbconvert_exporter": "python",
117 "pygments_lexer": "ipython3",
118 "version": long_version,
119 }
120 },
121 "nbformat": 4,
122 "nbformat_minor": 0
123 }
124 #@+node:ekr.20180408103729.1: *3* ipy_w.get_file_name
125 def get_file_name(self):
126 """Open a dialog to write a Jupyter (.ipynb) file."""
127 c = self.c
128 fn = g.app.gui.runSaveFileDialog(
129 c,
130 defaultextension=".ipynb",
131 filetypes=[
132 ("Jupyter files", "*.ipynb"),
133 ("All files", "*"),
134 ],
135 title="Export To Jupyter File",
136 )
137 c.bringToFront()
138 return fn
139 #@+node:ekr.20180407193222.1: *3* ipy_w.get_ua
140 def get_ua(self, p, key=None):
141 """Return the ipynb uA. If key is given, return the inner dict."""
142 d = p.v.u.get('ipynb')
143 if not d:
144 return {}
145 if key:
146 return d.get(key)
147 return d
148 #@+node:ekr.20180407191219.1: *3* ipy_w.make_notebook
149 def make_notebook(self):
150 """Create a JSON notebook"""
151 root = self.root
152 nb = self.get_ua(root, key='prefix') or self.default_metadata()
153 # Write the expansion status of the root.
154 meta = nb.get('metadata') or {}
155 meta ['collapsed'] = not root.isExpanded()
156 nb ['metadata'] = meta
157 # Put all the cells.
158 nb ['cells'] = [self.put_body(p) for p in root.subtree()]
159 return nb
160 #@+node:ekr.20180407195341.1: *3* ipy_w.put_body & helpers
161 def put_body(self, p):
162 """Put the body text of p, as an element of dict d."""
163 cell = self.get_ua(p, 'cell') or {}
164 meta = cell.get('metadata') or {}
165 self.update_cell_properties(cell, meta, p)
166 self.update_cell_body(cell, meta, p)
167 # g.printObj(meta, tag='metadata')
168 # g.printObj(cell, tag='cell')
169 return cell
170 #@+node:ekr.20180409120613.1: *4* ipy_w.update_cell_body
171 pat1 = re.compile(r'^.*<[hH]([123456])>(.*)</[hH]([123456])>')
172 pat2 = re.compile(r'^\s*([#]+)')
174 def update_cell_body(self, cell, meta, p):
175 """Create a new body text, depending on kind."""
177 def clean(lines):
178 lines = [z for z in lines if not g.isDirective(z)]
179 s = ''.join(lines).strip() + '\n'
180 return g.splitLines(s)
182 kind = self.cell_type(p)
183 lines = g.splitLines(p.b)
184 level = p.level() - self.root.level()
185 if kind == 'markdown':
186 # Remove all header markup lines.
187 lines = [z for z in lines if
188 not self.pat1.match(z) and not self.pat2.match(z)]
189 lines = clean(lines)
190 # Insert a new header markup line.
191 if level > 0:
192 lines.insert(0, '%s %s\n' % ('#'*level, self.clean_headline(p.h)))
193 else:
194 # Remember the level for the importer.
195 meta ['leo_level'] = level
196 lines = clean(lines)
197 # Remove leading whitespace lines inserted during import.
198 cell ['source'] = lines
199 #@+node:ekr.20180409120454.1: *4* ipy_w.update_cell_properties
200 def update_cell_properties(self, cell, meta, p):
201 """Update cell properties."""
202 # Update the metadata.
203 meta ['leo_headline'] = p.h
204 meta ['collapsed'] = not p.isExpanded()
205 # "cell_type" should not be in the metadata.
206 if meta.get('cell_type'):
207 del meta ['cell_type']
208 cell ['metadata'] = meta
209 # Update required properties.
210 cell ['cell_type'] = kind = self.cell_type(p)
211 if kind == 'code':
212 if cell.get('outputs') is None:
213 cell ['outputs'] = []
214 if cell.get('execution_count') is None:
215 cell ['execution_count'] = 0
216 else:
217 # These properties are invalid!
218 for prop in ('execution_count', 'outputs'):
219 if cell.get(prop) is not None:
220 del cell [prop]
221 return kind
223 #@-others
224#@-others
225writer_dict = {
226 '@auto': ['@auto-jupyter','@auto-ipynb',],
227 'class': Export_IPYNB,
228 'extensions': ['.ipynb',],
229}
230#@@language python
231#@@tabwidth -4
232#@-leo