Browse Source

ansi_wrapper

main
pvincent 4 months ago
parent
commit
a2f883b8fd
  1. 10
      .vscode/settings.json
  2. 42
      app/controllers/scores_controller.rb
  3. 9
      config/environments/development.rb
  4. 55
      lib/formatters/ansi_colors.rb
  5. 53
      lib/formatters/ansi_formatter.rb
  6. 78
      lib/formatters/ansi_wrapper.rb

10
.vscode/settings.json

@ -21,5 +21,13 @@
},
"vscode-erb-beautify.keepBlankLines": 1,
"inlineFold.regex": "(class=|className=|class:\\s*)(({(`|))|(['\"`]))(.*?)(\\2|(\\4)})" // inline AlpineJs `class: `
"inlineFold.regex": "(class=|className=|class:\\s*)(({(`|))|(['\"`]))(.*?)(\\2|(\\4)})", // inline AlpineJs `class: `
// REQUIRED FOR PLAIN 256-color ANSI SUPPORT
"terminal.integrated.drawBoldTextInBrightColors": true,
"terminal.integrated.minimumContrastRatio": 1,
"workbench.colorCustomizations" : {
"terminal.foreground" : "#EEEEEE",
"terminal.background" : "#000000"
},
}

42
app/controllers/scores_controller.rb

@ -1,21 +1,57 @@
# ScoresController define Score and Grade interactions
class ScoresController < ApplicationController
include Pagy::Backend
include AnsiColors
before_action :set_score, only: %i[show edit update destroy]
def grayshade(n)
shade = eval("TEXT_GRAY_#{n}")
shade += n.to_s
end
# GET /scores
def index
@pagy, @scores = pagy(Score.all)
logger.info 'this is a normal message'
logger.info 'this is a super long message which should be wrapped. ' * 5
# logger.debug 'this is a debug message'
# logger.info 'this is a super long message which should be wrapped. ' * 5
shades = [800, 700, 600, 500, 400, 300, 200, 100].map { |n| grayshade(n) }.join(' ')
logger.info(BG_BLACK + shades)
logger.info(BG_GRAY + shades)
logger.info(BG_WHITE + shades)
logger.info(BG_BLACK + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_GRAY + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_WHITE + TEXT_GRAY_800 + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_WHITE + TEXT_GRAY_600 + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_WHITE + TEXT_GRAY_400 + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_WHITE + TEXT_GRAY_300 + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_WHITE + TEXT_GRAY_200 + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
logger.info(BG_WHITE + TEXT_GRAY_100 + "this is #{BOLD}a message in bold#{CLEAR}, isnt'it?")
# logger.info "these are gray shades : #{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100 \x1b[38;5;7m7.0\x1b[38;5;15m15.0\x1b[38;5;232m232 \x1b[38;5;233m233 \x1b[38;5;234m234 \x1b[38;5;235m235 \x1b[38;5;236m236 \x1b[38;5;237m237 \x1b[38;5;238m \x1b[38;5;239m \x1b[38;5;240m \x1b[38;5;241m \x1b[38;5;242m \x1b[38;5;243m
# \x1b[38;5;244m \x1b[38;5;245m245 \x1b[38;5;246m \x1b[38;5;247m \x1b[38;5;248m248 \x1b[38;5;249m \x1b[38;5;250m \x1b[38;5;251m \x1b[38;5;252m \x1b[38;5;253m253 \x1b[38;5;254m \x1b[38;5;255m255"
# logger.info "these are gray shades : #{BG_BLACK}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100 \x1b[38;5;7m7.0 \x1b[38;5;15m15.0 \x1b[38;5;232m232 \x1b[38;5;233m233 \x1b[38;5;234m234 \x1b[38;5;235m235 \x1b[38;5;236m236 \x1b[38;5;237m237 \x1b[38;5;238m \x1b[38;5;239m239 \x1b[38;5;240m \x1b[38;5;241m \x1b[38;5;242m242 \x1b[38;5;243m
# \x1b[38;5;244m \x1b[38;5;245m245 \x1b[38;5;246m246 \x1b[38;5;247m \x1b[38;5;248m248 \x1b[38;5;249m249 \x1b[38;5;250m \x1b[38;5;251m \x1b[38;5;252m252 \x1b[38;5;253m253 \x1b[38;5;254m \x1b[38;5;255m255 "
# logger.info "these are gray shades : #{BG_WHITE}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100 \x1b[38;5;7m7.0\x1b[38;5;15m15.0\x1b[38;5;232m232 \x1b[38;5;233m233 \x1b[38;5;234m234 \x1b[38;5;235m235 \x1b[38;5;236m236 \x1b[38;5;237m237 \x1b[38;5;238m \x1b[38;5;239m \x1b[38;5;240m \x1b[38;5;241m \x1b[38;5;242m \x1b[38;5;243m
# \x1b[38;5;244m \x1b[38;5;245m245 \x1b[38;5;246m \x1b[38;5;247m \x1b[38;5;248m248 \x1b[38;5;249m \x1b[38;5;250m \x1b[38;5;251m \x1b[38;5;252m \x1b[38;5;253m253 \x1b[38;5;254m \x1b[38;5;255m255"
# logger.info "these are gray shades : #{BG_BLACK}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100"
# logger.info "these are gray shades : #{BG_WHITE}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100"
# logger.info "these are gray shades : #{BOLD}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100"
# logger.info "these are gray shades : #{BOLD}#{UNDERLINE}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100"
# logger.info "these are gray shades : #{UNDERLINE}#{TEXT_GRAY_800}800#{TEXT_GRAY_600}600#{TEXT_GRAY_400}400#{TEXT_GRAY_200}200#{TEXT_GRAY_100}100"
# logger.info({ one: 1, two: 2 })
# logger.info 'this is an information', { four: 4, five: 5 }
# logger.debug BigDecimal('0.0003')
# logger.warn 'scores are', @scores
logger.warn "this is a warning\n yop\n cool"
# logger.warn "this is a warning\n yop\n cool"
# logger.error 'this is an error message'
# logger.error "error message line #1\nerror message line #2\nerror message line #3\n\n"
# logger.info 'end of normal message'
# logger.debug @scores
# sleep 0.5

9
config/environments/development.rb

@ -80,8 +80,13 @@ Rails.application.configure do # rubocop:disable Metrics/BlockLength
config.rails_semantic_logger.processing = true
config.rails_semantic_logger.rendered = true
config.rails_semantic_logger.add_file_appender = false
# config.semantic_logger.add_appender(io: $stdout,
# formatter: BasicFormatter.new,
# filter: BasicFormatter::EXCLUDE_LAMBDA)
require Rails.root.join('lib', 'formatters', 'basic_formatter')
require Rails.root.join('lib', 'formatters', 'ansi_formatter')
config.semantic_logger.add_appender(io: $stdout,
formatter: BasicFormatter.new,
filter: BasicFormatter::EXCLUDE_LAMBDA)
formatter: AnsiFormatter.new)
end

55
lib/formatters/ansi_colors.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

53
lib/formatters/ansi_formatter.rb

@ -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

78
lib/formatters/ansi_wrapper.rb

@ -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
Loading…
Cancel
Save