Coverage for src/pycse/orgmode_v1.py: 0.00%

122 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-23 16:23 -0400

1"""Module to enhance Python for use in org-mode. 

2 

31. redirect stderr to stdout for use in Emacs + orgmode. 

4 

5 org-mode does not capture stderr in code blocks. Here we redirect it so that 

6 the stderr stream shows up in the results section of a code block. 

7 

82. Modify matplotlib.pyplot.savefig and show 

9 

103. Provide table and figure commands that generate org-markup. 

11 

124. Provide functions that generate org markup, e.g. results, comments, 

13headlines and links. 

14 

15# Copyright 2015-2022, John Kitchin 

16# (see accompanying license files for details). 

17 

18""" 

19 

20from hashlib import sha1 

21import io 

22import os 

23import sys 

24import textwrap 

25import matplotlib.pyplot 

26 

27 

28def stderr_to_stdout(): 

29 """Redirect stderr to stdout so it can be captured. 

30 

31 Note: if your python returns an error, you may see nothing from this. 

32 """ 

33 sys.stderr = sys.stdout 

34 

35 

36class print_redirect: 

37 """Context manager for pycse.orgmode. 

38 

39 Most functions print to stdout. Use this to capture it in a file. 

40 with print_redirect(some_file): 

41 org() 

42 print(something) 

43 

44 mode='w' overwrites the file 

45 mode='a' appends to the file 

46 """ 

47 

48 def __init__(self, fname, mode="w"): 

49 """Initialize the decorator.""" 

50 self.fname = fname 

51 self.mode = mode 

52 

53 def __enter__(self): 

54 """Enter the decorator.""" 

55 sys.stdout = open(self.fname, self.mode) 

56 return self 

57 

58 def __exit__(self, exc_type, exc_val, exc_tb): 

59 """Exit the decorator.""" 

60 sys.stdout.close() 

61 sys.stdout = sys.__stdout__ 

62 

63 

64original_savefig = matplotlib.pyplot.savefig 

65 

66 

67# patch to capture savefig 

68def mysave(fname, *args, **kwargs): 

69 """Wrap savefig for org-mode. 

70 

71 Returns an org-mode link to the file. 

72 """ 

73 original_savefig(fname, *args, **kwargs) 

74 return "[[file:{}]]".format(fname) 

75 

76 

77matplotlib.pyplot.savefig = mysave 

78 

79 

80def git_hash(string): 

81 """Return a git hash of string.""" 

82 s = sha1() 

83 g = "blob {0:d}\0".format(len(string)) 

84 s.update(g.encode("utf-8")) 

85 s.update(string) 

86 return s.hexdigest() 

87 

88 

89original_show = matplotlib.pyplot.show 

90 

91SHOW = True 

92 

93 

94def myshow(*args, **kwargs): 

95 """Wrap matplotlib.pyplot.show for orgmode. 

96 

97 Saves the figure in a directory called pyshow with the filename derived 

98 from its git-hash. 

99 

100 """ 

101 sio = io.BytesIO() 

102 original_savefig(sio, format="png") 

103 fig_contents = sio.getvalue() 

104 

105 _hash = git_hash(fig_contents) 

106 

107 if not os.path.isdir("pyshow"): 

108 os.mkdir("pyshow") 

109 

110 png = os.path.join("pyshow", _hash + ".png") 

111 

112 with open(png, "wb") as f: 

113 f.write(fig_contents) 

114 

115 print("[[file:{}]]".format(png)) 

116 

117 if SHOW: 

118 original_show(*args, **kwargs) 

119 

120 

121matplotlib.pyplot.show = myshow 

122 

123 

124# Tables and figures 

125def table(data, name=None, caption=None, attributes=None, none=""): 

126 """Return a formatted table. 

127 

128 :data: A list-like data structure. A None value is converted to hline. 

129 :name: The name of the table 

130 :caption: The caption text 

131 :attributes: [(backend, 'attributes')] 

132 :none: A string for None values 

133 

134 """ 

135 s = [] 

136 

137 if caption is not None: 

138 s += ["#+caption: {}".format(caption)] 

139 

140 if attributes is not None: 

141 for backend, attrs in attributes: 

142 s += ["#+attr_{}: {}".format(backend.lower(), attrs)] 

143 

144 if name is not None: 

145 s += ["#+name: {}".format(name)] 

