Skip to content

Commit 8cb4233

Browse files
committed
fixes #810
1 parent 4606aa7 commit 8cb4233

3 files changed

Lines changed: 721 additions & 536 deletions

File tree

fastcore/_modidx.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,14 +517,19 @@
517517
'fastcore.nbio.Notebook.view': ('nbio.html#notebook.view', 'fastcore/nbio.py'),
518518
'fastcore.nbio._dict2obj': ('nbio.html#_dict2obj', 'fastcore/nbio.py'),
519519
'fastcore.nbio._join': ('nbio.html#_join', 'fastcore/nbio.py'),
520+
'fastcore.nbio._mkout': ('nbio.html#_mkout', 'fastcore/nbio.py'),
520521
'fastcore.nbio._preferred_msg_out': ('nbio.html#_preferred_msg_out', 'fastcore/nbio.py'),
521522
'fastcore.nbio._read_json': ('nbio.html#_read_json', 'fastcore/nbio.py'),
523+
'fastcore.nbio._render_text': ('nbio.html#_render_text', 'fastcore/nbio.py'),
522524
'fastcore.nbio.apply_controls': ('nbio.html#apply_controls', 'fastcore/nbio.py'),
523525
'fastcore.nbio.cell2xml': ('nbio.html#cell2xml', 'fastcore/nbio.py'),
524526
'fastcore.nbio.cells2xml': ('nbio.html#cells2xml', 'fastcore/nbio.py'),
525527
'fastcore.nbio.concat_streams': ('nbio.html#concat_streams', 'fastcore/nbio.py'),
526528
'fastcore.nbio.dict2nb': ('nbio.html#dict2nb', 'fastcore/nbio.py'),
527529
'fastcore.nbio.mk_cell': ('nbio.html#mk_cell', 'fastcore/nbio.py'),
530+
'fastcore.nbio.mk_display': ('nbio.html#mk_display', 'fastcore/nbio.py'),
531+
'fastcore.nbio.mk_error': ('nbio.html#mk_error', 'fastcore/nbio.py'),
532+
'fastcore.nbio.mk_result': ('nbio.html#mk_result', 'fastcore/nbio.py'),
528533
'fastcore.nbio.mk_stream': ('nbio.html#mk_stream', 'fastcore/nbio.py'),
529534
'fastcore.nbio.nb2dict': ('nbio.html#nb2dict', 'fastcore/nbio.py'),
530535
'fastcore.nbio.nb2str': ('nbio.html#nb2str', 'fastcore/nbio.py'),
@@ -533,6 +538,7 @@
533538
'fastcore.nbio.read_nb': ('nbio.html#read_nb', 'fastcore/nbio.py'),
534539
'fastcore.nbio.render_output': ('nbio.html#render_output', 'fastcore/nbio.py'),
535540
'fastcore.nbio.render_outputs': ('nbio.html#render_outputs', 'fastcore/nbio.py'),
541+
'fastcore.nbio.render_text': ('nbio.html#render_text', 'fastcore/nbio.py'),
536542
'fastcore.nbio.write_nb': ('nbio.html#write_nb', 'fastcore/nbio.py')},
537543
'fastcore.net': { 'fastcore.net.HTTP4xxClientError': ('net.html#http4xxclienterror', 'fastcore/net.py'),
538544
'fastcore.net.HTTP5xxServerError': ('net.html#http5xxservererror', 'fastcore/net.py'),

fastcore/nbio.py

Lines changed: 115 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/13_nbio.ipynb.
44

55
# %% auto #0
6-
__all__ = ['NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'nb2dict', 'nb2str', 'write_nb', 'cell2xml', 'cells2xml',
7-
'Notebook', 'preferred_out', 'apply_controls', 'mk_stream', 'concat_streams', 'render_output',
8-
'render_outputs']
6+
__all__ = ['NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'nb2dict', 'nb2str', 'write_nb', 'preferred_out',
7+
'apply_controls', 'mk_stream', 'mk_result', 'mk_display', 'mk_error', 'concat_streams', 'render_output',
8+
'render_outputs', 'render_text', 'cell2xml', 'cells2xml', 'Notebook']
99

1010
# %% ../nbs/13_nbio.ipynb #954ca1aa
1111
from .basics import *
@@ -106,8 +106,117 @@ def write_nb(nb, path):
106106
if new!=old:
107107
with open(path, 'w', encoding='utf-8') as f: f.write(new)
108108

109-
# %% ../nbs/13_nbio.ipynb #102e32c6
110-
from .xml import Code,Markdown,Raw,NB,Source,Out,to_xml
109+
# %% ../nbs/13_nbio.ipynb #530b9cd1
110+
from .xml import Code,Markdown,Raw,NB,Source,Out,to_xml,ft
111+
112+
# %% ../nbs/13_nbio.ipynb #9f22b923
113+
def preferred_out(data, html1st=True, include_imgs=False):
114+
preftyps = ('application/javascript', 'text/latex')
115+
preftyps = (('text/html', 'text/markdown') if html1st else ('text/markdown', 'text/html')) + preftyps
116+
if include_imgs: preftyps += 'image/jpeg','image/png','image/svg+xml'
117+
preftyps += ('text/plain',)
118+
for mt in preftyps:
119+
if (text := data.get(mt)): return mt,text
120+
return 'text/plain',''
121+
122+
# %% ../nbs/13_nbio.ipynb #efec6078
123+
def apply_controls(text):
124+
r"Apply \r and \b to text, returning processed result"
125+
lines = text.split('\n')
126+
for i, line in enumerate(lines):
127+
if 0<(rpos := line.rfind('\r'))<len(line)-1: lines[i] = line[rpos+1:]
128+
text = '\n'.join(lines)
129+
while (pos := text.find('\b')) >= 0: text = text[:max(0, pos-1)] + text[pos+1:]
130+
return text
131+
132+
# %% ../nbs/13_nbio.ipynb #24a47a87
133+
def _join(d): return ''.join(d) if isinstance(d, list) else d
134+
135+
# %% ../nbs/13_nbio.ipynb #51fcbb01
136+
def mk_stream(name, text):
137+
"Helper to create an output stream dict"
138+
return {'output_type': 'stream', 'name': name, 'text': text}
139+
140+
def _mkout(typ, metadata=None, **data):
141+
return dict(output_type=typ, data={k.replace('_','/'):v for k,v in data.items()}, metadata=metadata or {})
142+
143+
def mk_result(metadata=None, **data):
144+
"Helper to create an execute_result output dict"
145+
return _mkout('execute_result', metadata, **data)
146+
147+
def mk_display(metadata=None, **data):
148+
"Helper to create a display_data output dict"
149+
return _mkout('display_data', metadata, **data)
150+
151+
def mk_error(traceback, ename='', evalue=''):
152+
"Helper to create an error output dict"
153+
return dict(output_type='error', ename=ename, evalue=evalue, traceback=listify(traceback))
154+
155+
# %% ../nbs/13_nbio.ipynb #493e36b0
156+
def concat_streams(outputs):
157+
"Concatenate stream outputs by name (stdout/stderr), preserving execute_result at end"
158+
streams, res, execute_results = {}, [], []
159+
for out in outputs:
160+
if out['output_type'] == 'stream':
161+
name, text = out['name'], _join(out['text'])
162+
streams[name] = apply_controls(streams.get(name, '') + text)
163+
elif out['output_type'] in ('error','execute_result'): execute_results.append(out)
164+
else: res.append(out)
165+
if 'stdout' in streams: res.append(mk_stream('stdout', streams['stdout']))
166+
if 'stderr' in streams: res.append(mk_stream('stderr', streams['stderr']))
167+
res.extend(execute_results)
168+
return res
169+
170+
# %% ../nbs/13_nbio.ipynb #a3deacac
171+
def _preferred_msg_out(out, **kwargs):
172+
typ = out['output_type']
173+
if typ == 'stream': return 'text/plain', _join(out.get('text', ""))
174+
elif typ == 'error': return 'text/plain', '\n'.join(out.get('traceback', []))
175+
elif typ in ('execute_result', 'display_data'): return preferred_out(out.get('data', {}), **kwargs)
176+
return 'text/plain',f'Error: Failed to parse unknown output - {out}'
177+
178+
# %% ../nbs/13_nbio.ipynb #4daf81fd
179+
def render_output(out):
180+
"Convert a single output dict to an HTML string"
181+
def _fmt(text):
182+
res = ansi2html(str(text))
183+
return f'<pre class="!border-0 !rounded-none !my-0 !p-0"><code class="nohighlight">{res}</code></pre>'
184+
ptyp,d = _preferred_msg_out(out, html1st=True, include_imgs=True)
185+
d = _join(d)
186+
if ptyp=='text/plain': return _fmt(d)
187+
elif ptyp=='text/html': return d
188+
elif ptyp=='application/javascript': return f'<script>{d}</script>'
189+
elif ptyp=='text/markdown': return d
190+
elif ptyp=='text/latex': return f'<div>{d}</div>'
191+
elif ptyp=='image/jpeg': return f'<img src="data:image/jpeg;base64,{d}"/>'
192+
elif ptyp=='image/png': return f'<img src="data:image/png;base64,{d}"/>'
193+
elif ptyp=='image/svg+xml': return d
194+
return ''
195+
196+
# %% ../nbs/13_nbio.ipynb #e59b5289
197+
def render_outputs(outputs):
198+
"Render a full list of outputs, concatenating streams first."
199+
if (not isinstance(outputs, (list,tuple))) or (outputs and not isinstance(outputs[0],dict)): return ''
200+
return '\n'.join(render_output(o) for o in concat_streams(outputs))
201+
202+
# %% ../nbs/13_nbio.ipynb #88b0018a
203+
def _render_text(out, html1st=False):
204+
typ = out['output_type']
205+
mime,d = _preferred_msg_out(out, html1st=html1st, include_imgs=False)
206+
d = _join(d)
207+
if not d: return None
208+
attrs = {}
209+
if typ == 'stream': typ = out.get('name')
210+
elif typ in ('execute_result','display_data') and mime!='text/plain': attrs['mime'] = mime
211+
body = f'\n{d}' if d.endswith('\n') else f'\n{d}\n'
212+
return d, to_xml(ft(typ, body, **attrs), do_escape=False, indent=False)
213+
214+
def render_text(outputs, html1st=False):
215+
"Render notebook outputs to concise text, using XML-ish tags when multiple outputs are present."
216+
if (not isinstance(outputs, (list,tuple))) or (outputs and not isinstance(outputs[0],dict)): return ''
217+
items = [o for out in concat_streams(outputs) if (o := _render_text(out, html1st=html1st))]
218+
if not items: return ''
219+
return items[0][0] if len(items)==1 else '\n'.join(o[1] for o in items)
111220

112221
# %% ../nbs/13_nbio.ipynb #ca73be1c
113222
_ctfuns_nb = dict(code=Code, markdown=Markdown, raw=Raw)
@@ -118,7 +227,7 @@ def cell2xml(cell, ids=True, incl_out=True):
118227
kw = dict(id=cell.id) if ids else {}
119228
output = getattr(cell, 'outputs', None) if incl_out else None
120229
if not output: return f(cell.source, **kw)
121-
return f(Source(cell.source), Out(str(output)), **kw)
230+
return f(Source(cell.source), Out(render_text(output)), **kw)
122231

123232
def cells2xml(cells, wrap=NB, ids=True, incl_out=True, **kw):
124233
"Convert notebook cells to XML format"
@@ -202,78 +311,3 @@ def view(self:Notebook, id, nums=True):
202311
lines = self[id].source.splitlines()
203312
if nums: lines = [f'{i+1:6d}{l}' for i,l in enumerate(lines)]
204313
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

Comments
 (0)