Skip to content

Commit 3fc8853

Browse files
authored
Merge pull request #57 from fastai/ansi2html-renderer
Add option to use ansi2html renderer
2 parents 4492a79 + 2d17bf5 commit 3fc8853

File tree

2 files changed

+59
-21
lines changed

2 files changed

+59
-21
lines changed

execnb/shell.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from fastcore.utils import *
99
from fastcore.script import call_parse
10+
from fastcore.ansi import ansi2html
1011

1112
import multiprocessing,types,traceback
1213
try:
@@ -125,11 +126,11 @@ def run(self:CaptureShell,
125126
return _out_nb(res, self.display_formatter)
126127

127128
# %% ../nbs/02_shell.ipynb 30
128-
def render_outputs(outputs):
129+
def render_outputs(outputs, ansi_renderer=strip_ansi):
129130
def render_output(out):
130131
otype = out['output_type']
131132
if otype == 'stream':
132-
txt = strip_ansi(''.join(out['text']))
133+
txt = ansi_renderer(''.join(out['text']))
133134
return f"<pre>{txt}</pre>" if out['name']=='stdout' else f"<pre class='stderr'>{txt}</pre>"
134135
elif otype in ('display_data','execute_result'):
135136
data = out['data']
@@ -146,7 +147,7 @@ def render_output(out):
146147

147148
return '\n'.join(map(render_output, outputs))
148149

149-
# %% ../nbs/02_shell.ipynb 41
150+
# %% ../nbs/02_shell.ipynb 43
150151
@patch
151152
def cell(self:CaptureShell, cell, stdout=True, stderr=True):
152153
"Run `cell`, skipping if not code, and store outputs back in cell"
@@ -158,32 +159,32 @@ def cell(self:CaptureShell, cell, stdout=True, stderr=True):
158159
for o in outs:
159160
if 'execution_count' in o: cell['execution_count'] = o['execution_count']
160161

161-
# %% ../nbs/02_shell.ipynb 44
162+
# %% ../nbs/02_shell.ipynb 46
162163
def find_output(outp, # Output from `run`
163164
ot='execute_result' # Output_type to find
164165
):
165166
"Find first output of type `ot` in `CaptureShell.run` output"
166167
return first(o for o in outp if o['output_type']==ot)
167168

168-
# %% ../nbs/02_shell.ipynb 47
169+
# %% ../nbs/02_shell.ipynb 49
169170
def out_exec(outp):
170171
"Get data from execution result in `outp`."
171172
out = find_output(outp)
172173
if out: return '\n'.join(first(out['data'].values()))
173174

174-
# %% ../nbs/02_shell.ipynb 49
175+
# %% ../nbs/02_shell.ipynb 51
175176
def out_stream(outp):
176177
"Get text from stream in `outp`."
177178
out = find_output(outp, 'stream')
178179
if out: return ('\n'.join(out['text'])).strip()
179180

180-
# %% ../nbs/02_shell.ipynb 51
181+
# %% ../nbs/02_shell.ipynb 53
181182
def out_error(outp):
182183
"Get traceback from error in `outp`."
183184
out = find_output(outp, 'error')
184185
if out: return '\n'.join(out['traceback'])
185186

186-
# %% ../nbs/02_shell.ipynb 53
187+
# %% ../nbs/02_shell.ipynb 55
187188
def _false(o): return False
188189

189190
@patch
@@ -203,7 +204,7 @@ def run_all(self:CaptureShell,
203204
postproc(cell)
204205
if self.exc and exc_stop: raise self.exc from None
205206

206-
# %% ../nbs/02_shell.ipynb 67
207+
# %% ../nbs/02_shell.ipynb 69
207208
@patch
208209
def execute(self:CaptureShell,
209210
src:str|Path, # Notebook path to read from
@@ -224,7 +225,7 @@ def execute(self:CaptureShell,
224225
inject_code=inject_code, inject_idx=inject_idx)
225226
if dest: write_nb(nb, dest)
226227

227-
# %% ../nbs/02_shell.ipynb 71
228+
# %% ../nbs/02_shell.ipynb 73
228229
@patch
229230
def prettytb(self:CaptureShell,
230231
fname:str|Path=None): # filename to print alongside the traceback
@@ -236,7 +237,7 @@ def prettytb(self:CaptureShell,
236237
fname_str = f' in {fname}' if fname else ''
237238
return f"{type(self.exc).__name__}{fname_str}:\n{_fence}\n{cell_str}\n"
238239

239-
# %% ../nbs/02_shell.ipynb 90
240+
# %% ../nbs/02_shell.ipynb 92
240241
@call_parse
241242
def exec_nb(
242243
src:str, # Notebook path to read from
@@ -250,7 +251,7 @@ def exec_nb(
250251
CaptureShell().execute(src, dest, exc_stop=exc_stop, inject_code=inject_code,
251252
inject_path=inject_path, inject_idx=inject_idx)
252253

253-
# %% ../nbs/02_shell.ipynb 93
254+
# %% ../nbs/02_shell.ipynb 95
254255
class SmartCompleter(IPCompleter):
255256
def __init__(self, shell, namespace=None, jedi=False):
256257
if namespace is None: namespace = shell.user_ns
@@ -268,9 +269,9 @@ def __call__(self, c):
268269
with provisionalcompleter():
269270
return [o.text.rpartition('.')[-1]
270271
for o in self.completions(c, len(c))
271-
if o.type=='<unknown>']
272+
if o.type not in ('magic', 'path')]
272273

273-
# %% ../nbs/02_shell.ipynb 95
274+
# %% ../nbs/02_shell.ipynb 97
274275
@patch
275276
def complete(self:CaptureShell, c):
276277
if not hasattr(self, '_completer'): self._completer = SmartCompleter(self)

nbs/02_shell.ipynb

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"\n",
3131
"from fastcore.utils import *\n",
3232
"from fastcore.script import call_parse\n",
33+
"from fastcore.ansi import ansi2html\n",
3334
"\n",
3435
"import multiprocessing,types,traceback\n",
3536
"try:\n",
@@ -763,11 +764,11 @@
763764
"outputs": [],
764765
"source": [
765766
"#| export\n",
766-
"def render_outputs(outputs):\n",
767+
"def render_outputs(outputs, ansi_renderer=strip_ansi):\n",
767768
" def render_output(out):\n",
768769
" otype = out['output_type']\n",
769770
" if otype == 'stream':\n",
770-
" txt = strip_ansi(''.join(out['text']))\n",
771+
" txt = ansi_renderer(''.join(out['text']))\n",
771772
" return f\"<pre>{txt}</pre>\" if out['name']=='stdout' else f\"<pre class='stderr'>{txt}</pre>\"\n",
772773
" elif otype in ('display_data','execute_result'):\n",
773774
" data = out['data']\n",
@@ -794,11 +795,11 @@
794795
"data": {
795796
"text/html": [
796797
"<pre>---------------------------------------------------------------------------\n",
797-
"ZeroDivisionError Traceback (most recent call last)\n",
798-
"File <ipython-input-1-9e1622b385b6>:1\n",
799-
"----> 1 1/0\n",
798+
"Exception Traceback (most recent call last)\n",
799+
"File <ipython-input-1-01648acb07bd>:1\n",
800+
"----> 1 raise Exception(\"Oops\")\n",
800801
"\n",
801-
"ZeroDivisionError: division by zero\n",
802+
"Exception: Oops\n",
802803
"</pre>\n"
803804
],
804805
"text/plain": [
@@ -814,6 +815,42 @@
814815
"HTML(render_outputs(o))"
815816
]
816817
},
818+
{
819+
"cell_type": "markdown",
820+
"metadata": {},
821+
"source": [
822+
"We can use `ansi2html` to convert from ANSI to HTML for color rendering. You need some [css styles](https://github.com/fastai/fastcore/blob/master/examples/ansi.css) for the colors to render properly. Jupyter already has these built in so it's not neccessary here, but if you plan on using this in another web app you will need to ensure that css styling is included."
823+
]
824+
},
825+
{
826+
"cell_type": "code",
827+
"execution_count": null,
828+
"metadata": {},
829+
"outputs": [
830+
{
831+
"data": {
832+
"text/html": [
833+
"<pre><span class=\"ansi-red-fg\">---------------------------------------------------------------------------</span>\n",
834+
"<span class=\"ansi-red-fg\">Exception</span> Traceback (most recent call last)\n",
835+
"File <span class=\"ansi-green-fg\">&lt;ipython-input-1-01648acb07bd&gt;:1</span>\n",
836+
"<span class=\"ansi-green-fg\">----&gt; 1</span> <span class=\"ansi-bold\" style=\"color: rgb(0,135,0)\">raise</span> <span class=\"ansi-bold\" style=\"color: rgb(215,95,95)\">Exception</span>(<span style=\"color: rgb(175,0,0)\">&#34;</span><span style=\"color: rgb(175,0,0)\">Oops</span><span style=\"color: rgb(175,0,0)\">&#34;</span>)\n",
837+
"\n",
838+
"<span class=\"ansi-red-fg\">Exception</span>: Oops\n",
839+
"</pre>\n"
840+
],
841+
"text/plain": [
842+
"<IPython.core.display.HTML object>"
843+
]
844+
},
845+
"execution_count": null,
846+
"metadata": {},
847+
"output_type": "execute_result"
848+
}
849+
],
850+
"source": [
851+
"HTML(render_outputs(o, ansi2html))"
852+
]
853+
},
817854
{
818855
"cell_type": "markdown",
819856
"metadata": {},
@@ -1818,7 +1855,7 @@
18181855
" with provisionalcompleter():\n",
18191856
" return [o.text.rpartition('.')[-1]\n",
18201857
" for o in self.completions(c, len(c))\n",
1821-
" if o.type=='<unknown>']"
1858+
" if o.type not in ('magic', 'path')]"
18221859
]
18231860
},
18241861
{

0 commit comments

Comments
 (0)