146 

147 for row in data: 

148 if row is None: 

149 s += ["|-"] 

150 else: 

151 s += ["| " + " | ".join([str(x) if x is not None else none for x in row]) + "|"] 

152 

153 print("\n".join(s)) 

154 

155 

156def figure(fname, caption=None, name=None, attributes=None): 

157 """Return a formatted figure. 

158 

159 :fname: A string for the filename. 

160 :caption: A string of the caption text. 

161 :name: A string for a label. 

162 :attributes: [(backend, 'attributes')] 

163 

164 """ 

165 s = [] 

166 

167 if attributes is not None: 

168 for backend, attrs in attributes: 

169 s += ["#+attr_{}: {}".format(backend.lower(), attrs)] 

170 

171 if name is not None: 

172 s += ["#+name: {}".format(name)] 

173 

174 if caption is not None: 

175 s += ["#+caption: {}".format(caption)] 

176 

177 if fname.startswith("[[file:"): 

178 s += [fname] 

179 else: 

180 if not os.path.exists(fname): 

181 if not os.path.exists(os.path.dirname(fname)): 

182 os.makedirs(os.path.dirname(fname), exist_ok=True) 

183 s += [mysave(fname)] 

184 

185 print("\n".join(s)) 

186 

187 

188def verbatim(s): 

189 """Print s in verbatim. 

190 

191 If s is one line, print it in ==, otherwise use a block. 

192 """ 

193 if "\n" in str(s): 

194 print("\n#+begin_example\n{}\n#+end_example\n".format(s)) 

195 else: 

196 print("={}=".format(s)) 

197 

198 

199def comment(s): 

200 """Print s as a comment. 

201 

202 If s is one line, print it in #, otherwise use a block. 

203 """ 

204 if "\n" in str(s): 

205 print("\n#+begin_comment\n{}\n#+end_comment\n".format(s)) 

206 else: 

207 print(textwrap.fill(s, initial_indent="# ", subsequent_indent="# ", width=79)) 

208 

209 

210def fixed_width(s): 

211 """Print s as a fixed-width element.""" 

212 print("\n".join([": " + x for x in str(s).split("\n")])) 

213 

214 

215def result(s): 

216 """Return a fixed_width string. 

217 

218 An org src block result. 

219 """ 

220 return fixed_width(str(s)) 

221 

222 

223def latex(s): 

224 """Print s as a latex block.""" 

225 print("\n#+begin_latex\n{}\n#+end_latex\n".format(s)) 

226 

227 

228def org(s): 

229 """Print s as it is.""" 

230 print(s) 

231 

232 

233def headline( 

234 title, 

235 level=1, 

236 todo=None, 

237 tags=(), 

238 deadline=None, 

239 scheduled=None, 

240 properties=None, 

241 body=None, 

242): 

243 """Print an org headline. 

244 

245 :title: A string for the headline 

246 

247 :level: an integer for number of stars in the headline 

248 

249 :tags: a list of strings as tags for the headline 

250 :properties: A dictionary of property: value pairs 

251 :body: a string representing the body. 

252 

253 """ 

254 s = "*" * level + " " 

255 if todo is not None: 

256 s += "{} ".format(todo) 

257 

258 s += title 

259 if tags: 

260 s += " :" + ":".join(tags) + ":" 

261 s += "\n" 

262 

263 if scheduled and deadline: 

264 s += " SCHEDULED: {} DEADLINE: {}\n".format(scheduled, deadline) 

265 elif scheduled: 

266 s += " SCHEDULED: {}\n".format(scheduled) 

267 elif deadline: 

268 s += " DEADLINE: {}\n".format(deadline) 

269 if properties: 

270 s += " :PROPERTIES:\n" 

271 for key, val in properties.items(): 

272 s += " :{}: {}\n".format(key, val) 

273 s += " :END:\n\n" 

274 

275 if body: 

276 s += body + "\n" 

277 

278 print(s) 

279 

280 

281def link(linktype=None, path=None, desc=None): 

282 """Print an org link [[type:path][desc]]. 

283 

284 :path: is all that is mandatory. 

285 """ 

286 s = "[[" 

287 if linktype is not None: 

288 s += linktype + ":" 

289 s += path + "]" 

290 

291 if desc is not None: 

292 s += "[{}]".format(desc) 

293 

294 s += "]" 

295 

296 print(s)