-- 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 = [[ ]] 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 = "","" 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, '
'..out..'
') 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} }