Skip to content

Instantly share code, notes, and snippets.

@epitron
Created January 28, 2012 10:02
Show Gist options
  • Save epitron/1693810 to your computer and use it in GitHub Desktop.
Save epitron/1693810 to your computer and use it in GitHub Desktop.
Convert ANSI to HTML
require 'epitools'
class State
attr_accessor :fore, :back, :attrs
COLORS = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white]
ATTRS = {
0 => :reset,
1 => :bright,
2 => :dim,
4 => :underscore,
5 => :blink,
7 => :reverse,
8 => :hidden,
}
FORES = {
30 => :black,
31 => :red,
32 => :green,
33 => :yellow,
34 => :blue,
35 => :magenta,
36 => :cyan,
37 => :white,
}
BACKS = {
40 => :black,
41 => :red,
42 => :green,
43 => :yellow,
44 => :blue,
45 => :magenta,
46 => :cyan,
47 => :white,
}
PALETTE = [
"#000000",
"#CC0000",
"#4E9A06",
"#C4A000",
"#3465A4",
"#75507B",
"#06989A",
"#D3D7CF",
"#555753",
"#EF2929",
"#8AE234",
"#FCE94F",
"#729FCF",
"#AD7FA8",
"#34E2E2",
"#EEEEEC"
]
def initialize
reset
end
def reset
@fore = :white
@back = :black
@attrs ||= Set.new
@attrs.clear
end
def update(code)
case
when attr = ATTRS[code]
if attr == :reset
reset
else
attrs << attr
end
when fore = FORES[code]
@fore = fore
when back = BACKS[code]
@back = back
end
end
def html_color(sym, bright=false)
n = COLORS.index(sym)
n += 8 if bright
PALETTE[n]
end
def html_for(text)
bright = @attrs.include?(:bright)
style = "color:#{html_color @fore, bright }; background-color:#{ html_color @back };"
result = "<span style='#{style}'>"
if bright
result << "<b>#{text}</b>"
else
result << text
end
result << "</span>"
result
end
end
def entities(text)
text.gsub("<", "&lt;").gsub(">", "&gt;")
end
def ansi2html(ansi)
# problem:
# * minimize the number of tags
#
# complex algorithm:
# * parse the ansi into a structure with color/runlength for each extent of text
# * an extent of color A, bracketed by extents of color B, should be turned into a nested tag
# [ INPUT = flat, OUTPUT = tree ]
# questions:
# * what scenario(s) will cause this scheme to produce more tags than a naive approach?
#
# simpler algorithm:
# * split on color codes
# * delete redundant codes
# * create <span>s
# debugging info
tokens = ansi.split(/(\e\[\d{1,4}(?:;\d{1,4})*[mhclnRABCDfsugJKi])/)
# remove non-color terminal codes
tokens = tokens.select{|s| not ( s.blank? or s =~ /^\e\[([\d;]+)?[hclnRABCDfsugJKi]$/ ) }
output = [] # will contain series of html tags
state = State.new # the state of the terminal (current color and attributes)
tokens.each do |token|
if token =~ /^\e\[(.+)m/
codes = $1.scan(/(\d+);?/).flatten.map(&:to_i) # grab all the code numbers
codes.each do |code|
state.update(code)
end
else # it's a blob of text.
output << state.html_for(entities(token))
end
end
puts
output.join("")#.gsub("\n", "<br/>\n")
end
def ansi2file(ansi, f)
f.puts "<pre style='background-color: black;'>"
f.puts ansi2html(ansi)
f.puts "</pre>"
end
def test_pattern
result = ansi2html("\e[c" + "<red>red <light_green>green <blue>blue".colorize + "\e[31;1m")
# print the output
puts "result: "; pp result
colors = State::COLORS + State::COLORS.map {|color| "light_#{color}" }
ansi = colors.map{|color| "<8>[<#{color}>#{color}<8>]\n" }.join('').colorize
open("testpat.html", "w") do |f|
f.puts "<code style='background-color: black; display: block;'>"
f.puts ansi2html(ansi)
f.puts "</code>"
puts "* testpat.html written!"
end
end
if $0 == __FILE__
ansi2file(ARGF.read, $stdout)
end
input:
"\e[c\e[31mred \e[0m\e[32m\e[1mgreen \e[0m\e[34mblue\e[0m\e[31;1m"
tokens:
[:code, "\e[31m"]
[:text, "red "]
[:code, "\e[0m"]
[:code, "\e[32m"]
[:code, "\e[1m"]
[:text, "green "]
[:code, "\e[0m"]
[:code, "\e[34m"]
[:text, "blue"]
[:code, "\e[0m"]
[:code, "\e[31;1m"]
result:
"<span style='color:#CC0000; background-color:#000000;'>red <span><span style='color:#8AE234; background-color:#000000;'>green <span><span style='color:#3465A4; background-color:#000000;'>blue<span>"
input:
"\e[30m\e[1m[\e[0m\e[30mblack\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[31mred\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[32mgreen\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[33myellow\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[34mblue\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[35mmagenta\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[36mcyan\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[37mwhite\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[30m\e[1mlight_black\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[31m\e[1mlight_red\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[32m\e[1mlight_green\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[33m\e[1mlight_yellow\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[34m\e[1mlight_blue\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[35m\e[1mlight_magenta\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[36m\e[1mlight_cyan\e[0m\e[30m\e[1m]\n\e[0m\e[30m\e[1m[\e[0m\e[37m\e[1mlight_white\e[0m\e[30m\e[1m]\n\e[0m"
tokens:
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:text, "black"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[31m"]
[:text, "red"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[32m"]
[:text, "green"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[33m"]
[:text, "yellow"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[34m"]
[:text, "blue"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[35m"]
[:text, "magenta"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[36m"]
[:text, "cyan"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[37m"]
[:text, "white"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "light_black"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[31m"]
[:code, "\e[1m"]
[:text, "light_red"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[32m"]
[:code, "\e[1m"]
[:text, "light_green"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[33m"]
[:code, "\e[1m"]
[:text, "light_yellow"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[34m"]
[:code, "\e[1m"]
[:text, "light_blue"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[35m"]
[:code, "\e[1m"]
[:text, "light_magenta"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[36m"]
[:code, "\e[1m"]
[:text, "light_cyan"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "["]
[:code, "\e[0m"]
[:code, "\e[37m"]
[:code, "\e[1m"]
[:text, "light_white"]
[:code, "\e[0m"]
[:code, "\e[30m"]
[:code, "\e[1m"]
[:text, "]\n"]
[:code, "\e[0m"]
* testpat.html written!
<code style='background-color: black; display: block;'>
<span style='color:#555753; background-color:#000000;'>[<span><span style='color:#000000; background-color:#000000;'>black<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#CC0000; background-color:#000000;'>red<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#4E9A06; background-color:#000000;'>green<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#C4A000; background-color:#000000;'>yellow<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#3465A4; background-color:#000000;'>blue<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#75507B; background-color:#000000;'>magenta<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#06989A; background-color:#000000;'>cyan<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#D3D7CF; background-color:#000000;'>white<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#555753; background-color:#000000;'>light_black<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#EF2929; background-color:#000000;'>light_red<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#8AE234; background-color:#000000;'>light_green<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#FCE94F; background-color:#000000;'>light_yellow<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#729FCF; background-color:#000000;'>light_blue<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#AD7FA8; background-color:#000000;'>light_magenta<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#34E2E2; background-color:#000000;'>light_cyan<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span><span style='color:#555753; background-color:#000000;'>[<span><span style='color:#EEEEEC; background-color:#000000;'>light_white<span><span style='color:#555753; background-color:#000000;'>]<br/>
<span>
</code>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment