424 lines
13 KiB
Lua
424 lines
13 KiB
Lua
-- this is essentially
|
|
-- https://github.com/jupyter/nbconvert/blob/main/nbconvert/filters/ansi.py
|
|
-- converted to lua
|
|
|
|
-- good list of ANSI escape sequences:
|
|
-- https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
|
|
|
local css = [[
|
|
<!-- CSS added by ANSI escape sequences filter -->
|
|
<style>
|
|
.ansi {line-height:1; padding:0; margin: 0; letter-spacing:0;
|
|
overflow:visible;font-family:JuliaMono;}
|
|
|
|
/* console foregrounds and backgrounds */
|
|
pre .ansi-black-fg { color: #3e424d; }
|
|
pre .ansi-red-fg { color: #e75c58; }
|
|
pre .ansi-green-fg { color: #00a250; }
|
|
pre .ansi-yellow-fg { color: #ddb62b; }
|
|
pre .ansi-blue-fg { color: #208ffb; }
|
|
pre .ansi-magenta-fg { color: #d160c4; }
|
|
pre .ansi-cyan-fg { color: #60c6c8; }
|
|
pre .ansi-white-fg { color: #c5c1b4; }
|
|
pre .ansi-black-bg { background-color: #3e424d; }
|
|
pre .ansi-red-bg { background-color: #e75c58; }
|
|
pre .ansi-green-bg { background-color: #00a250; }
|
|
pre .ansi-yellow-bg { background-color: #ddb62b; }
|
|
pre .ansi-blue-bg { background-color: #208ffb; }
|
|
pre .ansi-magenta-bg { background-color: #d160c4; }
|
|
pre .ansi-cyan-bg { background-color: #60c6c8; }
|
|
pre .ansi-white-bg { background-color: #c5c1b4; }
|
|
pre .ansi-black-intense-fg { color: #282c36; }
|
|
pre .ansi-red-intense-fg { color: #b22b31; }
|
|
pre .ansi-green-intense-fg { color: #007427; }
|
|
pre .ansi-yellow-intense-fg { color: #b27d12; }
|
|
pre .ansi-blue-intense-fg { color: #0065ca; }
|
|
pre .ansi-magenta-intense-fg { color: #a03196; }
|
|
pre .ansi-cyan-intense-fg { color: #258f8f; }
|
|
pre .ansi-white-intense-fg { color: #a1a6b2; }
|
|
pre .ansi-black-intense-bg { background-color: #282c36; }
|
|
pre .ansi-red-intense-bg { background-color: #b22b31; }
|
|
pre .ansi-green-intense-bg { background-color: #007427; }
|
|
pre .ansi-yellow-intense-bg { background-color: #b27d12; }
|
|
pre .ansi-blue-intense-bg { background-color: #0065ca; }
|
|
pre .ansi-magenta-intense-bg { background-color: #a03196; }
|
|
pre .ansi-cyan-intense-bg { background-color: #258f8f; }
|
|
pre .ansi-white-intense-bg { background-color: #a1a6b2; }
|
|
pre .ansi-default-inverse-fg { color: rgba(255, 255, 255, 1); }
|
|
pre .ansi-default-inverse-bg { background-color: #111111; }
|
|
pre .ansi-bold { font-weight: bold; }
|
|
pre .ansi-underline { text-decoration: underline; }
|
|
</style>
|
|
]]
|
|
|
|
local latexpreamble = [[
|
|
\usepackage{fancyvrb}
|
|
\usepackage{fontspec}
|
|
\usepackage{xcolor}
|
|
\renewenvironment{verbatim}{%
|
|
\VerbatimEnvironment
|
|
\begin{Verbatim}[commandchars=\\\{\}]%
|
|
}{%
|
|
\end{Verbatim}%
|
|
}
|
|
\setmonofont{JuliaMono}[
|
|
Scale = MatchLowercase,
|
|
UprightFont = *-Regular,
|
|
BoldFont = *-Bold,
|
|
ItalicFont = *-RegularItalic,
|
|
BoldItalicFont = *-BoldItalic,
|
|
]
|
|
\definecolor{ansi-black}{HTML}{3E424D}
|
|
\definecolor{ansi-black-intense}{HTML}{282C36}
|
|
\definecolor{ansi-red}{HTML}{E75C58}
|
|
\definecolor{ansi-red-intense}{HTML}{B22B31}
|
|
\definecolor{ansi-green}{HTML}{00A250}
|
|
\definecolor{ansi-green-intense}{HTML}{007427}
|
|
\definecolor{ansi-yellow}{HTML}{DDB62B}
|
|
\definecolor{ansi-yellow-intense}{HTML}{B27D12}
|
|
\definecolor{ansi-blue}{HTML}{208FFB}
|
|
\definecolor{ansi-blue-intense}{HTML}{0065CA}
|
|
\definecolor{ansi-magenta}{HTML}{D160C4}
|
|
\definecolor{ansi-magenta-intense}{HTML}{A03196}
|
|
\definecolor{ansi-cyan}{HTML}{60C6C8}
|
|
\definecolor{ansi-cyan-intense}{HTML}{258F8F}
|
|
\definecolor{ansi-white}{HTML}{C5C1B4}
|
|
\definecolor{ansi-white-intense}{HTML}{A1A6B2}
|
|
\definecolor{ansi-default-inverse-fg}{HTML}{FFFFFF}
|
|
\definecolor{ansi-default-inverse-bg}{HTML}{000000}
|
|
]]
|
|
|
|
local 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"
|
|
}
|
|
|
|
local flag = false -- set to true if we find ANSI sequences, used by Meta
|
|
|
|
local function get_extended_color(numbers)
|
|
local n = table.remove(numbers, 1)
|
|
local r,g,b,idx
|
|
if n == 2 and #numbers >=3 then
|
|
-- 24bit RGB
|
|
r = table.remove(numbers, 1)
|
|
g = table.remove(numbers, 1)
|
|
b = table.remove(numbers, 1)
|
|
elseif n == 5 and #numbers >= 1 then
|
|
-- 256 colors
|
|
idx = table.remove(numbers, 1)
|
|
if idx < 16 then
|
|
-- 16 default terminal colors
|
|
return idx
|
|
elseif idx < 232 then
|
|
-- 6x6x6 color cube, see http://stackoverflow.com/a/27165165/500098
|
|
r = (idx - 16) // 36
|
|
r = 55 + r * 40
|
|
if r < 0 then r = 0 end
|
|
g = ((idx - 16) % 36) // 6
|
|
g = 55 + g * 40
|
|
if g < 0 then g = 0 end
|
|
b = (idx - 16) % 6
|
|
b = 55 + b * 40
|
|
if b < 0 then b = 0 end
|
|
elseif idx < 256 then
|
|
-- grayscale, see http://stackoverflow.com/a/27165165/500098
|
|
r = (idx - 232) * 10 + 8
|
|
g = r
|
|
b = r
|
|
end
|
|
end
|
|
return {r, g, b}
|
|
end
|
|
|
|
|
|
--[=[
|
|
local re = require "re"
|
|
|
|
local ANSI = re.compile [[
|
|
'\x1b%[' {.*?} {[@-~]}
|
|
]]
|
|
|
|
--]=]
|
|
|
|
local function LaTeXconverter(fg, bg, bold, underline, inverse)
|
|
if not (fg or bg or bold or underline or inverse) then
|
|
return "",""
|
|
end
|
|
|
|
local starttag = ""
|
|
local endtag = ""
|
|
|
|
if inverse then
|
|
fg, bg = bg, fg
|
|
end
|
|
|
|
if type(fg) == "number" then
|
|
starttag = starttag .. [[\textcolor{]] .. ANSI_COLORS[fg+1] .. "}{"
|
|
endtag = "}" .. endtag
|
|
elseif type(fg) == "table" then
|
|
-- See http://tex.stackexchange.com/a/291102/13684
|
|
starttag = starttag .. [[\def\tcRGB{\textcolor[RGB]}\expandafter]]
|
|
starttag = starttag .. string.format([[\tcRGB\expandafter{\detokenize{%d,%d,%d}}{]], fg[1], fg[2], fg[3])
|
|
endtag = "}" .. endtag
|
|
elseif inverse then
|
|
starttag = starttag .. [[\textcolor{ansi-default-inverse-fg}{]]
|
|
endtag = "}" .. endtag
|
|
end
|
|
|
|
if type(bg) == "number" then
|
|
starttag = starttag .. [[\setlength{\fboxsep}{0pt}]]
|
|
starttag = starttag .. [[\colorbox{]] .. ANSI_COLORS[bg+1] .. "}{"
|
|
endtag = [[\strut}]] .. endtag
|
|
elseif type(bg) == "table" then
|
|
-- See http://tex.stackexchange.com/a/291102/13684
|
|
starttag = starttag .. [[\setlength{\fboxsep}{0pt}]]
|
|
starttag = starttag .. [[\def\cbRGB{\colorbox[RGB]}\expandafter]]
|
|
starttag = starttag .. string.format([[\cbRGB\expandafter{\detokenize{%d,%d,%d}}{]], bg[1], bg[2], bg[3])
|
|
endtag = [[\strut}]] .. endtag
|
|
elseif inverse then
|
|
starttag = starttag .. [[\setlength{\fboxsep}{0pt}]]
|
|
starttag = starttag .. [[\colorbox{ansi-default-inverse-bg}{]]
|
|
endtag = [[\strut}]] .. endtag
|
|
end
|
|
|
|
if bold then
|
|
starttag = starttag .. [[\textbf{]]
|
|
endtag = "}" .. endtag
|
|
end
|
|
|
|
if underline then
|
|
starttag = starttag .. [[\underline{]]
|
|
endtag = "}" .. endtag
|
|
end
|
|
return starttag, endtag
|
|
end
|
|
|
|
local function HTMLconverter(fg, bg, bold, underline, inverse)
|
|
if not (fg or bg or bold or underline or inverse) then
|
|
return "",""
|
|
end
|
|
local classes = {}
|
|
local styles = {}
|
|
local type = type -- more efficient?
|
|
local next = next
|
|
if inverse then
|
|
fg, bg = bg, fg
|
|
end
|
|
|
|
if type(fg) == "number" then
|
|
table.insert(classes, ANSI_COLORS[fg+1] .. "-fg")
|
|
elseif type(fg) == "table" then
|
|
table.insert(styles, string.format("color: rgb(%d,%d,%d)", fg[1], fg[2], fg[3]))
|
|
elseif inverse then
|
|
table.insert(classes, "ansi-default-inverse-fg")
|
|
end
|
|
|
|
if type(bg) == "number" then
|
|
table.insert(classes, ANSI_COLORS[bg+1] .. "-bg")
|
|
elseif type(bg) == "table" then
|
|
table.insert(styles, string.format("background-color: rgb(%d,%d,%d)",
|
|
bg[1], bg[2], bg[3]))
|
|
elseif inverse then
|
|
table.insert(classes, "ansi-default-inverse-bg")
|
|
end
|
|
|
|
if bold then
|
|
table.insert(classes, "ansi-bold")
|
|
end
|
|
|
|
if underline then
|
|
table.insert(classes, "ansi-underline")
|
|
end
|
|
|
|
local starttag = "<span"
|
|
if next(classes) ~= nil then
|
|
starttag = starttag .. ' class="' .. table.concat(classes, " ") .. '"'
|
|
end
|
|
|
|
if next(styles) ~= nil then
|
|
starttag = starttag .. ' style="' .. table.concat(styles, " ") .. '"'
|
|
end
|
|
|
|
return starttag..">","</span>"
|
|
end
|
|
|
|
|
|
local function codeBlockTrans(e)
|
|
local converter, fmt
|
|
if FORMAT:match 'latex' then
|
|
converter = LaTeXconverter
|
|
fmt = 'latex'
|
|
elseif FORMAT:match 'html' then
|
|
converter = HTMLconverter
|
|
fmt = 'html'
|
|
else
|
|
return
|
|
end
|
|
local out=""
|
|
if string.find(e.text, "\x1b%[") then
|
|
flag = true
|
|
local bold = false
|
|
local underline = false
|
|
local inverse = false
|
|
local text = e.text
|
|
local chunk = ""
|
|
local fg = nil
|
|
local bg = nil
|
|
local starttag = ""
|
|
local endtag = ""
|
|
local numbers={}
|
|
|
|
while text ~= "" do
|
|
numbers = {}
|
|
local s1, e1, c1, d1 = string.find(text, "\x1b%[(.-)([@-~])")
|
|
if s1 then
|
|
if d1 == "m" then
|
|
for i in string.gmatch(c1, "([^;]*)") do
|
|
table.insert(numbers, tonumber(i))
|
|
end
|
|
else
|
|
io.stderr:write("Unsupported ANSI sequence ESC["..c1..d1.." ignored\n" )
|
|
end
|
|
chunk, text = text:sub(1, s1-1), text:sub(e1+1)
|
|
else
|
|
chunk, text = text, ""
|
|
end
|
|
|
|
if chunk ~= "" then
|
|
if bold and type(fg)=="number" and fg<8 then
|
|
starttag, endtag = converter(fg+8, bg, bold, underline, inverse)
|
|
else
|
|
starttag, endtag = converter(fg, bg, bold, underline, inverse)
|
|
end
|
|
out = out .. starttag .. chunk .. endtag
|
|
end
|
|
|
|
while next(numbers) ~= nil do
|
|
local n = table.remove(numbers, 1)
|
|
if n == 0 then
|
|
fg = nil
|
|
bg = nil
|
|
bold = false
|
|
inverse = false
|
|
underline = false
|
|
elseif n == 1 then
|
|
bold = true
|
|
elseif n == 4 then
|
|
underline = true
|
|
elseif n == 5 then
|
|
bold = true -- 'blinking'
|
|
elseif n == 7 then
|
|
inverse = true
|
|
elseif n == 21 or n == 22 then
|
|
bold = false
|
|
elseif n == 24 then
|
|
underline = false
|
|
elseif n == 27 then
|
|
inverse = false
|
|
elseif n >= 30 and n <= 37 then
|
|
fg = n - 30
|
|
elseif n == 38 then
|
|
fg = get_extended_color(numbers)
|
|
elseif n == 39 then
|
|
fg = nil
|
|
elseif n >= 40 and n <= 47 then
|
|
bg = n - 40
|
|
elseif n == 48 then
|
|
bg = get_extended_color(numbers)
|
|
elseif n == 49 then
|
|
bg = nil
|
|
elseif n >= 90 and n <= 97 then
|
|
fg = n + 8 - 90
|
|
elseif n >= 100 and n <= 107 then
|
|
bg = n + 8 - 100
|
|
else
|
|
io.stderr:write(string.format("ESC sequence with unknown code %d before:\n",n))
|
|
io.stderr:write(chunk.."\n")
|
|
end
|
|
end
|
|
end
|
|
if fmt == 'html' then
|
|
return pandoc.RawBlock(fmt, '<pre class="ansi"><code class="ansi">'..out..'</code></pre>')
|
|
end
|
|
if fmt == 'latex' then
|
|
return pandoc.RawBlock(fmt, "\\begin{verbatim}\n"..out.."\n\\end{verbatim}")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- algo from https://github.com/jgm/pandoc/commit/77faccb505992c944cd1b92f50e4e00d2927682b
|
|
-- simulate --ipynb-outbut=best, but w/o removing ANSI colors
|
|
-- consider only formats html and latex
|
|
local function divTrans(e)
|
|
local fmt
|
|
if FORMAT:match 'latex' then
|
|
fmt = 'latex'
|
|
elseif FORMAT:match 'html' then
|
|
fmt = 'html'
|
|
else
|
|
return
|
|
end
|
|
|
|
local function cmp(a,b)
|
|
return a["rank"] < b["rank"]
|
|
end
|
|
|
|
if e.classes[1] == "output" then
|
|
local c = e.content
|
|
local ranks = {}
|
|
for i, el in pairs(c) do
|
|
if el.t == "RawBlock" then
|
|
if el.format == fmt then
|
|
table.insert(ranks, {rank = 1, el})
|
|
else
|
|
table.insert(ranks, {rank = 3, el})
|
|
end
|
|
else
|
|
table.insert(ranks, {rank = 2, el})
|
|
end
|
|
end
|
|
table.sort(ranks, cmp)
|
|
local winner = ranks[1][1]
|
|
e.content = pandoc.List:new()
|
|
table.insert(e.content, winner)
|
|
return e
|
|
end
|
|
end
|
|
|
|
|
|
local function metaAdd(meta)
|
|
if flag then
|
|
-- read current "header-includes" from metadata, or make a new one
|
|
-- and add css to the end of "header-includes"
|
|
local current = meta['header-includes'] or pandoc.MetaList{meta['header-includes']}
|
|
if FORMAT:match 'html' then
|
|
current[#current+1] = pandoc.MetaBlocks(pandoc.RawBlock("html", css))
|
|
end
|
|
if FORMAT:match 'latex' then
|
|
current[#current+1] = pandoc.MetaBlocks(pandoc.RawBlock("latex", latexpreamble))
|
|
end
|
|
meta['header-includes'] = current
|
|
return(meta)
|
|
end
|
|
end
|
|
|
|
return {
|
|
{Div = divTrans},
|
|
{CodeBlock = codeBlockTrans},
|
|
{Meta = metaAdd}
|
|
}
|