diff --git a/.vscode/settings.json b/.vscode/settings.json index 629bd0c..63e9f8b 100644 --- a/.vscode/settings.json +++ b/.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" + }, } \ No newline at end of file diff --git a/app/controllers/scores_controller.rb b/app/controllers/scores_controller.rb index 0de2ca3..66b9b72 100644 --- a/app/controllers/scores_controller.rb +++ b/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 diff --git a/config/environments/development.rb b/config/environments/development.rb index f44c0e9..b56c090 100644 --- a/config/environments/development.rb +++ b/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 diff --git a/lib/formatters/ansi_colors.rb b/lib/formatters/ansi_colors.rb new file mode 100644 index 0000000..0e6b562 --- /dev/null +++ b/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 diff --git a/lib/formatters/ansi_formatter.rb b/lib/formatters/ansi_formatter.rb new file mode 100644 index 0000000..37abdd6 --- /dev/null +++ b/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 diff --git a/lib/formatters/ansi_wrapper.rb b/lib/formatters/ansi_wrapper.rb new file mode 100644 index 0000000..586436f --- /dev/null +++ b/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