# frozen_string_literal: true module Doing ## ## Template string formatting ## class TemplateString < String class ::String ## ## Extract the longest valid color from a string. ## ## Allows %colors to bleed into other text and still ## be recognized, e.g. %greensomething still finds ## %green. ## ## @return [String] a valid color name ## @api private def validate_color valid_color = nil compiled = '' split('').each do |char| compiled += char valid_color = compiled if Color.attributes.include?(compiled.to_sym) end valid_color end end attr_reader :original include Color def initialize(string, placeholders: {}, force_color: false, wrap_width: 0, color: '', tags_color: '', reset: '') Color.coloring = true if force_color @colors = nil @original = string super(Color.reset + string) placeholders.each { |k, v| fill(k, v, wrap_width: wrap_width, color: color, tags_color: tags_color) } end ## ## Test if string contains any valid %colors ## ## @return [Boolean] True if colors, False otherwise. ## def colors? scan(/%([a-z]+)/).each do return true if Regexp.last_match(1).validate_color end false end def reparse @parsed_colors = nil end ## ## Return string with %colors replaced with escape codes ## ## @return [String] colorized string ## def colored reparse parsed_colors[:string].apply_colors(parsed_colors[:colors]) end ## ## Remove all valid %colors from string ## ## @return [String] cleaned string ## def raw parsed_colors[:string].uncolor end def parsed_colors @parsed_colors ||= parse_colors end ## ## Parse a template string for %colors and return a hash ## of colors and string locations ## ## @return [Hash] Uncolored string and array of colors and locations def parse_colors working = dup color_array = [] scan(/(?-?\d+)?(?:\^(?.))?(?:(?[ _t]|[^a-z0-9])(?\d+))?(?.[ _t]?)?#{placeholder.sub(/^%/, '')}(?.*?)$/ ph = raw.match(rx) return unless ph placeholder_offset = ph.begin(0) last_colors = parsed_colors[:colors].select { |v| v[:index] <= placeholder_offset + 4 } last_color = last_colors.map { |v| v[:color] }.pop(3).join('') sub!(rx) do m = Regexp.last_match after = m['after'] if value.nil? || value.empty? after else pad = m['width'].to_i mark = m['mchar'] || '' if placeholder == 'shortdate' && m['width'].nil? pad = 13 end indent = nil if m['ichar'] char = m['ichar'] =~ /t/ ? "\t" : ' ' indent = char * m['icount'].to_i end indent ||= placeholder =~ /^title/ ? '' : "\t" prefix = m['prefix'] if placeholder =~ /^title/ color = last_color + color if wrap_width.positive? || pad.positive? width = pad.positive? ? pad : wrap_width out = value.gsub(/%/, '\%').strip.wrap(width, pad: pad, indent: indent, offset: placeholder_offset, prefix: prefix, color: color, after: after, reset: reset, pad_first: false) out.highlight_tags!(tags_color, last_color: color) if tags_color && !tags_color.empty? out else out = format("%s%s%#{pad}s%s", prefix, color, value.gsub(/%/, '\%').sub(/\s*$/, ''), after) out.highlight_tags!(tags_color, last_color: color) if tags_color && !tags_color.empty? out end elsif placeholder =~ /^note/ if wrap_width.positive? || pad.positive? width = pad.positive? ? pad : wrap_width outstring = value.map do |l| if l.empty? ' ' else line = l.gsub(/%/, '\%').strip.wrap(width, pad: pad, indent: indent, offset: 0, prefix: prefix, color: last_color, after: after, reset: reset, pad_first: true) line.highlight_tags!(tags_color, last_color: last_color) unless !tags_color || tags_color.nil? || tags_color.empty? "#{line} " end end.join("\n") "\n#{last_color}#{mark}#{outstring} " else out = format("\n%s%s%s%#{pad}s%s", indent, prefix, last_color, value.join("\n#{indent}#{prefix}").gsub(/%/, '\%').sub(/\s*$/, ''), after) out.highlight_tags!(tags_color, last_color: last_color) if tags_color && !tags_color.empty? out end else format("%s%#{pad}s%s", prefix, value.gsub(/%/, '\%').sub(/\s*$/, ''), after) end end end @parsed_colors = parse_colors end end end