Skip to content

Commit

Permalink
fixes #647
Browse files Browse the repository at this point in the history
  • Loading branch information
jph00 committed Nov 11, 2024
1 parent a11d284 commit 4480fab
Show file tree
Hide file tree
Showing 4 changed files with 448 additions and 0 deletions.
31 changes: 31 additions & 0 deletions examples/ansi.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.ansi-black-bg { background-color: #3e424d; }
.ansi-red-bg { background-color: #e75c58; }
.ansi-green-bg { background-color: #00a250; }
.ansi-yellow-bg { background-color: #ddb62b; }
.ansi-blue-bg { background-color: #208ffb; }
.ansi-magenta-bg { background-color: #d160c4; }
.ansi-cyan-bg { background-color: #60c6c8; }
.ansi-white-bg { background-color: #c5c1b4; }
.ansi-black-intense-fg { color: #282c36; }
.ansi-red-intense-fg { color: #b22b31; }
.ansi-green-intense-fg { color: #007427; }
.ansi-yellow-intense-fg { color: #b27d12; }
.ansi-blue-intense-fg { color: #0065ca; }
.ansi-magenta-intense-fg { color: #a03196; }
.ansi-cyan-intense-fg { color: #258f8f; }
.ansi-white-intense-fg { color: #a1a6b2; }
.ansi-black-intense-bg { background-color: #282c36; }
.ansi-red-intense-bg { background-color: #b22b31; }
.ansi-green-intense-bg { background-color: #007427; }
.ansi-yellow-intense-bg { background-color: #b27d12; }
.ansi-blue-intense-bg { background-color: #0065ca; }
.ansi-magenta-intense-bg { background-color: #a03196; }
.ansi-cyan-intense-bg { background-color: #258f8f; }
.ansi-white-intense-bg { background-color: #a1a6b2; }
.ansi-bold { font-weight: bold; }
.ansi-underline { text-decoration: underline; }
{ background-color: #a03196; }
.ansi-cyan-intense-bg { background-color: #258f8f; }
.ansi-white-intense-bg { background-color: #a1a6b2; }
.ansi-bold { font-weight: bold; }
.ansi-underline { text-decoration: underline; }
1 change: 1 addition & 0 deletions fastcore/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'git_url': 'https://github.com/fastai/fastcore/',
'lib_path': 'fastcore'},
'syms': { 'fastcore.all': {},
'fastcore.ansi': {},
'fastcore.basics': { 'fastcore.basics.AttrDict': ('basics.html#attrdict', 'fastcore/basics.py'),
'fastcore.basics.AttrDict.__dir__': ('basics.html#attrdict.__dir__', 'fastcore/basics.py'),
'fastcore.basics.AttrDict.__getattr__': ('basics.html#attrdict.__getattr__', 'fastcore/basics.py'),
Expand Down
180 changes: 180 additions & 0 deletions fastcore/ansi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"Filters for processing ANSI colors."

# Copyright (c) IPython Development Team.
# Modifications by Jeremy Howard.

import re, markupsafe

__all__ = ["strip_ansi", "ansi2html", "ansi2latex"]

_ANSI_RE = re.compile("\x1b\\[(.*?)([@-~])")
_ANSI_COLORS = ( "ansi-black", "ansi-red", "ansi-green", "ansi-yellow", "ansi-blue", "ansi-magenta", "ansi-cyan", "ansi-white", "ansi-black-intense",
"ansi-red-intense", "ansi-green-intense", "ansi-yellow-intense", "ansi-blue-intense", "ansi-magenta-intense", "ansi-cyan-intense", "ansi-white-intense")


def strip_ansi(source):
"Remove ANSI escape codes from text."
return _ANSI_RE.sub("", source)


def ansi2html(text):
"Convert ANSI colors to HTML colors."
text = markupsafe.escape(text)
return _ansi2anything(text, _htmlconverter)


def ansi2latex(text):
"Convert ANSI colors to LaTeX colors."
return _ansi2anything(text, _latexconverter)


def _htmlconverter(fg, bg, bold, underline, inverse):
"Return start and end tags for given foreground/background/bold/underline."
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False): return "", ""

classes,styles = [],[]
if inverse: fg, bg = bg, fg
if isinstance(fg, int): classes.append(_ANSI_COLORS[fg] + "-fg")
elif fg: styles.append("color: rgb({},{},{})".format(*fg))
elif inverse: classes.append("ansi-default-inverse-fg")

if isinstance(bg, int): classes.append(_ANSI_COLORS[bg] + "-bg")
elif bg: styles.append("background-color: rgb({},{},{})".format(*bg))
elif inverse: classes.append("ansi-default-inverse-bg")

if bold: classes.append("ansi-bold")
if underline: classes.append("ansi-underline")

starttag = "<span"
if classes: starttag += ' class="' + " ".join(classes) + '"'
if styles: starttag += ' style="' + "; ".join(styles) + '"'
starttag += ">"
return starttag, "</span>"


def _latexconverter(fg, bg, bold, underline, inverse):
"Return start and end markup given foreground/background/bold/underline."
if (fg, bg, bold, underline, inverse) == (None, None, False, False, False): return "", ""
starttag, endtag = "", ""
if inverse: fg, bg = bg, fg
if isinstance(fg, int):
starttag += r"\textcolor{" + _ANSI_COLORS[fg] + "}{"
endtag = "}" + endtag
elif fg:
# See http://tex.stackexchange.com/a/291102/13684
starttag += r"\def\tcRGB{\textcolor[RGB]}\expandafter"
starttag += r"\tcRGB\expandafter{{\detokenize{{{},{},{}}}}}{{".format(*fg)
endtag = "}" + endtag
elif inverse:
starttag += r"\textcolor{ansi-default-inverse-fg}{"
endtag = "}" + endtag

if isinstance(bg, int):
starttag += r"\setlength{\fboxsep}{0pt}"
starttag += r"\colorbox{" + _ANSI_COLORS[bg] + "}{"
endtag = r"\strut}" + endtag
elif bg:
starttag += r"\setlength{\fboxsep}{0pt}"
# See http://tex.stackexchange.com/a/291102/13684
starttag += r"\def\cbRGB{\colorbox[RGB]}\expandafter"
starttag += r"\cbRGB\expandafter{{\detokenize{{{},{},{}}}}}{{".format(*bg)
endtag = r"\strut}" + endtag
elif inverse:
starttag += r"\setlength{\fboxsep}{0pt}"
starttag += r"\colorbox{ansi-default-inverse-bg}{"
endtag = r"\strut}" + endtag

if bold:
starttag += r"\textbf{"
endtag = "}" + endtag

if underline:
starttag += r"\underline{"
endtag = "}" + endtag

return starttag, endtag


def _ansi2anything(text, converter):
"Convert ANSI colors to HTML or LaTeX."
fg, bg = None, None
bold, underline, inverse = False, False, False
numbers,out = [],[]

while text:
m = _ANSI_RE.search(text)
if m:
if m.group(2) == "m":
# Empty code is same as code 0
try: numbers = [int(n) if n else 0 for n in m.group(1).split(";")]
except ValueError: pass # Invalid color specification
else: pass # Not a color code
chunk, text = text[: m.start()], text[m.end() :]
else: chunk, text = text, ""

if chunk:
starttag, endtag = converter(
fg + 8 if bold and fg in range(8) else fg, # type:ignore[operator]
bg, bold, underline, inverse)
out.append(starttag)
out.append(chunk)
out.append(endtag)

while numbers:
n = numbers.pop(0)
if n == 0:
# Code 0 (same as empty code): reset everything
fg = bg = None
bold = underline = inverse = False
elif n == 1: bold = True
elif n == 4: underline = True
# Code 5: blinking
elif n == 5: bold = True
elif n == 7: inverse = True
elif n in (21, 22): bold = False
elif n == 24: underline = False
elif n == 27: inverse = False
elif 30 <= n <= 37: fg = n - 30
elif n == 38:
try: fg = _get_extended_color(numbers)
except ValueError: numbers.clear()
elif n == 39: fg = None
elif 40 <= n <= 47: bg = n - 40
elif n == 48:
try: bg = _get_extended_color(numbers)
except ValueError: numbers.clear()
elif n == 49: bg = None
elif 90 <= n <= 97: fg = n - 90 + 8
elif 100 <= n <= 107: bg = n - 100 + 8
else: pass # Unknown codes are ignored
return "".join(out)


def _get_extended_color(numbers):
n = numbers.pop(0)
if n == 2 and len(numbers) >= 3:
# 24-bit RGB
r = numbers.pop(0)
g = numbers.pop(0)
b = numbers.pop(0)
if not all(0 <= c <= 255 for c in (r, g, b)): raise ValueError()
elif n == 5 and len(numbers) >= 1:
# 256 colors
idx = numbers.pop(0)
if idx < 0: raise ValueError()
# 16 default terminal colors
if idx < 16: return idx
if idx < 232:
# 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098
r = (idx - 16) // 36
r = 55 + r * 40 if r > 0 else 0
g = ((idx - 16) % 36) // 6
g = 55 + g * 40 if g > 0 else 0
b = (idx - 16) % 6
b = 55 + b * 40 if b > 0 else 0
# grayscale, see http://stackoverflow.com/a/27165165/500098
elif idx < 256: r = g = b = (idx - 232) * 10 + 8
else: raise ValueError()
else: raise ValueError()
return r, g, b

Loading

0 comments on commit 4480fab

Please sign in to comment.