gitlabhq/gitlabhq

View on GitHub
lib/gitlab/ci/ansi2json/parser.rb

Summary

Maintainability
C
1 day
Test Coverage
# frozen_string_literal: true

# This Parser translates ANSI escape codes into human readable format.
# It considers color and format changes.
# Inspired by http://en.wikipedia.org/wiki/ANSI_escape_code
module Gitlab
  module Ci
    module Ansi2json
      class Parser
        # keys represent the trailing digit in color changing command (30-37, 40-47, 90-97. 100-107)
        COLOR = {
          0 => 'black', # not that this is gray in the intense color table
          1 => 'red',
          2 => 'green',
          3 => 'yellow',
          4 => 'blue',
          5 => 'magenta',
          6 => 'cyan',
          7 => 'white' # not that this is gray in the dark (aka default) color table
        }.freeze

        STYLE_SWITCHES = {
          bold:       0x01,
          italic:     0x02,
          underline:  0x04,
          conceal:    0x08,
          cross:      0x10
        }.freeze

        def self.bold?(mask)
          mask & STYLE_SWITCHES[:bold] != 0
        end

        def self.matching_formats(mask)
          formats = []
          STYLE_SWITCHES.each do |text_format, flag|
            formats << "term-#{text_format}" if mask & flag != 0
          end

          formats
        end

        def initialize(command, ansi_stack = nil)
          @command = command
          @ansi_stack = ansi_stack
        end

        def changes
          if self.respond_to?("on_#{@command}")
            send("on_#{@command}", @ansi_stack) # rubocop:disable GitlabSecurity/PublicSend
          end
        end

        # rubocop:disable Style/SingleLineMethods
        def on_0(_) { reset: true } end

        def on_1(_) { enable: STYLE_SWITCHES[:bold] } end

        def on_3(_) { enable: STYLE_SWITCHES[:italic] } end

        def on_4(_) { enable: STYLE_SWITCHES[:underline] } end

        def on_8(_) { enable: STYLE_SWITCHES[:conceal] } end

        def on_9(_) { enable: STYLE_SWITCHES[:cross] } end

        def on_21(_) { disable: STYLE_SWITCHES[:bold] } end

        def on_22(_) { disable: STYLE_SWITCHES[:bold] } end

        def on_23(_) { disable: STYLE_SWITCHES[:italic] } end

        def on_24(_) { disable: STYLE_SWITCHES[:underline] } end

        def on_28(_) { disable: STYLE_SWITCHES[:conceal] } end

        def on_29(_) { disable: STYLE_SWITCHES[:cross] } end

        def on_30(_) { fg: fg_color(0) } end

        def on_31(_) { fg: fg_color(1) } end

        def on_32(_) { fg: fg_color(2) } end

        def on_33(_) { fg: fg_color(3) } end

        def on_34(_) { fg: fg_color(4) } end

        def on_35(_) { fg: fg_color(5) } end

        def on_36(_) { fg: fg_color(6) } end

        def on_37(_) { fg: fg_color(7) } end

        def on_38(stack) { fg: fg_color_256(stack) } end

        def on_39(_) { fg: nil } end

        def on_40(_) { bg: bg_color(0) } end

        def on_41(_) { bg: bg_color(1) } end

        def on_42(_) { bg: bg_color(2) } end

        def on_43(_) { bg: bg_color(3) } end

        def on_44(_) { bg: bg_color(4) } end

        def on_45(_) { bg: bg_color(5) } end

        def on_46(_) { bg: bg_color(6) } end

        def on_47(_) { bg: bg_color(7) } end

        def on_48(stack) { bg: bg_color_256(stack) } end

        def on_49(_) { bg: nil } end

        def on_90(_) { fg: fg_color(0, 'l') } end

        def on_91(_) { fg: fg_color(1, 'l') } end

        def on_92(_) { fg: fg_color(2, 'l') } end

        def on_93(_) { fg: fg_color(3, 'l') } end

        def on_94(_) { fg: fg_color(4, 'l') } end

        def on_95(_) { fg: fg_color(5, 'l') } end

        def on_96(_) { fg: fg_color(6, 'l') } end

        def on_97(_) { fg: fg_color(7, 'l') } end

        def on_99(_) { fg: fg_color(9, 'l') } end

        def on_100(_) { fg: bg_color(0, 'l') } end

        def on_101(_) { fg: bg_color(1, 'l') } end

        def on_102(_) { fg: bg_color(2, 'l') } end

        def on_103(_) { fg: bg_color(3, 'l') } end

        def on_104(_) { fg: bg_color(4, 'l') } end

        def on_105(_) { fg: bg_color(5, 'l') } end

        def on_106(_) { fg: bg_color(6, 'l') } end

        def on_107(_) { fg: bg_color(7, 'l') } end

        def on_109(_) { fg: bg_color(9, 'l') } end
        # rubocop:enable Style/SingleLineMethods

        def fg_color(color_index, prefix = nil)
          term_color_class(color_index, ['fg', prefix])
        end

        def fg_color_256(command_stack)
          xterm_color_class(command_stack, 'fg')
        end

        def bg_color(color_index, prefix = nil)
          term_color_class(color_index, ['bg', prefix])
        end

        def bg_color_256(command_stack)
          xterm_color_class(command_stack, 'bg')
        end

        def term_color_class(color_index, prefix)
          color_name = COLOR[color_index]
          return if color_name.nil?

          color_class(['term', prefix, color_name])
        end

        def xterm_color_class(command_stack, prefix)
          # the 38 and 48 commands have to be followed by "5" and the color index
          return unless command_stack.length >= 2
          return unless command_stack[0] == "5"

          command_stack.shift # ignore the "5" command
          color_index = command_stack.shift.to_i

          return unless color_index >= 0
          return unless color_index <= 255

          color_class(["xterm", prefix, color_index])
        end

        def color_class(segments)
          [segments].flatten.compact.join('-')
        end
      end
    end
  end
end