5 changed files with 11 additions and 512 deletions
			
			
		- 
					8config/environments/development.rb
 - 
					12lib/formatters/ansi_formatter.rb
 - 
					243lib/old/basic_formatter.rb
 - 
					171lib/old/symbols.txt
 - 
					89lib/old/wrapper.rb
 
@ -1,243 +0,0 @@ | 
			
		|||||
require_relative 'wrapper' | 
				 | 
			
		||||
require_relative 'base' | 
				 | 
			
		||||
require 'io/console' | 
				 | 
			
		||||
require 'amazing_print' | 
				 | 
			
		||||
require 'json' | 
				 | 
			
		||||
 | 
				 | 
			
		||||
# Opinioned Rails custom formatter | 
				 | 
			
		||||
class BasicFormatter < SemanticLogger::Formatters::Color # rubocop:disable Metrics/ClassLength #FIXME: remove rubocop disable here! | 
				 | 
			
		||||
  NAME_MAX_SIZE = 25 | 
				 | 
			
		||||
  TERMINAL_PREFIX = ENV['TERMINAL_PREFIX'].to_i || 0 | 
				 | 
			
		||||
  CONTENT_PREFIX = '   '.freeze | 
				 | 
			
		||||
  PREFIX_RAILS_INTERNAL = '⬂ '.freeze | 
				 | 
			
		||||
  PREFIX_RECORD_INTERNAL = '⬄ '.freeze | 
				 | 
			
		||||
  PREFIX_ACTION_INTERNAL = '⬃ '.freeze | 
				 | 
			
		||||
  PREFIX_BUG_INTERNAL = '➟ '.freeze | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  RENDERED_VIEW_DURATION = 100 | 
				 | 
			
		||||
  COMPLETED_DURATION = RENDERED_VIEW_DURATION * 5 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  ANSI_RESET = "\e[0m".freeze | 
				 | 
			
		||||
  ANSI_BOLD = "\e[1m".freeze | 
				 | 
			
		||||
  ANSI_DEBUG = "\e[90m".freeze | 
				 | 
			
		||||
  ANSI_INFO = SemanticLogger::AnsiColors::GREEN | 
				 | 
			
		||||
  ANSI_WARN = SemanticLogger::AnsiColors::YELLOW | 
				 | 
			
		||||
  ANSI_ERROR = "\e[91m".freeze | 
				 | 
			
		||||
  ANSI_NEUTRAL_INFO = SemanticLogger::AnsiColors::WHITE | 
				 | 
			
		||||
  ANSI_REVERSED_WARNING = "\e[0;30;43m".freeze | 
				 | 
			
		||||
  ANSI_REVERSED_ERROR = "\e[1;30;41m".freeze | 
				 | 
			
		||||
  ANSI_REVERSED_FATAL = "\e[1;30;41m".freeze | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  CONTENT_COLOR_MAP = ColorMap.new( | 
				 | 
			
		||||
    debug: ANSI_DEBUG, | 
				 | 
			
		||||
    info: ANSI_NEUTRAL_INFO, | 
				 | 
			
		||||
    warn: ANSI_REVERSED_WARNING, | 
				 | 
			
		||||
    error: ANSI_REVERSED_ERROR, | 
				 | 
			
		||||
    fatal: ANSI_REVERSED_FATAL | 
				 | 
			
		||||
  ) | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  # exclude log eagerly! | 
				 | 
			
		||||
  EXCLUDE_LAMBDA = lambda { |log| | 
				 | 
			
		||||
    if log.name == 'ActionView::Base' | 
				 | 
			
		||||
      !log.message.starts_with?('  Rendering') | 
				 | 
			
		||||
    elsif log.name == 'Rails' && !log.message.nil? | 
				 | 
			
		||||
      log.message.exclude?('Started GET "/rails/live/reload') | 
				 | 
			
		||||
    elsif log.name == 'ActiveRecord::Base' | 
				 | 
			
		||||
      log.message.exclude?('↳ lib/formatters/basic_formatter.rb') | 
				 | 
			
		||||
    else | 
				 | 
			
		||||
      true | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
  } | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def initialize | 
				 | 
			
		||||
    super(color_map: ColorMap.new( | 
				 | 
			
		||||
      debug: ANSI_DEBUG, | 
				 | 
			
		||||
      info: ANSI_INFO, | 
				 | 
			
		||||
      warn: ANSI_WARN, | 
				 | 
			
		||||
      error: ANSI_ERROR, | 
				 | 
			
		||||
      fatal: ANSI_ERROR | 
				 | 
			
		||||
    )) | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def message | 
				 | 
			
		||||
    return unless log.message | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    message = wrap_message(log.message) | 
				 | 
			
		||||
    ansi_wrap(message, CONTENT_COLOR_MAP[log.level]) | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def payload | 
				 | 
			
		||||
    return unless log.payload | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    lines = log.payload.ai(ruby19_syntax: true, indent: 2, object_id: false).split("\n") | 
				 | 
			
		||||
    first_line = lines.shift | 
				 | 
			
		||||
    space_prefix = first_line.match(/^\s*/) | 
				 | 
			
		||||
    lines = lines.map do |l| | 
				 | 
			
		||||
      "#{before_message(wrapped: true)}#{space_prefix}#{l}" | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
    result = lines.unshift(first_line).join("\n") | 
				 | 
			
		||||
    result.sub!(/\s*/) { |m| '-' * m.length } | 
				 | 
			
		||||
    result | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def level | 
				 | 
			
		||||
    case log.level | 
				 | 
			
		||||
    when :info, :debug then draw_rails ' ' | 
				 | 
			
		||||
    else draw_rails(log.level.to_s.chr.upcase) | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def name | 
				 | 
			
		||||
    ansi_wrap(log.name.truncate(NAME_MAX_SIZE).center(NAME_MAX_SIZE), ANSI_DEBUG) | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def exception | 
				 | 
			
		||||
    return unless log.exception | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    clazz = log.exception.class | 
				 | 
			
		||||
    message = log.exception.message | 
				 | 
			
		||||
    stack = backtrace(log.exception) | 
				 | 
			
		||||
    "#{ansi_wrap(clazz, ANSI_REVERSED_WARNING)} #{ansi_wrap(message, ANSI_REVERSED_ERROR)}#{stack}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def call(log, logger) | 
				 | 
			
		||||
    self.log = transform_log(log) | 
				 | 
			
		||||
    self.color = color_map[self.log.level] | 
				 | 
			
		||||
    self.logger = logger | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    before_message + [message, payload, exception].compact.join(' ') | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  private | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def ansi_wrap(text, ansi_code) | 
				 | 
			
		||||
    "#{ansi_code}#{text}#{color_map.clear}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def draw_rails(char) | 
				 | 
			
		||||
    ansi_wrap("╣#{char}╠", color) | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def continuation | 
				 | 
			
		||||
    draw_rails('┆') | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  # transform log before display | 
				 | 
			
		||||
  def transform_log(log) | 
				 | 
			
		||||
    case log.name | 
				 | 
			
		||||
    when 'ActionView::Base' then transform_action_view_base(log) | 
				 | 
			
		||||
    when 'Rails' then transform_rails_log(log) | 
				 | 
			
		||||
    when 'ActiveRecord::Base' | 
				 | 
			
		||||
      log.message = transform_active_record_message(log.message) | 
				 | 
			
		||||
      log | 
				 | 
			
		||||
    else log end | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_rails_log(log) | 
				 | 
			
		||||
    return log unless log.message | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    log.message = transform_rails_message(log.message.rstrip) | 
				 | 
			
		||||
    log.level = :debug if log.message =~ /^#{PREFIX_RAILS_INTERNAL}(Processing|Parameters)/ | 
				 | 
			
		||||
    log | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_active_record_message(message) | 
				 | 
			
		||||
    message = message.lstrip.sub(/^↳ /, 'Processed by ') | 
				 | 
			
		||||
    "#{PREFIX_RECORD_INTERNAL}#{message.lstrip}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_rails_message(message) | 
				 | 
			
		||||
    case message | 
				 | 
			
		||||
    when /^Completed/ then transform_rails_completed(message) | 
				 | 
			
		||||
    when /^Started/ then two_captures_last_as_bold(message, /(^Started \w* )"(.*?)"/) | 
				 | 
			
		||||
    when /^  Parameters/ then transform_rails_parameters(message) | 
				 | 
			
		||||
    when /^Processing/ then transform_rails_processing(message) | 
				 | 
			
		||||
    else message end | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_rails_processing(message) | 
				 | 
			
		||||
    message = two_captures_last_as_bold(message, /(^Processing by \w*#\w* as )(.*)/) | 
				 | 
			
		||||
    "#{PREFIX_RAILS_INTERNAL}#{message}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_rails_parameters(message) | 
				 | 
			
		||||
    parameters = message.lstrip.match(/Parameters: ({.*}$)/).match(1) | 
				 | 
			
		||||
    parameters = JSON.parse(parameters.gsub('=>', ':'), symbolize_names: true) | 
				 | 
			
		||||
    "#{PREFIX_RAILS_INTERNAL}Parameters: #{parameters.ai(ruby19_syntax: true, plain: true, multiline: false)}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_rails_completed(message) | 
				 | 
			
		||||
    m1, m2, m3, m4 = message.match(/^Completed (\d*) (.*) in (\d*)ms(.*)$/).captures | 
				 | 
			
		||||
    http_code = ansi_wrap("#{m1} #{m2}", ANSI_BOLD) | 
				 | 
			
		||||
    message = "Completed #{http_code} in #{m3}ms" | 
				 | 
			
		||||
    message += m4 if m3.to_i > COMPLETED_DURATION | 
				 | 
			
		||||
    message += "\n" if m1 =~ /^[23]/ | 
				 | 
			
		||||
    message | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def two_captures_last_as_bold(message, regex) | 
				 | 
			
		||||
    m1, m2 = message.match(regex).captures | 
				 | 
			
		||||
    "#{m1}#{ansi_wrap(m2, ANSI_BOLD)}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_action_view_base(log) | 
				 | 
			
		||||
    log.level, message = transform_log_debug_lstrip(log) | 
				 | 
			
		||||
    message = transform_rendered_message_with_filename(message) | 
				 | 
			
		||||
    message = transform_duration_strip(message) | 
				 | 
			
		||||
    log.message = "#{PREFIX_ACTION_INTERNAL}#{message}" | 
				 | 
			
		||||
    log | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_duration_strip(message) | 
				 | 
			
		||||
    md2 = message.match(/( \(Duration: \d+\.?\d?ms.*\))/) | 
				 | 
			
		||||
    md3 = md2.match(1).match(/Duration: (\d+\.?\d?)ms/) | 
				 | 
			
		||||
    duration = md3.match(1).to_f | 
				 | 
			
		||||
    duration < RENDERED_VIEW_DURATION ? md2.pre_match : message | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_rendered_message_with_filename(message) | 
				 | 
			
		||||
    md = message.match(/^Rendered( layout| collection of|) (.*?\.erb)/) | 
				 | 
			
		||||
    return message unless md | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    filename = "app/views/#{md.match(2)}" | 
				 | 
			
		||||
    "Rendered#{md.match(1)} #{filename}#{md.post_match}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def transform_log_debug_lstrip(log) | 
				 | 
			
		||||
    [:debug, log.message.lstrip] | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def wrap_message(message) | 
				 | 
			
		||||
    message, space_prefix = split_spaces_in_front(message) | 
				 | 
			
		||||
    message = Wrapper.wrap("#{CONTENT_COLOR_MAP[log.level]}#{message}", | 
				 | 
			
		||||
                           before_message(true) + space_prefix.to_s, | 
				 | 
			
		||||
                           compute_useful_length - space_prefix.length) | 
				 | 
			
		||||
    "#{space_prefix}#{message}" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def split_spaces_in_front(message) | 
				 | 
			
		||||
    md = message.match(/^\s*/) | 
				 | 
			
		||||
    [md.post_match, md.match(0)] | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def compute_useful_length | 
				 | 
			
		||||
    IO.console.winsize[1] - TERMINAL_PREFIX - before_message.length + CONTENT_PREFIX.length + 12 | 
				 | 
			
		||||
  rescue StandardError | 
				 | 
			
		||||
    100 # FIXME: CONSTANTIZE, only useful in DEBUGGER, no IO.console detected! | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def before_message(wrapped = false) | 
				 | 
			
		||||
    [name, wrapped ? continuation : level, tags, named_tags, duration].compact.join(' ') + CONTENT_PREFIX | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def backtrace(exception) | 
				 | 
			
		||||
    root_path = Rails.root.to_s | 
				 | 
			
		||||
    stack = exception.backtrace.select { |line| line.starts_with?(root_path) } | 
				 | 
			
		||||
    stack = stack.map { |line| line.delete_prefix("#{root_path}/") } | 
				 | 
			
		||||
    return "\n" unless stack.count.positive? | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    stack_message = PREFIX_BUG_INTERNAL | 
				 | 
			
		||||
    stack_message += stack.join("\n#{before_message}#{ANSI_ERROR}#{PREFIX_BUG_INTERNAL}") | 
				 | 
			
		||||
    "\n#{before_message}#{ansi_wrap(stack_message, ANSI_ERROR)}\n" | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
end | 
				 | 
			
		||||
@ -1,171 +0,0 @@ | 
			
		|||||
╣ ╠ | 
				 | 
			
		||||
╣ǁ╠ | 
				 | 
			
		||||
╣▹╠ | 
				 | 
			
		||||
╣◉╠ | 
				 | 
			
		||||
╣◼╠ | 
				 | 
			
		||||
╙╨╜ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣▥╠ | 
				 | 
			
		||||
╣☉╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣☀╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
🛑 | 
				 | 
			
		||||
🚨 | 
				 | 
			
		||||
🛟 | 
				 | 
			
		||||
🚧 | 
				 | 
			
		||||
🔥 | 
				 | 
			
		||||
💣 | 
				 | 
			
		||||
⛔ | 
				 | 
			
		||||
↘ | 
				 | 
			
		||||
↪ | 
				 | 
			
		||||
☑ | 
				 | 
			
		||||
✅ | 
				 | 
			
		||||
❌ | 
				 | 
			
		||||
✔ | 
				 | 
			
		||||
⚪ | 
				 | 
			
		||||
🏁 | 
				 | 
			
		||||
☠️ | 
				 | 
			
		||||
😵💫 | 
				 | 
			
		||||
👁 | 
				 | 
			
		||||
👣 | 
				 | 
			
		||||
👉 | 
				 | 
			
		||||
💫 | 
				 | 
			
		||||
🎗 | 
				 | 
			
		||||
🚆 | 
				 | 
			
		||||
⚒ | 
				 | 
			
		||||
📌 | 
				 | 
			
		||||
📎 | 
				 | 
			
		||||
✹♾🔥🚧 | 
				 | 
			
		||||
➾✮✵✹ | 
				 | 
			
		||||
☇ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙─╜ 200 OK  | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙ ╜ 300 redirect | 
				 | 
			
		||||
╙⸾╜ 300 redirect | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙ ╜ | 
				 | 
			
		||||
╓ ╖ | 
				 | 
			
		||||
╣⸾╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙⍑╜ | 
				 | 
			
		||||
╓─╖ | 
				 | 
			
		||||
╣⸾╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╓─╖ | 
				 | 
			
		||||
╣⸾╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
╣─╠ | 
				 | 
			
		||||
╙┻╜ | 
				 | 
			
		||||
╓─╖ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙╨╜ 400 client error | 
				 | 
			
		||||
╙ǁ╜ 400 client error | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╙⍑╜ 500 servererror | 
				 | 
			
		||||
╙⯶╜ 500 servererror | 
				 | 
			
		||||
╙ ╜ 500 servererror | 
				 | 
			
		||||
 | 
				 | 
			
		||||
00:20:45 web.1  |        Rails         ╣ ╠  Redirected to http://127.0.0.1:7500/scores/1 | 
				 | 
			
		||||
00:20:45 web.1  |        Rails         ╙║╜  Completed 303 See Other in 10ms (ActiveRecord: 0.7ms | Allocations: 2390) | 
				 | 
			
		||||
00:20:45 web.1  |        Rails         ╓║╖  Started GET "/scores/1" for 127.0.0.1 at 2024-06-07 00:20:45 +0400 | 
				 | 
			
		||||
00:20:45 web.1  |        Rails         ╣ ╠  Processing by ScoresController#show as TURBO_STREAM | 
				 | 
			
		||||
00:20:45 web.1  |        Rails         ╣ ╠    Parameters: {"id"=>"1"} | 
				 | 
			
		||||
 | 
				 | 
			
		||||
00:37:27 web.1  |        Rails         ╙║╜  Completed 303 See Other in 16ms (ActiveRecord: 0.8ms | Allocations: 2395) | 
				 | 
			
		||||
00:37:27 web.1  |        Rails         ╓║╖  Started GET "/scores/1" for 127.0.0.1 at 2024-06-07 00:37:27 +0400 | 
				 | 
			
		||||
00:37:27 web.1  |        Rails         ╣ ╠  Processing by ScoresController#show as TURBO_STREAM | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣ ╠ | 
				 | 
			
		||||
╣⍑╠ | 
				 | 
			
		||||
╣⍑╠ | 
				 | 
			
		||||
╣⯶╠ | 
				 | 
			
		||||
╣⸾╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣┻╠ | 
				 | 
			
		||||
╓─╖ | 
				 | 
			
		||||
╣║╠ | 
				 | 
			
		||||
╣║╠ | 
				 | 
			
		||||
╣╎╠ | 
				 | 
			
		||||
╣┊╠ | 
				 | 
			
		||||
╣┆╠ | 
				 | 
			
		||||
╣│╠ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣⇰╠ | 
				 | 
			
		||||
╣⭪╠ | 
				 | 
			
		||||
╣⥱╠ | 
				 | 
			
		||||
╣⇴╠ | 
				 | 
			
		||||
╣⇲╠ | 
				 | 
			
		||||
╣⇥╠ | 
				 | 
			
		||||
╣⇄╠ | 
				 | 
			
		||||
╣⇣╠ | 
				 | 
			
		||||
╙╨╜ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
╣⍾╠ | 
				 | 
			
		||||
╣⬸╠ | 
				 | 
			
		||||
╣☐╠ | 
				 | 
			
		||||
╣☑╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣☒╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣⚐╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣⛭╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣➾╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣⤫╠ | 
				 | 
			
		||||
╙─╜ | 
				 | 
			
		||||
⦹ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
╣⦾╠ | 
				 | 
			
		||||
 ⨈ | 
				 | 
			
		||||
⦾ | 
				 | 
			
		||||
⦿ | 
				 | 
			
		||||
⮾ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
ⵙⵞ | 
				 | 
			
		||||
⸾ | 
				 | 
			
		||||
╣⫨╠ | 
				 | 
			
		||||
╣⤫╠ | 
				 | 
			
		||||
╣⫫╠ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
⫫ | 
				 | 
			
		||||
⫨ | 
				 | 
			
		||||
⮓ | 
				 | 
			
		||||
⮱ | 
				 | 
			
		||||
⮾ | 
				 | 
			
		||||
⇤ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
⛭ | 
				 | 
			
		||||
➾ | 
				 | 
			
		||||
➟ | 
				 | 
			
		||||
⬎ | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  | 
				 | 
			
		||||
 | 
				 | 
			
		||||
@ -1,89 +0,0 @@ | 
			
		|||||
class Wrapper | 
				 | 
			
		||||
  def self.wrap(text, prefix = ' > ', length = 80) | 
				 | 
			
		||||
    text = carriage_return_filler(text.chomp(''), length) | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    pure = '' | 
				 | 
			
		||||
    ansi_code = {} | 
				 | 
			
		||||
    while (md = text.match(/\e\[\d+;?\d*m/)) | 
				 | 
			
		||||
      pos = md.begin(0) + pure.length | 
				 | 
			
		||||
      pure += md.pre_match | 
				 | 
			
		||||
      text = md.post_match | 
				 | 
			
		||||
      append_in_hash(ansi_code, pos, md.match(0)) | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
    pure += text | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    offset = 0 | 
				 | 
			
		||||
    ansi_extra = {} | 
				 | 
			
		||||
    rows = pure.length / length | 
				 | 
			
		||||
    last_code = nil | 
				 | 
			
		||||
    rows.times do |i| | 
				 | 
			
		||||
      pos = (i + 1) * length | 
				 | 
			
		||||
      last_code = last_ansi_by_range(ansi_code, last_code, offset, pos) | 
				 | 
			
		||||
      if last_code | 
				 | 
			
		||||
        append_in_hash(ansi_extra, pos, "\e[0m\n#{prefix}#{last_code}") | 
				 | 
			
		||||
      elsif pos < pure.length | 
				 | 
			
		||||
        append_in_hash(ansi_extra, pos, "\n#{prefix}") | 
				 | 
			
		||||
      end | 
				 | 
			
		||||
      offset = pos + 1 | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
    ansi_extra.each_pair do |k, v| | 
				 | 
			
		||||
      append_in_hash(ansi_code, k, v.first) | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    ansi_code = ansi_code.sort | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    final = pure | 
				 | 
			
		||||
    offset = 0 | 
				 | 
			
		||||
    ansi_code.each do |k, v| | 
				 | 
			
		||||
      insert_text = v.join | 
				 | 
			
		||||
      final = final.insert(k + offset, insert_text) | 
				 | 
			
		||||
      offset += insert_text.length | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    final | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  private | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def self.carriage_return_filler(text, length) | 
				 | 
			
		||||
    lines = text.split("\n") | 
				 | 
			
		||||
    return text if lines.count < 2 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
    last_ansi = nil | 
				 | 
			
		||||
    final = lines.map do |line| | 
				 | 
			
		||||
      fill_count = length - line.length | 
				 | 
			
		||||
      current = line | 
				 | 
			
		||||
      result = last_ansi ? "#{last_ansi}#{line}" : line | 
				 | 
			
		||||
      last_ansi = nil if last_ansi == "\e[0m" | 
				 | 
			
		||||
      while (md = current.match(/\e\[\d+;?\d*m/)) | 
				 | 
			
		||||
        last_ansi = md.to_s | 
				 | 
			
		||||
        fill_count += last_ansi.length | 
				 | 
			
		||||
        current = md.post_match | 
				 | 
			
		||||
      end | 
				 | 
			
		||||
      result = "#{result}\e[0m" if last_ansi | 
				 | 
			
		||||
      next result if fill_count < 1 | 
				 | 
			
		||||
 | 
				 | 
			
		||||
      "#{result}#{' ' * fill_count}" | 
				 | 
			
		||||
    end.join | 
				 | 
			
		||||
    last_ansi ? "#{final}\e[0m" : final | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def self.last_ansi_by_range(ansi_code, last_code, offset, pos) | 
				 | 
			
		||||
    pos.downto(offset) do |i| | 
				 | 
			
		||||
      next unless ansi_code.has_key?(i) | 
				 | 
			
		||||
 | 
				 | 
			
		||||
      result = ansi_code[i].last | 
				 | 
			
		||||
      result = nil if result == "\e[0m" | 
				 | 
			
		||||
      return result | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
    last_code | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
 | 
				 | 
			
		||||
  def self.append_in_hash(hash, key, value) | 
				 | 
			
		||||
    if hash.has_key?(key) | 
				 | 
			
		||||
      hash[key] << value | 
				 | 
			
		||||
    else | 
				 | 
			
		||||
      hash[key] = [value] | 
				 | 
			
		||||
    end | 
				 | 
			
		||||
  end | 
				 | 
			
		||||
end | 
				 | 
			
		||||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue