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
« 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.
31. redirect stderr to stdout for use in Emacs + orgmode.
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.
82. Modify matplotlib.pyplot.savefig and show
103. Provide table and figure commands that generate org-markup.
124. Provide functions that generate org markup, e.g. results, comments,
13headlines and links.
15# Copyright 2015-2022, John Kitchin
16# (see accompanying license files for details).
18"""
20from hashlib import sha1
21import io
22import os
23import sys
24import textwrap
25import matplotlib.pyplot
28def stderr_to_stdout():
29 """Redirect stderr to stdout so it can be captured.
31 Note: if your python returns an error, you may see nothing from this.
32 """
33 sys.stderr = sys.stdout
36class print_redirect:
37 """Context manager for pycse.orgmode.
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)
44 mode='w' overwrites the file
45 mode='a' appends to the file
46 """
48 def __init__(self, fname, mode="w"):
49 """Initialize the decorator."""
50 self.fname = fname
51 self.mode = mode
53 def __enter__(self):
54 """Enter the decorator."""
55 sys.stdout = open(self.fname, self.mode)
56 return self
58 def __exit__(self, exc_type, exc_val, exc_tb):
59 """Exit the decorator."""
60 sys.stdout.close()
61 sys.stdout = sys.__stdout__
64original_savefig = matplotlib.pyplot.savefig
67# patch to capture savefig
68def mysave(fname, *args, **kwargs):
69 """Wrap savefig for org-mode.
71 Returns an org-mode link to the file.
72 """
73 original_savefig(fname, *args, **kwargs)
74 return "[[file:{}]]".format(fname)
77matplotlib.pyplot.savefig = mysave
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()
89original_show = matplotlib.pyplot.show
91SHOW = True
94def myshow(*args, **kwargs):
95 """Wrap matplotlib.pyplot.show for orgmode.
97 Saves the figure in a directory called pyshow with the filename derived
98 from its git-hash.
100 """
101 sio = io.BytesIO()
102 original_savefig(sio, format="png")
103 fig_contents = sio.getvalue()
105 _hash = git_hash(fig_contents)
107 if not os.path.isdir("pyshow"):
108 os.mkdir("pyshow")
110 png = os.path.join("pyshow", _hash + ".png")
112 with open(png, "wb") as f:
113 f.write(fig_contents)
115 print("[[file:{}]]".format(png))
117 if SHOW:
118 original_show(*args, **kwargs)
121matplotlib.pyplot.show = myshow
124# Tables and figures
125def table(data, name=None, caption=None, attributes=None, none=""):
126 """Return a formatted table.
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
134 """
135 s = []
137 if caption is not None:
138 s += ["#+caption: {}".format(caption)]
140 if attributes is not None:
141 for backend, attrs in attributes:
142 s += ["#+attr_{}: {}".format(backend.lower(), attrs)]
144 if name is not None:
145 s += ["#+name: {}".format(name)]
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]) + "|"]
153 print("\n".join(s))
156def figure(fname, caption=None, name=None, attributes=None):
157 """Return a formatted figure.
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')]
164 """
165 s = []
167 if attributes is not None:
168 for backend, attrs in attributes:
169 s += ["#+attr_{}: {}".format(backend.lower(), attrs)]
171 if name is not None:
172 s += ["#+name: {}".format(name)]
174 if caption is not None:
175 s += ["#+caption: {}".format(caption)]
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)]
185 print("\n".join(s))
188def verbatim(s):
189 """Print s in verbatim.
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))
199def comment(s):
200 """Print s as a comment.
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))
210def fixed_width(s):
211 """Print s as a fixed-width element."""
212 print("\n".join([": " + x for x in str(s).split("\n")]))
215def result(s):
216 """Return a fixed_width string.
218 An org src block result.
219 """
220 return fixed_width(str(s))
223def latex(s):
224 """Print s as a latex block."""
225 print("\n#+begin_latex\n{}\n#+end_latex\n".format(s))
228def org(s):
229 """Print s as it is."""
230 print(s)
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.
245 :title: A string for the headline
247 :level: an integer for number of stars in the headline
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.
253 """
254 s = "*" * level + " "
255 if todo is not None:
256 s += "{} ".format(todo)
258 s += title
259 if tags:
260 s += " :" + ":".join(tags) + ":"
261 s += "\n"
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"
275 if body:
276 s += body + "\n"
278 print(s)
281def link(linktype=None, path=None, desc=None):
282 """Print an org link [[type:path][desc]].
284 :path: is all that is mandatory.
285 """
286 s = "[["
287 if linktype is not None:
288 s += linktype + ":"
289 s += path + "]"
291 if desc is not None:
292 s += "[{}]".format(desc)
294 s += "]"
296 print(s)