Skip to content

Commit bac7156

Browse files
committed
fixes #805
1 parent 684244b commit bac7156

File tree

4 files changed

+416
-46
lines changed

4 files changed

+416
-46
lines changed

fastcore/_modidx.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,15 +515,23 @@
515515
'fastcore.nbio.Notebook.save': ('nbio.html#notebook.save', 'fastcore/nbio.py'),
516516
'fastcore.nbio.Notebook.view': ('nbio.html#notebook.view', 'fastcore/nbio.py'),
517517
'fastcore.nbio._dict2obj': ('nbio.html#_dict2obj', 'fastcore/nbio.py'),
518+
'fastcore.nbio._join': ('nbio.html#_join', 'fastcore/nbio.py'),
519+
'fastcore.nbio._preferred_msg_out': ('nbio.html#_preferred_msg_out', 'fastcore/nbio.py'),
518520
'fastcore.nbio._read_json': ('nbio.html#_read_json', 'fastcore/nbio.py'),
521+
'fastcore.nbio.apply_controls': ('nbio.html#apply_controls', 'fastcore/nbio.py'),
519522
'fastcore.nbio.cell2xml': ('nbio.html#cell2xml', 'fastcore/nbio.py'),
520523
'fastcore.nbio.cells2xml': ('nbio.html#cells2xml', 'fastcore/nbio.py'),
524+
'fastcore.nbio.concat_streams': ('nbio.html#concat_streams', 'fastcore/nbio.py'),
521525
'fastcore.nbio.dict2nb': ('nbio.html#dict2nb', 'fastcore/nbio.py'),
522526
'fastcore.nbio.mk_cell': ('nbio.html#mk_cell', 'fastcore/nbio.py'),
527+
'fastcore.nbio.mk_stream': ('nbio.html#mk_stream', 'fastcore/nbio.py'),
523528
'fastcore.nbio.nb2dict': ('nbio.html#nb2dict', 'fastcore/nbio.py'),
524529
'fastcore.nbio.nb2str': ('nbio.html#nb2str', 'fastcore/nbio.py'),
525530
'fastcore.nbio.new_nb': ('nbio.html#new_nb', 'fastcore/nbio.py'),
531+
'fastcore.nbio.preferred_out': ('nbio.html#preferred_out', 'fastcore/nbio.py'),
526532
'fastcore.nbio.read_nb': ('nbio.html#read_nb', 'fastcore/nbio.py'),
533+
'fastcore.nbio.render_output': ('nbio.html#render_output', 'fastcore/nbio.py'),
534+
'fastcore.nbio.render_outputs': ('nbio.html#render_outputs', 'fastcore/nbio.py'),
527535
'fastcore.nbio.write_nb': ('nbio.html#write_nb', 'fastcore/nbio.py')},
528536
'fastcore.net': { 'fastcore.net.HTTP4xxClientError': ('net.html#http4xxclienterror', 'fastcore/net.py'),
529537
'fastcore.net.HTTP5xxServerError': ('net.html#http5xxservererror', 'fastcore/net.py'),

fastcore/ansi.py

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,6 @@ def strip_ansi(source, term_queries:bool=False):
2525
return _ANSI_RE.sub("", source)
2626

2727

28-
def ansi2html(text):
29-
"Convert ANSI colors to HTML colors."
30-
text = escape(strip_terminal_queries(text))
31-
return _ansi2anything(text, _htmlconverter)
32-
33-
34-
def ansi2latex(text):
35-
"Convert ANSI colors to LaTeX colors."
36-
return _ansi2anything(text, _latexconverter)
37-
38-
3928
def _htmlconverter(fg, bg, bold, underline, inverse):
4029
"Return start and end tags for given foreground/background/bold/underline."
4130
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False): return "", ""
@@ -103,6 +92,35 @@ def _latexconverter(fg, bg, bold, underline, inverse):
10392
return starttag, endtag
10493

10594

95+
def _get_extended_color(numbers):
96+
n = numbers.pop(0)
97+
if n == 2 and len(numbers) >= 3:
98+
# 24-bit RGB
99+
r = numbers.pop(0)
100+
g = numbers.pop(0)
101+
b = numbers.pop(0)
102+
if not all(0 <= c <= 255 for c in (r, g, b)): raise ValueError()
103+
elif n == 5 and len(numbers) >= 1:
104+
# 256 colors
105+
idx = numbers.pop(0)
106+
if idx < 0: raise ValueError()
107+
# 16 default terminal colors
108+
if idx < 16: return idx
109+
if idx < 232:
110+
# 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098
111+
r = (idx - 16) // 36
112+
r = 55 + r * 40 if r > 0 else 0
113+
g = ((idx - 16) % 36) // 6
114+
g = 55 + g * 40 if g > 0 else 0
115+
b = (idx - 16) % 6
116+
b = 55 + b * 40 if b > 0 else 0
117+
# grayscale, see http://stackoverflow.com/a/27165165/500098
118+
elif idx < 256: r = g = b = (idx - 232) * 10 + 8
119+
else: raise ValueError()
120+
else: raise ValueError()
121+
return r, g, b
122+
123+
106124
def _ansi2anything(text, converter):
107125
"Convert ANSI colors to HTML or LaTeX."
108126
fg, bg = None, None
@@ -158,31 +176,13 @@ def _ansi2anything(text, converter):
158176
return "".join(out)
159177

160178

161-
def _get_extended_color(numbers):
162-
n = numbers.pop(0)
163-
if n == 2 and len(numbers) >= 3:
164-
# 24-bit RGB
165-
r = numbers.pop(0)
166-
g = numbers.pop(0)
167-
b = numbers.pop(0)
168-
if not all(0 <= c <= 255 for c in (r, g, b)): raise ValueError()
169-
elif n == 5 and len(numbers) >= 1:
170-
# 256 colors
171-
idx = numbers.pop(0)
172-
if idx < 0: raise ValueError()
173-
# 16 default terminal colors
174-
if idx < 16: return idx
175-
if idx < 232:
176-
# 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098
177-
r = (idx - 16) // 36
178-
r = 55 + r * 40 if r > 0 else 0
179-
g = ((idx - 16) % 36) // 6
180-
g = 55 + g * 40 if g > 0 else 0
181-
b = (idx - 16) % 6
182-
b = 55 + b * 40 if b > 0 else 0
183-
# grayscale, see http://stackoverflow.com/a/27165165/500098
184-
elif idx < 256: r = g = b = (idx - 232) * 10 + 8
185-
else: raise ValueError()
186-
else: raise ValueError()
187-
return r, g, b
179+
def ansi2html(text):
180+
"Convert ANSI colors to HTML colors."
181+
text = escape(strip_terminal_queries(text))
182+
return _ansi2anything(text, _htmlconverter)
183+
184+
185+
def ansi2latex(text):
186+
"Convert ANSI colors to LaTeX colors."
187+
return _ansi2anything(text, _latexconverter)
188188

fastcore/nbio.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
# %% auto #0
66
__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']
89

9-
# %% ../nbs/13_nbio.ipynb #5faca748
10+
# %% ../nbs/13_nbio.ipynb #954ca1aa
1011
from .basics import *
1112
from .xtras import rtoken_hex
1213
from .imports import *
14+
from .ansi import ansi2html
1315

1416
import ast,functools
1517
from pprint import pformat,pprint
@@ -200,3 +202,78 @@ def view(self:Notebook, id, nums=True):
200202
lines = self[id].source.splitlines()
201203
if nums: lines = [f'{i+1:6d}{l}' for i,l in enumerate(lines)]
202204
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)