File Manager

Path: /proc/self/root/opt/chef/embedded/lib/ruby/gems/2.7.0/gems/pastel-0.7.4/lib/pastel/

Viewing File: color.rb

# encoding: utf-8

require 'equatable'

require_relative 'ansi'

module Pastel
  # A class responsible for coloring strings.
  class Color
    include Equatable
    include ANSI

    # All color aliases
    ALIASES = {}

    # Match all color escape sequences
    ANSI_COLOR_REGEXP = /\x1b+(\[|\[\[)[0-9;:?]+m/mo.freeze

    attr_reader :enabled
    alias_method :enabled?, :enabled

    attr_reader :eachline

    # Initialize a Terminal Color
    #
    # @api public
    def initialize(options = {})
      @enabled  = options[:enabled]
      @eachline = options.fetch(:eachline) { false }
      @cache    = {}
    end

    # Disable coloring of this terminal session
    #
    # @api public
    def disable!
      @enabled = false
    end

    # Apply ANSI color to the given string.
    #
    # Wraps eachline with clear escape.
    #
    # @param [String] string
    #   text to add ANSI strings
    #
    # @param [Array[Symbol]] colors
    #   the color names
    #
    # @example
    #   color.decorate "text", :yellow, :on_green, :underline
    #
    # @return [String]
    #   the colored string
    #
    # @api public
    def decorate(string, *colors)
      return string if blank?(string) || !enabled || colors.empty?

      ansi_colors = lookup(*colors.dup.uniq)
      if eachline
        string.dup.split(eachline).map! do |line|
          apply_codes(line, ansi_colors)
        end.join(eachline)
      else
        apply_codes(string.dup, ansi_colors)
      end
    end

    # Apply escape codes to the string
    #
    # @param [String] string
    #   the string to apply escapes to
    # @param [Strin] ansi_colors
    #   the ansi colors to apply
    #
    # @return [String]
    #   return the string surrounded by escape codes
    #
    # @api private
    def apply_codes(string, ansi_colors)
      "#{ansi_colors}#{string.gsub(/(\e\[0m)([^\e]+)$/, "\\1#{ansi_colors}\\2")}\e[0m"
    end

    # Reset sequence
    #
    # @api public
    def clear
      lookup(:clear)
    end

    # Strip ANSI color codes from a string.
    #
    # Only ANSI color codes are removed, not movement codes or
    # other escapes sequences are stripped.
    #
    # @param [Array[String]] strings
    #   a string or array of strings to sanitize
    #
    # @example
    #   strip("foo\e[1mbar\e[0m")  # => "foobar"
    #
    # @return [String]
    #
    # @api public
    def strip(*strings)
      modified = strings.map { |string| string.dup.gsub(ANSI_COLOR_REGEXP, '') }
      modified.size == 1 ? modified[0] : modified
    end

    # Check if string has color escape codes
    #
    # @param [String] string
    #   the string to check for color strings
    #
    # @return [Boolean]
    #   true when string contains color codes, false otherwise
    #
    # @api public
    def colored?(string)
      !ANSI_COLOR_REGEXP.match(string).nil?
    end

    # Find the escape code for a given set of color attributes
    #
    # @example
    #   color.lookup(:red, :on_green) # => "\e[31;42m"
    #
    # @param [Array[Symbol]] colors
    #   the list of color name(s) to lookup
    #
    # @return [String]
    #   the ANSI code(s)
    #
    # @raise [InvalidAttributeNameError]
    #   exception raised for any invalid color name
    #
    # @api private
    def lookup(*colors)
      @cache.fetch(colors) do
        @cache[colors] = "\e[#{code(*colors).join(';')}m"
      end
    end

    # Return raw color code without embeding it into a string.
    #
    # @return [Array[String]]
    #   ANSI escape codes
    #
    # @api public
    def code(*colors)
      attribute = []
      colors.each do |color|
        value = ANSI::ATTRIBUTES[color] || ALIASES[color]
        if value
          attribute << value
        else
          validate(color)
        end
      end
      attribute
    end

    # Expose all ANSI color names and their codes
    #
    # @return [Hash[Symbol]]
    #
    # @api public
    def styles
      ANSI::ATTRIBUTES.merge(ALIASES)
    end

    # List all available style names
    #
    # @return [Array[Symbol]]
    #
    # @api public
    def style_names
      styles.keys
    end

    # Check if provided colors are known colors
    #
    # @param [Array[Symbol,String]]
    #   the list of colors to check
    #
    # @example
    #   valid?(:red)   # => true
    #
    # @return [Boolean]
    #   true if all colors are valid, false otherwise
    #
    # @api public
    def valid?(*colors)
      colors.all? { |color| style_names.include?(color.to_sym) }
    end

    # Define a new colors alias
    #
    # @param [String] alias_name
    #   the colors alias to define
    # @param [Array[Symbol,String]] color
    #   the colors the alias will correspond to
    #
    # @return [Array[String]]
    #   the standard color values of the alias
    #
    # @api public
    def alias_color(alias_name, *colors)
      validate(*colors)

      if !(alias_name.to_s =~ /^[\w]+$/)
        fail InvalidAliasNameError, "Invalid alias name `#{alias_name}`"
      elsif ANSI::ATTRIBUTES[alias_name]
        fail InvalidAliasNameError,
             "Cannot alias standard color `#{alias_name}`"
      end

      ALIASES[alias_name.to_sym] = colors.map(&ANSI::ATTRIBUTES.method(:[]))
      colors
    end

    private

    # Check if value contains anything to style
    #
    # @return [Boolean]
    #
    # @api private
    def blank?(value)
      value.nil? || !value.respond_to?(:to_str) || value.to_s == ''
    end

    # @api private
    def validate(*colors)
      return if valid?(*colors)
      fail InvalidAttributeNameError, 'Bad style or unintialized constant, ' \
        " valid styles are: #{style_names.join(', ')}."
    end
  end # Color
end # TTY