pvincent
6 months ago
6 changed files with 241 additions and 6 deletions
-
10.vscode/settings.json
-
42app/controllers/scores_controller.rb
-
9config/environments/development.rb
-
55lib/formatters/ansi_colors.rb
-
53lib/formatters/ansi_formatter.rb
-
78lib/formatters/ansi_wrapper.rb
@ -0,0 +1,55 @@ |
|||||
|
module AnsiColors |
||||
|
# FORMAT |
||||
|
CLEAR = "\e[0m".freeze |
||||
|
BOLD = "\e[1m".freeze |
||||
|
UNDERLINE = "\e[4m".freeze |
||||
|
|
||||
|
# TEXT |
||||
|
TEXT_BLACK = "\e[30m".freeze |
||||
|
TEXT_RED = "\e[31m".freeze |
||||
|
TEXT_GREEN = "\e[32m".freeze |
||||
|
TEXT_YELLOW = "\e[33m".freeze |
||||
|
TEXT_BLUE = "\e[34m".freeze |
||||
|
TEXT_MAGENTA = "\e[35m".freeze |
||||
|
TEXT_CYAN = "\e[36m".freeze |
||||
|
TEXT_WHITE = "\e[37m".freeze |
||||
|
|
||||
|
# TEXT GRAY SHADES |
||||
|
TEXT_GRAY_800 = "\e[38;5;232m".freeze |
||||
|
TEXT_GRAY_700 = "\e[38;5;233m".freeze |
||||
|
TEXT_GRAY_600 = "\e[38;5;235m".freeze |
||||
|
TEXT_GRAY_500 = "\e[38;5;238m".freeze |
||||
|
TEXT_GRAY_400 = "\e[38;5;241m".freeze |
||||
|
TEXT_GRAY_300 = "\e[38;5;244m".freeze |
||||
|
TEXT_GRAY_200 = "\e[38;5;249m".freeze |
||||
|
TEXT_GRAY_100 = "\e[38;5;252m".freeze |
||||
|
|
||||
|
# DARK TEXT |
||||
|
DARK_TEXT_BLACK = "\e[90m".freeze |
||||
|
DARK_TEXT_RED = "\e[91m".freeze |
||||
|
DARK_TEXT_GREEN = "\e[92m".freeze |
||||
|
DARK_TEXT_YELLOW = "\e[93m".freeze |
||||
|
DARK_TEXT_BLUE = "\e[94m".freeze |
||||
|
DARK_TEXT_MAGENTA = "\e[95m".freeze |
||||
|
DARK_TEXT_CYAN = "\e[96m".freeze |
||||
|
DARK_TEXT_WHITE = "\e[97m".freeze |
||||
|
|
||||
|
# BACKGROUND |
||||
|
BG_BLACK = "\e[40m".freeze |
||||
|
BG_WHITE = "\e[47m".freeze |
||||
|
BG_GRAY = "\e[100m".freeze |
||||
|
BG_RED = "\e[41m".freeze |
||||
|
BG_GREEN = "\e[42m".freeze |
||||
|
BG_YELLOW = "\e[43m".freeze |
||||
|
BG_BLUE = "\e[44m".freeze |
||||
|
BG_MAGENTA = "\e[45m".freeze |
||||
|
BG_CYAN = "\e[46m".freeze |
||||
|
|
||||
|
# DARK BACKGROUND |
||||
|
DARK_BG_RED = "\e[101m".freeze |
||||
|
DARK_BG_GREEN = "\e[102m".freeze |
||||
|
DARK_BG_YELLOW = "\e[103m".freeze |
||||
|
DARK_BG_BLUE = "\e[104m".freeze |
||||
|
DARK_BG_MAGENTA = "\e[105m".freeze |
||||
|
DARK_BG_CYAN = "\e[106m".freeze |
||||
|
end |
@ -0,0 +1,53 @@ |
|||||
|
require_relative 'ansi_wrapper' |
||||
|
require_relative 'ansi_colors' |
||||
|
|
||||
|
require 'io/console' |
||||
|
require 'amazing_print' |
||||
|
require 'json' |
||||
|
|
||||
|
class AnsiFormatter < SemanticLogger::Formatters::Color |
||||
|
include AnsiColors |
||||
|
|
||||
|
ANSI_DEBUG = CLEAR + TEXT_CYAN |
||||
|
ANSI_INFO = CLEAR + TEXT_WHITE |
||||
|
ANSI_WARN = BG_YELLOW + TEXT_BLACK |
||||
|
ANSI_ERROR = BG_RED + TEXT_BLACK |
||||
|
ANSI_FATAL = DARK_BG_RED + TEXT_BLACK |
||||
|
|
||||
|
def initialize |
||||
|
super(color_map: ColorMap.new( |
||||
|
debug: ANSI_DEBUG, |
||||
|
info: ANSI_INFO, |
||||
|
warn: ANSI_WARN, |
||||
|
error: ANSI_ERROR, |
||||
|
fatal: ANSI_FATAL |
||||
|
)) |
||||
|
end |
||||
|
|
||||
|
def message |
||||
|
return unless log.message |
||||
|
|
||||
|
colorize(log.message, color_map[log.level]) |
||||
|
end |
||||
|
|
||||
|
def call(log, logger) |
||||
|
self.log = log |
||||
|
self.logger = logger |
||||
|
self.color = color_map[log.level] |
||||
|
wrap_level(level, message, payload, exception) |
||||
|
end |
||||
|
|
||||
|
private |
||||
|
|
||||
|
def colorize(text, color) |
||||
|
"#{color}#{text}#{color_map.clear}" |
||||
|
end |
||||
|
|
||||
|
def wrap_level(level, *items) |
||||
|
prefix = " #{colorize('>', color)} " |
||||
|
continuation = " #{colorize('#', color)} " |
||||
|
items.map do |item| |
||||
|
AnsiWrapper.wrap(item, 100, prefix, continuation) |
||||
|
end.compact.join("\n") # FIXME: previously was: join(' ') |
||||
|
end |
||||
|
end |
@ -0,0 +1,78 @@ |
|||||
|
# AnsiWrapper cares about Ansi Colour Code \e[... |
||||
|
class AnsiWrapper |
||||
|
TAB_TO_SPACES = 2 |
||||
|
ANSI_REGEX = /\e\[[0-9;]*m/ # TODO: support for \x1b and \033 |
||||
|
ANSI_RESET = "\e[0m".freeze |
||||
|
|
||||
|
def self.wrap(text, length, prefix = '', continuation = prefix) |
||||
|
if prefix.length != continuation.length |
||||
|
raise "continuation <#{continuation}> should have the same length as prefix <#{prefix}>" |
||||
|
end |
||||
|
return unless text |
||||
|
|
||||
|
text = text.gsub("\t", ' ' * TAB_TO_SPACES) |
||||
|
|
||||
|
lines = split_text_to_lines(text, length - prefix.length) |
||||
|
lines = inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation) |
||||
|
lines.join("\n") |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.inject_continuation_and_ansi_colors_to_lines(lines, prefix, continuation) |
||||
|
last_ansi = '' |
||||
|
lines.each_with_index.map do |line, index| |
||||
|
current = index.zero? ? prefix : continuation |
||||
|
current += last_ansi unless last_ansi.empty? || last_ansi == ANSI_RESET |
||||
|
current += line |
||||
|
|
||||
|
last_ansi = scan_for_actual_ansi(line, last_ansi) |
||||
|
|
||||
|
current += ANSI_RESET if last_ansi.empty? || last_ansi != ANSI_RESET |
||||
|
current |
||||
|
end |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.scan_for_actual_ansi(line, last_ansi) |
||||
|
line.scan(ANSI_REGEX).each do |match| |
||||
|
ansi_code = match.to_s |
||||
|
if ansi_code == ANSI_RESET |
||||
|
last_ansi = ANSI_RESET |
||||
|
else |
||||
|
last_ansi += ansi_code |
||||
|
end |
||||
|
end |
||||
|
last_ansi |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.split_text_to_lines(text, length) |
||||
|
lines = text.split("\n") |
||||
|
sublines = lines.map do |line| |
||||
|
visible_length(line) > length ? visible_split(line, length) : [line] |
||||
|
end |
||||
|
sublines.flatten |
||||
|
end |
||||
|
|
||||
|
private_class_method def self.visible_length(line) |
||||
|
raise 'line should not contain carriage return character!' if line.match "\n" |
||||
|
|
||||
|
ansi_code_length = line.scan(ANSI_REGEX).map(&:length).sum |
||||
|
line.length - ansi_code_length |
||||
|
end |
||||
|
|
||||
|
# TODO: might be refactored with less complexity |
||||
|
private_class_method def self.visible_split(line, length, stack = '') # rubocop:disable Metrics/AbcSize,Metrics/MethodLength |
||||
|
before, ansi_code, after = line.partition(ANSI_REGEX) |
||||
|
stack_length = visible_length(stack) |
||||
|
visible_length = before.length + stack_length |
||||
|
if visible_length == length |
||||
|
["#{stack}#{before}#{ansi_code}"] + visible_split(after, length) |
||||
|
elsif visible_length > length |
||||
|
first_line = stack + before[0...length - stack_length] |
||||
|
tail = before[length - stack_length..] + ansi_code + after |
||||
|
[first_line] + visible_split(tail, length) |
||||
|
elsif ansi_code.length.positive? |
||||
|
visible_split(after, length, "#{stack}#{before}#{ansi_code}") |
||||
|
else |
||||
|
["#{stack}#{before}#{ansi_code}"] |
||||
|
end |
||||
|
end |
||||
|
end |
Write
Preview
Loading…
Cancel
Save
Reference in new issue