class CLI::UI::Formatter

Constants

BEGIN_EXPR
DISCARD_BRACES
END_EXPR
LITERAL_BRACES
SCAN_BODY
SCAN_FUNCNAME
SCAN_GLYPH
SCAN_WIDGET
SGR_MAP

Available mappings of formattings To use any of them, you can use {{<key>:<string>}} There are presentational (colours and formatters) and semantic (error, info, command) formatters available

Public Class Methods

new(text) click to toggle source

Initialize a formatter with text.

Attributes
  • text - the text to format

# File lib/cli/ui/formatter.rb, line 70
def initialize(text)
  @text = text
end

Public Instance Methods

format(sgr_map = SGR_MAP, enable_color: CLI::UI.enable_color?) click to toggle source

Format the text using a map.

Attributes
  • sgr_map - the mapping of the formattings. Defaults to SGR_MAP

Options
  • :enable_color - enable color output? Default is true unless output is redirected

# File lib/cli/ui/formatter.rb, line 84
def format(sgr_map = SGR_MAP, enable_color: CLI::UI.enable_color?)
  @nodes = []
  stack = parse_body(StringScanner.new(@text))
  prev_fmt = nil
  content = @nodes.each_with_object(+'') do |(text, fmt), str|
    if prev_fmt != fmt && enable_color
      text = apply_format(text, fmt, sgr_map)
    end
    str << text
    prev_fmt = fmt
  end

  stack.reject! { |e| e == LITERAL_BRACES }

  return content unless enable_color
  return content if stack == prev_fmt

  unless stack.empty? && (@nodes.size.zero? || @nodes.last[1].empty?)
    content << apply_format('', stack, sgr_map)
  end
  content
end

Private Instance Methods

apply_format(text, fmt, sgr_map) click to toggle source
# File lib/cli/ui/formatter.rb, line 109
def apply_format(text, fmt, sgr_map)
  sgr = fmt.each_with_object(+'0') do |name, str|
    next if name == LITERAL_BRACES
    begin
      str << ';' << sgr_map.fetch(name)
    rescue KeyError
      raise FormatError.new(
        "invalid format specifier: #{name}",
        @text,
        -1
      )
    end
  end
  CLI::UI::ANSI.sgr(sgr) + text
end
emit(text, stack) click to toggle source
# File lib/cli/ui/formatter.rb, line 186
def emit(text, stack)
  return if text.nil? || text.empty?
  @nodes << [text, stack.reject { |n| n == LITERAL_BRACES }]
end
parse_body(sc, stack = []) click to toggle source
# File lib/cli/ui/formatter.rb, line 167
def parse_body(sc, stack = [])
  match = sc.scan(SCAN_BODY)
  if match&.end_with?(BEGIN_EXPR)
    emit(match[DISCARD_BRACES], stack)
    parse_expr(sc, stack)
  elsif match&.end_with?(END_EXPR)
    emit(match[DISCARD_BRACES], stack)
    if stack.pop == LITERAL_BRACES
      emit('}}', stack)
    end
    parse_body(sc, stack)
  elsif match
    emit(match, stack)
  else
    emit(sc.rest, stack)
  end
  stack
end
parse_expr(sc, stack) click to toggle source
# File lib/cli/ui/formatter.rb, line 125
def parse_expr(sc, stack)
  if (match = sc.scan(SCAN_GLYPH))
    glyph_handle = match[0]
    begin
      glyph = Glyph.lookup(glyph_handle)
      emit(glyph.char, [glyph.color.name.to_s])
    rescue Glyph::InvalidGlyphHandle
      index = sc.pos - 2 # rewind past '}}'
      raise FormatError.new(
        "invalid glyph handle at index #{index}: '#{glyph_handle}'",
        @text,
        index
      )
    end
  elsif (match = sc.scan(SCAN_WIDGET))
    match_data = SCAN_WIDGET.match(match) # Regexp.last_match doesn't work here
    widget_handle = match_data['handle']
    begin
      widget = Widgets.lookup(widget_handle)
      emit(widget.call(match_data['args']), stack)
    rescue Widgets::InvalidWidgetHandle
      index = sc.pos - 2 # rewind past '}}'
      raise(FormatError.new(
        "invalid widget handle at index #{index}: '#{widget_handle}'",
        @text, index,
      ))
    end
  elsif (match = sc.scan(SCAN_FUNCNAME))
    funcname = match.chop
    stack.push(funcname)
  else
    # We read a {{ but it's not apparently Formatter syntax.
    # We could error, but it's nicer to just pass through as text.
    # We do kind of assume that the text will probably have balanced
    # pairs of {{ }} at least.
    emit('{{', stack)
    stack.push(LITERAL_BRACES)
  end
  parse_body(sc, stack)
  stack
end