|
4 | 4 |
|
5 | 5 | # %% auto #0 |
6 | 6 | __all__ = ['NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'nb2dict', 'nb2str', 'write_nb', 'cell2xml', 'cells2xml', |
7 | | - 'Notebook'] |
| 7 | + 'Notebook', 'preferred_out', 'apply_controls', 'mk_stream', 'concat_streams', 'render_output', |
| 8 | + 'render_outputs'] |
8 | 9 |
|
9 | | -# %% ../nbs/13_nbio.ipynb #5faca748 |
| 10 | +# %% ../nbs/13_nbio.ipynb #954ca1aa |
10 | 11 | from .basics import * |
11 | 12 | from .xtras import rtoken_hex |
12 | 13 | from .imports import * |
| 14 | +from .ansi import ansi2html |
13 | 15 |
|
14 | 16 | import ast,functools |
15 | 17 | from pprint import pformat,pprint |
@@ -200,3 +202,78 @@ def view(self:Notebook, id, nums=True): |
200 | 202 | lines = self[id].source.splitlines() |
201 | 203 | if nums: lines = [f'{i+1:6d} │ {l}' for i,l in enumerate(lines)] |
202 | 204 | return '\n'.join(lines) |
| 205 | + |
| 206 | +# %% ../nbs/13_nbio.ipynb #e9e2dd49 |
| 207 | +def preferred_out(data, html1st=True, include_imgs=False): |
| 208 | + preftyps = ('application/javascript', 'text/latex') |
| 209 | + preftyps = (('text/html', 'text/markdown') if html1st else ('text/markdown', 'text/html')) + preftyps |
| 210 | + if include_imgs: preftyps += 'image/jpeg','image/png','image/svg+xml' |
| 211 | + preftyps += ('text/plain',) |
| 212 | + for mt in preftyps: |
| 213 | + if (text := data.get(mt)): return mt,text |
| 214 | + return 'text/plain','' |
| 215 | + |
| 216 | +# %% ../nbs/13_nbio.ipynb #7ca5835e |
| 217 | +def apply_controls(text): |
| 218 | + r"Apply \r and \b to text, returning processed result" |
| 219 | + lines = text.split('\n') |
| 220 | + for i, line in enumerate(lines): |
| 221 | + if 0<(rpos := line.rfind('\r'))<len(line)-1: lines[i] = line[rpos+1:] |
| 222 | + text = '\n'.join(lines) |
| 223 | + while (pos := text.find('\b')) >= 0: text = text[:max(0, pos-1)] + text[pos+1:] |
| 224 | + return text |
| 225 | + |
| 226 | +# %% ../nbs/13_nbio.ipynb #b2078b30 |
| 227 | +def _join(d): return ''.join(d) if isinstance(d, list) else d |
| 228 | + |
| 229 | +# %% ../nbs/13_nbio.ipynb #19700480 |
| 230 | +def mk_stream(name, text): |
| 231 | + "Helper to create an output stream dict" |
| 232 | + return {'output_type': 'stream', 'name': name, 'text': text} |
| 233 | + |
| 234 | +# %% ../nbs/13_nbio.ipynb #4af14ed7 |
| 235 | +def concat_streams(outputs): |
| 236 | + "Concatenate stream outputs by name (stdout/stderr), preserving execute_result at end" |
| 237 | + streams, res, execute_results = {}, [], [] |
| 238 | + for out in outputs: |
| 239 | + if out['output_type'] == 'stream': |
| 240 | + name, text = out['name'], _join(out['text']) |
| 241 | + streams[name] = apply_controls(streams.get(name, '') + text) |
| 242 | + elif out['output_type'] in ('error','execute_result'): execute_results.append(out) |
| 243 | + else: res.append(out) |
| 244 | + if 'stdout' in streams: res.append(mk_stream('stdout', streams['stdout'])) |
| 245 | + if 'stderr' in streams: res.append(mk_stream('stderr', streams['stderr'])) |
| 246 | + res.extend(execute_results) |
| 247 | + return res |
| 248 | + |
| 249 | +# %% ../nbs/13_nbio.ipynb #12560bc9 |
| 250 | +def _preferred_msg_out(out, **kwargs): |
| 251 | + typ = out['output_type'] |
| 252 | + if typ == 'stream': return 'text/plain', _join(out.get('text', "")) |
| 253 | + elif typ == 'error': return 'text/plain', '\n'.join(out.get('traceback', [])) |
| 254 | + elif typ in ('execute_result', 'display_data'): return preferred_out(out.get('data', {}), **kwargs) |
| 255 | + return 'text/plain',f'Error: Failed to parse unknown output - {out}' |
| 256 | + |
| 257 | +# %% ../nbs/13_nbio.ipynb #7d321a24 |
| 258 | +def render_output(out): |
| 259 | + "Convert a single output dict to an HTML string" |
| 260 | + def _fmt(text): |
| 261 | + res = ansi2html(str(text)) |
| 262 | + return f'<pre class="!border-0 !rounded-none !my-0 !p-0"><code class="nohighlight">{res}</code></pre>' |
| 263 | + ptyp,d = _preferred_msg_out(out, html1st=True, include_imgs=True) |
| 264 | + d = _join(d) |
| 265 | + if ptyp=='text/plain': return _fmt(d) |
| 266 | + elif ptyp=='text/html': return d |
| 267 | + elif ptyp=='application/javascript': return f'<script>{d}</script>' |
| 268 | + elif ptyp=='text/markdown': return d |
| 269 | + elif ptyp=='text/latex': return f'<div>{d}</div>' |
| 270 | + elif ptyp=='image/jpeg': return f'<img src="data:image/jpeg;base64,{d}"/>' |
| 271 | + elif ptyp=='image/png': return f'<img src="data:image/png;base64,{d}"/>' |
| 272 | + elif ptyp=='image/svg+xml': return d |
| 273 | + return '' |
| 274 | + |
| 275 | +# %% ../nbs/13_nbio.ipynb #5ff35ad7 |
| 276 | +def render_outputs(outputs): |
| 277 | + "Render a full list of outputs, concatenating streams first." |
| 278 | + if (not isinstance(outputs, (list,tuple))) or (outputs and not isinstance(outputs[0],dict)): return '' |
| 279 | + return '\n'.join(render_output(o) for o in concat_streams(outputs)) |
0 commit comments