Hide keyboard shortcuts

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 

9 

10#@+others 

11#@+node:ekr.20160412101845.2: ** class Export_IPYNB 

12class Export_IPYNB(basewriter.BaseWriter): 

13 """A class to export outlines to .ipynb files.""" 

14 

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. 

22 

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. 

82 

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*([#]+)') 

173 

174 def update_cell_body(self, cell, meta, p): 

175 """Create a new body text, depending on kind.""" 

176 

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) 

181 

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 

222 

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