mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-10 21:26:07 +10:00
We send a state of ansi2html to client, client needs to send this state back. The state describes the configuration of generator and position within trace.
261 lines
7.4 KiB
Ruby
261 lines
7.4 KiB
Ruby
# ANSI color library
|
|
#
|
|
# Implementation per http://en.wikipedia.org/wiki/ANSI_escape_code
|
|
module Ci
|
|
module Ansi2html
|
|
# keys represent the trailing digit in color changing command (30-37, 40-47, 90-97. 100-107)
|
|
COLOR = {
|
|
0 => 'black', # not that this is gray in the intense color table
|
|
1 => 'red',
|
|
2 => 'green',
|
|
3 => 'yellow',
|
|
4 => 'blue',
|
|
5 => 'magenta',
|
|
6 => 'cyan',
|
|
7 => 'white', # not that this is gray in the dark (aka default) color table
|
|
}
|
|
|
|
STYLE_SWITCHES = {
|
|
bold: 0x01,
|
|
italic: 0x02,
|
|
underline: 0x04,
|
|
conceal: 0x08,
|
|
cross: 0x10,
|
|
}
|
|
|
|
def self.convert(ansi, state = nil)
|
|
Converter.new.convert(ansi, state)
|
|
end
|
|
|
|
class Converter
|
|
def on_0(s) reset() end
|
|
def on_1(s) enable(STYLE_SWITCHES[:bold]) end
|
|
def on_3(s) enable(STYLE_SWITCHES[:italic]) end
|
|
def on_4(s) enable(STYLE_SWITCHES[:underline]) end
|
|
def on_8(s) enable(STYLE_SWITCHES[:conceal]) end
|
|
def on_9(s) enable(STYLE_SWITCHES[:cross]) end
|
|
|
|
def on_21(s) disable(STYLE_SWITCHES[:bold]) end
|
|
def on_22(s) disable(STYLE_SWITCHES[:bold]) end
|
|
def on_23(s) disable(STYLE_SWITCHES[:italic]) end
|
|
def on_24(s) disable(STYLE_SWITCHES[:underline]) end
|
|
def on_28(s) disable(STYLE_SWITCHES[:conceal]) end
|
|
def on_29(s) disable(STYLE_SWITCHES[:cross]) end
|
|
|
|
def on_30(s) set_fg_color(0) end
|
|
def on_31(s) set_fg_color(1) end
|
|
def on_32(s) set_fg_color(2) end
|
|
def on_33(s) set_fg_color(3) end
|
|
def on_34(s) set_fg_color(4) end
|
|
def on_35(s) set_fg_color(5) end
|
|
def on_36(s) set_fg_color(6) end
|
|
def on_37(s) set_fg_color(7) end
|
|
def on_38(s) set_fg_color_256(s) end
|
|
def on_39(s) set_fg_color(9) end
|
|
|
|
def on_40(s) set_bg_color(0) end
|
|
def on_41(s) set_bg_color(1) end
|
|
def on_42(s) set_bg_color(2) end
|
|
def on_43(s) set_bg_color(3) end
|
|
def on_44(s) set_bg_color(4) end
|
|
def on_45(s) set_bg_color(5) end
|
|
def on_46(s) set_bg_color(6) end
|
|
def on_47(s) set_bg_color(7) end
|
|
def on_48(s) set_bg_color_256(s) end
|
|
def on_49(s) set_bg_color(9) end
|
|
|
|
def on_90(s) set_fg_color(0, 'l') end
|
|
def on_91(s) set_fg_color(1, 'l') end
|
|
def on_92(s) set_fg_color(2, 'l') end
|
|
def on_93(s) set_fg_color(3, 'l') end
|
|
def on_94(s) set_fg_color(4, 'l') end
|
|
def on_95(s) set_fg_color(5, 'l') end
|
|
def on_96(s) set_fg_color(6, 'l') end
|
|
def on_97(s) set_fg_color(7, 'l') end
|
|
def on_99(s) set_fg_color(9, 'l') end
|
|
|
|
def on_100(s) set_bg_color(0, 'l') end
|
|
def on_101(s) set_bg_color(1, 'l') end
|
|
def on_102(s) set_bg_color(2, 'l') end
|
|
def on_103(s) set_bg_color(3, 'l') end
|
|
def on_104(s) set_bg_color(4, 'l') end
|
|
def on_105(s) set_bg_color(5, 'l') end
|
|
def on_106(s) set_bg_color(6, 'l') end
|
|
def on_107(s) set_bg_color(7, 'l') end
|
|
def on_109(s) set_bg_color(9, 'l') end
|
|
|
|
attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask
|
|
|
|
STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask]
|
|
|
|
def convert(raw, new_state)
|
|
reset_state
|
|
restore_state(new_state) if new_state && new_state[:offset].to_i < raw.length
|
|
|
|
start = @offset
|
|
ansi = raw[@offset..-1]
|
|
|
|
open_new_tag
|
|
|
|
s = StringScanner.new(ansi)
|
|
while(!s.eos?)
|
|
if s.scan(/\e([@-_])(.*?)([@-~])/)
|
|
handle_sequence(s)
|
|
elsif s.scan(/\e(([@-_])(.*?)?)?$/)
|
|
break
|
|
elsif s.scan(/</)
|
|
@out << '<'
|
|
else
|
|
@out << s.scan(/./m)
|
|
end
|
|
@offset += s.matched_size
|
|
end
|
|
|
|
close_open_tags()
|
|
|
|
{ state: state, html: @out, text: ansi[0, @offset - start], append: start > 0 }
|
|
end
|
|
|
|
def handle_sequence(s)
|
|
indicator = s[1]
|
|
commands = s[2].split ';'
|
|
terminator = s[3]
|
|
|
|
# We are only interested in color and text style changes - triggered by
|
|
# sequences starting with '\e[' and ending with 'm'. Any other control
|
|
# sequence gets stripped (including stuff like "delete last line")
|
|
return unless indicator == '[' and terminator == 'm'
|
|
|
|
close_open_tags()
|
|
|
|
if commands.empty?()
|
|
reset()
|
|
return
|
|
end
|
|
|
|
evaluate_command_stack(commands)
|
|
|
|
open_new_tag
|
|
end
|
|
|
|
def evaluate_command_stack(stack)
|
|
return unless command = stack.shift()
|
|
|
|
if self.respond_to?("on_#{command}", true)
|
|
self.send("on_#{command}", stack)
|
|
end
|
|
|
|
evaluate_command_stack(stack)
|
|
end
|
|
|
|
def open_new_tag
|
|
css_classes = []
|
|
|
|
unless @fg_color.nil?
|
|
fg_color = @fg_color
|
|
# Most terminals show bold colored text in the light color variant
|
|
# Let's mimic that here
|
|
if @style_mask & STYLE_SWITCHES[:bold] != 0
|
|
fg_color.sub!(/fg-(\w{2,}+)/, 'fg-l-\1')
|
|
end
|
|
css_classes << fg_color
|
|
end
|
|
css_classes << @bg_color unless @bg_color.nil?
|
|
|
|
STYLE_SWITCHES.each do |css_class, flag|
|
|
css_classes << "term-#{css_class}" if @style_mask & flag != 0
|
|
end
|
|
|
|
return if css_classes.empty?
|
|
|
|
@out << %{<span class="#{css_classes.join(' ')}">}
|
|
@n_open_tags += 1
|
|
end
|
|
|
|
def close_open_tags
|
|
while @n_open_tags > 0
|
|
@out << %{</span>}
|
|
@n_open_tags -= 1
|
|
end
|
|
end
|
|
|
|
def reset_state
|
|
@offset = 0
|
|
@n_open_tags = 0
|
|
@out = ''
|
|
reset
|
|
end
|
|
|
|
def state
|
|
STATE_PARAMS.inject({}) do |h, param|
|
|
h[param] = send(param)
|
|
h
|
|
end
|
|
end
|
|
|
|
def restore_state(new_state)
|
|
STATE_PARAMS.each do |param|
|
|
send("#{param}=".to_sym, new_state[param])
|
|
end
|
|
end
|
|
|
|
def reset
|
|
@fg_color = nil
|
|
@bg_color = nil
|
|
@style_mask = 0
|
|
end
|
|
|
|
def enable(flag)
|
|
@style_mask |= flag
|
|
end
|
|
|
|
def disable(flag)
|
|
@style_mask &= ~flag
|
|
end
|
|
|
|
def set_fg_color(color_index, prefix = nil)
|
|
@fg_color = get_term_color_class(color_index, ["fg", prefix])
|
|
end
|
|
|
|
def set_bg_color(color_index, prefix = nil)
|
|
@bg_color = get_term_color_class(color_index, ["bg", prefix])
|
|
end
|
|
|
|
def get_term_color_class(color_index, prefix)
|
|
color_name = COLOR[color_index]
|
|
return nil if color_name.nil?
|
|
|
|
get_color_class(["term", prefix, color_name])
|
|
end
|
|
|
|
def set_fg_color_256(command_stack)
|
|
css_class = get_xterm_color_class(command_stack, "fg")
|
|
@fg_color = css_class unless css_class.nil?
|
|
end
|
|
|
|
def set_bg_color_256(command_stack)
|
|
css_class = get_xterm_color_class(command_stack, "bg")
|
|
@bg_color = css_class unless css_class.nil?
|
|
end
|
|
|
|
def get_xterm_color_class(command_stack, prefix)
|
|
# the 38 and 48 commands have to be followed by "5" and the color index
|
|
return unless command_stack.length >= 2
|
|
return unless command_stack[0] == "5"
|
|
|
|
command_stack.shift() # ignore the "5" command
|
|
color_index = command_stack.shift().to_i
|
|
|
|
return unless color_index >= 0
|
|
return unless color_index <= 255
|
|
|
|
get_color_class(["xterm", prefix, color_index])
|
|
end
|
|
|
|
def get_color_class(segments)
|
|
[segments].flatten.compact.join('-')
|
|
end
|
|
end
|
|
end
|
|
end
|