Class: Styles::Colors
- Inherits:
-
Object
- Object
- Styles::Colors
- Defined in:
- lib/styles/colors.rb
Overview
Basically a wrapper around Term::ANSIColor but also adds combination foreground and background colors (e.g. :red_on_white). Returns nil with an invalid color specification.
Constant Summary collapse
- CSS_TO_ANSI_VALUES =
Map CSS-style value to ANSI code name, where they are different
{ :line_through => :strikethrough }.freeze
- FOREGROUND_COLOR_VALUES =
[ :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white ].freeze
- BACKGROUND_COLOR_VALUES =
[ :on_black, :on_red, :on_green, :on_yellow, :on_blue, :on_magenta, :on_cyan, :on_white ].freeze
- TEXT_DECORATION_VALUES =
[:underline, :strikethrough, :blink].freeze
- COLOR_VALUES =
(FOREGROUND_COLOR_VALUES + BACKGROUND_COLOR_VALUES).freeze
- OTHER_STYLE_VALUES =
[:bold, :italic, :underline, :underscore, :blink, :strikethrough]
- NEGATIVE_PSEUDO_VALUES =
Only :reset is available to represent the complete absence of color and styling. There are no fine-grained negative codes to just remove foreground color or just remove bold. Our API should provide these to allow these kind of fine-grained transitions to other color states.
[ :no_fg_color, :no_bg_color, :no_bold, :no_italic, :no_text_decoration, :no_underline, :no_blink, :no_strikethrough ].freeze
- VALID_VALUES =
(::Term::ANSIColor.attributes + [:none] + CSS_TO_ANSI_VALUES.keys).freeze
- VALID_VALUES_AND_PSEUDO_VALUES =
(VALID_VALUES + NEGATIVE_PSEUDO_VALUES).freeze
Class Method Summary collapse
-
.[](*colors) ⇒ Object
(also: c)
Retrieve color codes with the corresponding symbol.
-
.color(string, *colors) ⇒ Object
Apply any valid colors to a string and auto-reset (if any colors applied).
-
.color_transition(before_colors, after_colors, hard = true) ⇒ Object
Produces a string of color codes to transition from one set of colors to another.
-
.force_color(string, *colors) ⇒ Object
Apply any valid colors to a string and auto-reset (if any colors applied).
- .is_basic_color?(color) ⇒ Boolean
-
.is_compound_color?(color) ⇒ Boolean
Returns an array of colors if the given symbol represents a compound color.
-
.line_substring_color_transitions(line_colors, substring_colors) ⇒ Object
Gives a pair of color codes for transitions into and out of a colored substring in the middle of a possibly differently colored line.
- .negative?(color) ⇒ Boolean
- .uncolor(string) ⇒ Object
- .valid?(color) ⇒ Boolean
Class Method Details
.[](*colors) ⇒ Object Also known as: c
Retrieve color codes with the corresponding symbol. Can be basic colors like :red or “compound” colors specifying foreground and background colors like :red_on_blue.
Any number of colors can be specified, either as multiple arguments or in an array.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/styles/colors.rb', line 40 def self.[](*colors) colors.flatten! valid_colors = [] colors.each do |color| if is_valid_basic_value? color valid_colors << (CSS_TO_ANSI_VALUES[color] || color) elsif color_parts = is_compound_color?(color) valid_colors += color_parts end end unless valid_colors.empty? valid_colors.uniq! valid_colors.sort! valid_colors.unshift(:reset) if valid_colors.delete(:reset) valid_colors.inject('') { |str, color| str += ansi_color.send(color) } end end |
.color(string, *colors) ⇒ Object
Apply any valid colors to a string and auto-reset (if any colors applied). Does not apply colors to an empty string.
66 67 68 69 70 71 72 73 74 75 |
# File 'lib/styles/colors.rb', line 66 def self.color(string, *colors) return string if string.nil? or string.empty? colors.flatten! colors.reject! { |col| col == :none || !VALID_VALUES.include?(col) } if colors.any? "#{colors.map { |col| c(col) }.join}#{string}#{c(:reset)}" else string end end |
.color_transition(before_colors, after_colors, hard = true) ⇒ Object
Produces a string of color codes to transition from one set of colors to another.
If hard is true then all foregound and background colors are reset before adding the after colors. In other words, no colors are allowed to continue, even if not replaced.
If hard is false then colors that are not explicitly replaced by new colors are not reset. This means that if there are foreground and background before colors and only a foreground after color then even though the foreground color is replaced by the new one the background color is allowed to continue and is not explicitly reset.
Regardless of whether all colors are reset, output of unnecessary codes is avoided. This means, for example, that if any before colors are replaced by new colors of the same category (foreground, background, underline, etc.) then there will never be an explicit reset because that would be redundant and merely add more characters.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/styles/colors.rb', line 142 def self.color_transition(before_colors, after_colors, hard=true) before_colors, after_colors = Array(before_colors), Array(after_colors) before_categories, after_categories = categorize(before_colors), categorize(after_colors) # Nothing to do if before and after colors are the same return '' if before_categories == after_categories transition = '' should_reset = false colors_to_apply = [] # Explicit reset is necessary if we want a hard transition and all colors in all # categories are not replaced. if hard before_categories.each_pair do |cat, before_color| next if negative?(before_color) after_color = after_categories[cat] if !after_color || negative?(after_color) should_reset = true break end end end # If soft transition then the only time we need an explicit reset is when we have a color # in a category that is explicitly turned off with a negative value. This also applies # to hard transitions. unless should_reset after_categories.each_pair do |cat, after_color| before_color = before_categories[cat] if before_color && negative?(after_color) && !negative?(before_color) should_reset = true break end end end after_categories.each_pair do |cat, after_color| before_color = before_categories[cat] if !negative?(after_color) transition << c(after_color) unless before_color == after_color && !should_reset end end # If we are resetting but using a soft transition then all colors execept negated ones # need to be set again after the reset. if should_reset && !hard before_categories.values.each do |color| unless negative?(color) || after_categories.values.include?(negate(color)) transition << c(color) unless after_categories.keys.include?(category(color)) end end end transition.prepend(c(:reset)) if should_reset transition end |
.force_color(string, *colors) ⇒ Object
Apply any valid colors to a string and auto-reset (if any colors applied). If there are any resets in the middle of the string, reapply the colors after them.
79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/styles/colors.rb', line 79 def self.force_color(string, *colors) return string if string.nil? or string.empty? colors.flatten! colors.reject! { |col| col == :none || !VALID_VALUES.include?(col) } if colors.any? codes = colors.map { |col| c(col) }.join "#{codes}#{string.gsub(c(:reset), c(:reset) + codes)}#{c(:reset)}" else string end end |
.is_basic_color?(color) ⇒ Boolean
95 96 97 |
# File 'lib/styles/colors.rb', line 95 def self.is_basic_color?(color) COLOR_VALUES.include?(color) end |
.is_compound_color?(color) ⇒ Boolean
Returns an array of colors if the given symbol represents a compound color. Returns nil otherwise.
101 102 103 104 105 106 107 108 |
# File 'lib/styles/colors.rb', line 101 def self.is_compound_color?(color) if color.to_s =~ /(\w+)_on_(\w+)/ colors = [$1.to_sym, "on_#{$2}".to_sym] if colors.all? { |c| COLOR_VALUES.include? c } colors end end end |
.line_substring_color_transitions(line_colors, substring_colors) ⇒ Object
Gives a pair of color codes for transitions into and out of a colored substring in the middle of a possibly differently colored line.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/styles/colors.rb', line 112 def self.line_substring_color_transitions(line_colors, substring_colors) line_colors, substring_colors = Array(line_colors), Array(substring_colors) implied_substring_colors = [] line_colors.each do |line_col| cat = category(line_col) replaced = substring_colors.any? { |substr_col| category(substr_col) == cat} implied_substring_colors << line_col unless replaced end [ color_transition(line_colors, substring_colors, false), color_transition(substring_colors + implied_substring_colors, line_colors) ] end |
.negative?(color) ⇒ Boolean
201 202 203 |
# File 'lib/styles/colors.rb', line 201 def self.negative?(color) NEGATIVE_PSEUDO_VALUES.include?(color) end |
.uncolor(string) ⇒ Object
205 206 207 |
# File 'lib/styles/colors.rb', line 205 def self.uncolor(string) ansi_color.uncolor(string) end |
.valid?(color) ⇒ Boolean
91 92 93 |
# File 'lib/styles/colors.rb', line 91 def self.valid?(color) is_valid_basic_value?(color) || is_compound_color?(color) end |