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

124 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

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 # Commander of present outline. 

19 self.root = None # The root of the outline. 

20 

21 #@+others 

22 #@+node:ekr.20160412114852.1: *3* ipy_w.Entries 

23 #@+node:ekr.20160412101845.4: *4* ipy_w.export_outline 

24 def export_outline(self, root, fn=None): 

25 """ 

26 Entry point for export-jupyter-notebook 

27 Export the given .ipynb file. 

28 """ 

29 self.root = root 

30 if not fn: 

31 fn = self.get_file_name() 

32 if not fn: 

33 return False 

34 try: 

35 nb = self.make_notebook() 

36 s = self.convert_notebook(nb) 

37 except Exception: 

38 g.es_exception() 

39 return False 

40 if not s: 

41 return False 

42 s = g.toEncodedString(s, encoding='utf-8', reportErrors=True) 

43 try: 

44 with open(fn, 'wb') as f: 

45 f.write(s) 

46 g.es_print('wrote: %s' % fn) 

47 except IOError: 

48 g.es_print('can not open: %s' % fn) 

49 return True 

50 #@+node:ekr.20160412114239.1: *4* ipy_w.write: @auto entry 

51 def write(self, root): 

52 """ 

53 Export_IPYNB: entry point for @auto writes. 

54 Signature must match signature of BaseWriter.write(). 

55 """ 

56 if not root: 

57 g.trace('can not happen: no root') 

58 return False 

59 self.root = root 

60 try: 

61 nb = self.make_notebook() 

62 s = self.convert_notebook(nb) 

63 except Exception: 

64 g.es_exception() 

65 return False 

66 if not s: 

67 return False 

68 self.put(g.toUnicode(s)) 

69 return True 

70 #@+node:ekr.20180409081735.1: *3* ipy_w.cell_type 

71 def cell_type(self, p): 

72 """Return the Jupyter cell type of p.b, honoring ancestor directives.""" 

73 c = self.c 

74 language = g.getLanguageAtPosition(c, p) 

75 return 'code' if language == 'python' else 'markdown' 

76 #@+node:ekr.20180410045324.1: *3* ipy_w.clean_headline 

77 def clean_headline(self, s): 

78 """ 

79 Return a cleaned version of a headline. 

80 

81 Used to clean section names and the [ ] part of markdown links. 

82 """ 

83 aList = [ch for ch in s if ch in '-: ' or ch.isalnum()] 

84 return ''.join(aList).rstrip('-').strip() 

85 #@+node:ekr.20180407191227.1: *3* ipy_w.convert_notebook 

86 def convert_notebook(self, nb): 

87 """Convert the notebook to a string.""" 

88 # Do *not* catch exceptions here. 

89 s = json.dumps(nb, 

90 sort_keys=True, 

91 indent=4, separators=(',', ': ')) 

92 return g.toUnicode(s) 

93 #@+node:ekr.20160412101845.21: *3* ipy_w.default_metadata 

94 def default_metadata(self): 

95 """Return the default top-level metadata.""" 

96 n1, n2 = sys.version_info[0], sys.version_info[1] 

97 version = n1 

98 long_version = '%s.%s' % (n1, n2) 

99 return { 

100 "metadata": { 

101 "kernelspec": { 

102 "display_name": "Python %s" % version, 

103 "language": "python", 

104 "name": "python%s" % version, 

105 }, 

106 "language_info": { 

107 "codemirror_mode": { 

108 "name": "ipython", 

109 "version": version, 

110 }, 

111 "file_extension": ".py", 

112 "mimetype": "text/x-python", 

113 "name": "python", 

114 "nbconvert_exporter": "python", 

115 "pygments_lexer": "ipython3", 

116 "version": long_version, 

117 } 

118 }, 

119 "nbformat": 4, 

120 "nbformat_minor": 0 

121 } 

122 #@+node:ekr.20180408103729.1: *3* ipy_w.get_file_name 

123 def get_file_name(self): 

124 """Open a dialog to write a Jupyter (.ipynb) file.""" 

125 c = self.c 

