Skip to content

Commit 474680e

Browse files
committed
Add option to use ansi2html renderer
1 parent 4492a79 commit 474680e

File tree

2 files changed

+56
-20
lines changed

2 files changed

+56
-20
lines changed

execnb/shell.py

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

88
from fastcore.utils import *
99
from fastcore.script import call_parse
10-
10+
from fastcore.ansi import ansi2html
1111
import multiprocessing,types,traceback
1212
try:
1313
if sys.platform == 'darwin': multiprocessing.set_start_method("fork")
@@ -125,11 +125,11 @@ def run(self:CaptureShell,
125125
return _out_nb(res, self.display_formatter)
126126

127127
# %% ../nbs/02_shell.ipynb 30
128-
def render_outputs(outputs):
128+
def render_outputs(outputs, ansi_renderer=strip_ansi):
129129
def render_output(out):
130130
otype = out['output_type']
131131
if otype == 'stream':
132-
txt = strip_ansi(''.join(out['text']))
132+
txt = ansi_renderer(''.join(out['text']))
133133
return f"<pre>{txt}</pre>" if out['name']=='stdout' else f"<pre class='stderr'>{txt}</pre>"
134134
elif otype in ('display_data','execute_result'):
135135
data = out['data']
@@ -146,7 +146,7 @@ def render_output(out):
146146

147147
return '\n'.join(map(render_output, outputs))
148148

149-
# %% ../nbs/02_shell.ipynb 41
149+
# %% ../nbs/02_shell.ipynb 43
150150
@patch
151151
def cell(self:CaptureShell, cell, stdout=True, stderr=True):
152152
"Run `cell`, skipping if not code, and store outputs back in cell"
@@ -158,32 +158,32 @@ def cell(self:CaptureShell, cell, stdout=True, stderr=True):
158158
for o in outs:
159159
if 'execution_count' in o: cell['execution_count'] = o['execution_count']
160160

161-
# %% ../nbs/02_shell.ipynb 44
161+
# %% ../nbs/02_shell.ipynb 46
162162
def find_output(outp, # Output from `run`
163163
ot='execute_result' # Output_type to find
164164
):
165165
"Find first output of type `ot` in `CaptureShell.run` output"
166166
return first(o for o in outp if o['output_type']==ot)
167167

168-
# %% ../nbs/02_shell.ipynb 47
168+
# %% ../nbs/02_shell.ipynb 49
169169
def out_exec(outp):
170170
"Get data from execution result in `outp`."
171171
out = find_output(outp)
172172
if out: return '\n'.join(first(out['data'].values()))
173173

174-
# %% ../nbs/02_shell.ipynb 49
174+
# %% ../nbs/02_shell.ipynb 51
175175
def out_stream(outp):
176176
"Get text from stream in `outp`."
177177
out = find_output(outp, 'stream')
178178
if out: return ('\n'.join(out['text'])).strip()
179179

180-
# %% ../nbs/02_shell.ipynb 51
180+
# %% ../nbs/02_shell.ipynb 53
181181
def out_error(outp):
182182
"Get traceback from error in `outp`."
183183
out = find_output(outp, 'error')
184184
if out: return '\n'.join(out['traceback'])
185185

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

189189
@patch
@@ -203,7 +203,7 @@ def run_all(self:CaptureShell,
203203
postproc(cell)
204204
if self.exc and exc_stop: raise self.exc from None
205205

206-
# %% ../nbs/02_shell.ipynb 67
206+
# %% ../nbs/02_shell.ipynb 69
207207
@patch
208208
def execute(self:CaptureShell,
209209
src:str|Path, # Notebook path to read from
@@ -224,7 +224,7 @@ def execute(self:CaptureShell,
224224
inject_code=inject_code, inject_idx=inject_idx)
225225
if dest: write_nb(nb, dest)
226226

227-
# %% ../nbs/02_shell.ipynb 71
227+
# %% ../nbs/02_shell.ipynb 73
228228
@patch
229229
def prettytb(self:CaptureShell,
230230
fname:str|Path=None): # filename to print alongside the traceback
@@ -236,7 +236,7 @@ def prettytb(self:CaptureShell,
236236
fname_str = f' in {fname}' if fname else ''
237237
return f"{type(self.exc).__name__}{fname_str}:\n{_fence}\n{cell_str}\n"
238238

239-
# %% ../nbs/02_shell.ipynb 90
239+
# %% ../nbs/02_shell.ipynb 92
240240
@call_parse
241241
def exec_nb(
242242
src:str, # Notebook path to read from
@@ -250,7 +250,7 @@ def exec_nb(
250250
CaptureShell().execute(src, dest, exc_stop=exc_stop, inject_code=inject_code,
251251
inject_path=inject_path, inject_idx=inject_idx)
252252

253-
# %% ../nbs/02_shell.ipynb 93
253+
# %% ../nbs/02_shell.ipynb 95
254254
class SmartCompleter(IPCompleter):
255255
def __init__(self, shell, namespace=None, jedi=False):
256256
if namespace is None: namespace = shell.user_ns
@@ -270,7 +270,7 @@ def __call__(self, c):
270270
for o in self.completions(c, len(c))
271271
if o.type=='<unknown>']
272272

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

nbs/02_shell.ipynb

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -763,11 +763,11 @@
763763
"outputs": [],
764764
"source": [
765765
"#| export\n",
766-
"def render_outputs(outputs):\n",
766+
"def render_outputs(outputs, ansi_renderer=strip_ansi):\n",
767767
" def render_output(out):\n",
768768
" otype = out['output_type']\n",
769769
" if otype == 'stream':\n",
770-
" txt = strip_ansi(''.join(out['text']))\n",
770+
" txt = ansi_renderer(''.join(out['text']))\n",
771771
" return f\"<pre>{txt}</pre>\" if out['name']=='stdout' else f\"<pre class='stderr'>{txt}</pre>\"\n",
772772
" elif otype in ('display_data','execute_result'):\n",
773773
" data = out['data']\n",
@@ -794,11 +794,11 @@
794794
"data": {
795795
"text/html": [
796796
"<pre>---------------------------------------------------------------------------\n",
797-
"ZeroDivisionError Traceback (most recent call last)\n",
798-
"File <ipython-input-1-9e1622b385b6>:1\n",
799-
"----> 1 1/0\n",
797+
"Exception Traceback (most recent call last)\n",
798+
"File <ipython-input-1-01648acb07bd>:1\n",
799+
"----> 1 raise Exception(\"Oops\")\n",
800800
"\n",
801-
"ZeroDivisionError: division by zero\n",
801+
"Exception: Oops\n",
802802
"</pre>\n"
803803
],
804804
"text/plain": [
@@ -814,6 +814,42 @@
814814
"HTML(render_outputs(o))"
815815
]
816816
},
817+
{
818+
"cell_type": "markdown",
819+
"metadata": {},
820+
"source": [
821+
"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."
822+
]
823+
},
824+
{
825+
"cell_type": "code",
826+
"execution_count": null,
827+
"metadata": {},
828+
"outputs": [
829+
{
830+
"data": {
831+
"text/html": [
832+
"<pre><span class=\"ansi-red-fg\">---------------------------------------------------------------------------</span>\n",
833+
"<span class=\"ansi-red-fg\">Exception</span> Traceback (most recent call last)\n",
834+
"File <span class=\"ansi-green-fg\">&lt;ipython-input-1-01648acb07bd&gt;:1</span>\n",
835+
"<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",
836+
"\n",
837+
"<span class=\"ansi-red-fg\">Exception</span>: Oops\n",
838+
"</pre>\n"
839+
],
840+
"text/plain": [
841+
"<IPython.core.display.HTML object>"
842+
]
843+
},
844+
"execution_count": null,
845+
"metadata": {},
846+
"output_type": "execute_result"
847+
}
848+
],
849+
"source": [
850+
"HTML(render_outputs(o, ansi2html))"
851+
]
852+
},
817853
{
818854
"cell_type": "markdown",
819855
"metadata": {},

0 commit comments

Comments
 (0)