lib/cucumber/term/ansicolor.rb



# frozen_string_literal: true

module Cucumber
  module Term
    # This module allows to colorize text using ANSI escape sequences.
    #
    # Include the module in your class and use its methods to colorize text.
    #
    # Example:
    #
    #   require 'cucumber/term/ansicolor'
    #
    #   class MyFormatter
    #     include Cucumber::Term::ANSIColor
    #
    #     def initialize(config)
    #       $stdout.puts yellow("Initializing formatter")
    #       $stdout.puts green("Coloring is active \o/") if Cucumber::Term::ANSIColor.coloring?
    #       $stdout.puts grey("Feature path:") + blue(bold(config.feature_dirs))
    #     end
    #   end
    #
    # To see what colours and effects are available, just run this in your shell:
    #
    #   ruby -e "require 'rubygems'; require 'cucumber/term/ansicolor'; puts Cucumber::Term::ANSIColor.attributes"
    #
    module ANSIColor
      # :stopdoc:
      ATTRIBUTES = [
        [:clear,         0],
        [:reset,         0], # synonym for :clear
        [:bold,          1],
        [:dark,          2],
        [:italic,        3], # not widely implemented
        [:underline,     4],
        [:underscore,    4], # synonym for :underline
        [:blink,         5],
        [:rapid_blink,   6], # not widely implemented
        [:negative,      7], # no reverse because of String#reverse
        [:concealed,     8],
        [:strikethrough, 9], # not widely implemented
        [:black,         30],
        [:red,           31],
        [:green,         32],
        [:yellow,        33],
        [:blue,          34],
        [:magenta,       35],
        [:cyan,          36],
        [:white,         37],
        [:grey,          90],
        [:on_black,      40],
        [:on_red,        41],
        [:on_green,      42],
        [:on_yellow,     43],
        [:on_blue,       44],
        [:on_magenta,    45],
        [:on_cyan,       46],
        [:on_white,      47]
      ].freeze

      ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
      # :startdoc:

      # Regular expression that is used to scan for ANSI-sequences while
      # uncoloring strings.
      COLORED_REGEXP = /\e\[(?:[34][0-7]|[0-9])?m/.freeze

      @coloring = true

      class << self
        # Turns the coloring on or off globally, so you can easily do
        # this for example:
        #  Cucumber::Term::ANSIColor::coloring = $stdout.isatty
        attr_accessor :coloring

        # Returns true, if the coloring function of this module
        # is switched on, false otherwise.
        alias coloring? :coloring

        def included(klass)
          return unless klass == String

          ATTRIBUTES.delete(:clear)
          ATTRIBUTE_NAMES.delete(:clear)
        end
      end

      ATTRIBUTES.each do |color_name, color_code|
        define_method(color_name) do |text = nil, &block|
          if block
            colorize(block.call, color_code)
          elsif text
            colorize(text, color_code)
          elsif respond_to?(:to_str)
            colorize(to_str, color_code)
          else
            colorize(nil, color_code) # switch coloration on
          end
        end
      end

      # Returns an uncolored version of the string
      # ANSI-sequences are stripped from the string.
      def uncolored(text = nil)
        if block_given?
          uncolorize(yield)
        elsif text
          uncolorize(text)
        elsif respond_to?(:to_str)
          uncolorize(to_str)
        else
          ''
        end
      end

      # Returns an array of all Cucumber::Term::ANSIColor attributes as symbols.
      def attributes
        ATTRIBUTE_NAMES
      end

      private

      def colorize(text, color_code)
        return String.new(text || '') unless Cucumber::Term::ANSIColor.coloring?
        return "\e[#{color_code}m" unless text

        "\e[#{color_code}m#{text}\e[0m"
      end

      def uncolorize(string)
        string.gsub(COLORED_REGEXP, '')
      end
    end
  end
end