126 fn = g.app.gui.runSaveFileDialog( 

127 c, 

128 defaultextension=".ipynb", 

129 filetypes=[ 

130 ("Jupyter files", "*.ipynb"), 

131 ("All files", "*"), 

132 ], 

133 title="Export To Jupyter File", 

134 ) 

135 c.bringToFront() 

136 return fn 

137 #@+node:ekr.20180407193222.1: *3* ipy_w.get_ua 

138 def get_ua(self, p, key=None): 

139 """Return the ipynb uA. If key is given, return the inner dict.""" 

140 d = p.v.u.get('ipynb') 

141 if not d: 

142 return {} 

143 if key: 

144 return d.get(key) 

145 return d 

146 #@+node:ekr.20180407191219.1: *3* ipy_w.make_notebook 

147 def make_notebook(self): 

148 """Create a JSON notebook""" 

149 root = self.root 

150 nb = self.get_ua(root, key='prefix') or self.default_metadata() 

151 # Write the expansion status of the root. 

152 meta = nb.get('metadata') or {} 

153 meta['collapsed'] = not root.isExpanded() 

154 nb['metadata'] = meta 

155 # Put all the cells. 

156 nb['cells'] = [self.put_body(p) for p in root.subtree()] 

157 return nb 

158 #@+node:ekr.20180407195341.1: *3* ipy_w.put_body & helpers 

159 def put_body(self, p): 

160 """Put the body text of p, as an element of dict d.""" 

161 cell = self.get_ua(p, 'cell') or {} 

162 meta = cell.get('metadata') or {} 

163 self.update_cell_properties(cell, meta, p) 

164 self.update_cell_body(cell, meta, p) 

165 # g.printObj(meta, tag='metadata') 

166 # g.printObj(cell, tag='cell') 

167 return cell 

168 #@+node:ekr.20180409120613.1: *4* ipy_w.update_cell_body 

169 pat1 = re.compile(r'^.*<[hH]([123456])>(.*)</[hH]([123456])>') 

170 pat2 = re.compile(r'^\s*([#]+)') 

171 

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

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

174 

175 def clean(lines): 

176 lines = [z for z in lines if not g.isDirective(z)] 

177 s = ''.join(lines).strip() + '\n' 

178 return g.splitLines(s) 

179 

180 kind = self.cell_type(p) 

181 lines = g.splitLines(p.b) 

182 level = p.level() - self.root.level() 

183 if kind == 'markdown': 

184 # Remove all header markup lines. 

185 lines = [z for z in lines if 

186 not self.pat1.match(z) and not self.pat2.match(z)] 

187 lines = clean(lines) 

188 # Insert a new header markup line. 

189 if level > 0: 

190 lines.insert(0, '%s %s\n' % ('#' * level, self.clean_headline(p.h))) 

191 else: 

192 # Remember the level for the importer. 

193 meta['leo_level'] = level 

194 lines = clean(lines) 

195 # Remove leading whitespace lines inserted during import. 

196 cell['source'] = lines 

197 #@+node:ekr.20180409120454.1: *4* ipy_w.update_cell_properties 

198 def update_cell_properties(self, cell, meta, p): 

199 """Update cell properties.""" 

200 # Update the metadata. 

201 meta['leo_headline'] = p.h 

202 meta['collapsed'] = not p.isExpanded() 

203 # "cell_type" should not be in the metadata. 

204 if meta.get('cell_type'): 

205 del meta['cell_type'] 

206 cell['metadata'] = meta 

207 # Update required properties. 

208 cell['cell_type'] = kind = self.cell_type(p) 

209 if kind == 'code': 

210 if cell.get('outputs') is None: 

211 cell['outputs'] = [] 

212 if cell.get('execution_count') is None: 

213 cell['execution_count'] = 0 

214 else: 

215 # These properties are invalid! 

216 for prop in ('execution_count', 'outputs'): 

217 if cell.get(prop) is not None: 

218 del cell[prop] 

219 return kind 

220 

221 #@-others 

222#@-others 

223writer_dict = { 

224 '@auto': ['@auto-jupyter', '@auto-ipynb',], 

225 'class': Export_IPYNB, 

226 'extensions': ['.ipynb',], 

227} 

228#@@language python 

229#@@tabwidth -4 

230#@-leo