Class: TIntMe::Style

Inherits:
Data
  • Object
show all
Includes:
ConstructorExtensions
Defined in:
lib/tint_me/style.rb,
lib/tint_me/style.rb,
lib/tint_me/style/types.rb,
lib/tint_me/style/schema.rb

Overview

A style class for applying ANSI colors and text effects to terminal output.

This class provides an immutable way to define and compose terminal styling options. It supports foreground/background colors, text decorations (bold, italic, underline, etc.), and composition via the >> operator for layering styles.

Examples:

Basic usage

style = Style.new(foreground: :red, bold: true)
puts style.call("Hello")  # or style["Hello"] or style.("Hello")

Style composition

base = Style.new(foreground: :blue)
emphasis = Style.new(bold: true, underline: true)
combined = base >> emphasis
puts combined.call("Styled text")  # or combined["Styled text"] or combined.("Styled text")

Color and decoration options

Style.new(
  foreground: :red,           # :default, :red, :green, :blue, etc. or hex "#FF0000"
  background: :yellow,        # same as foreground
  bold: true,                 # nil (unset), false (off), true (on)
  faint: false,               # mutually exclusive with bold
  underline: :double,         # nil, false, true, :double
  italic: true,               # nil, false, true
  inverse: true,              # nil, false, true
  # ... other boolean effects: overline, blink, conceal
)

Defined Under Namespace

Modules: ConstructorExtensions, Types

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ConstructorExtensions

#new, #normalize_constructor_args

Constructor Details

#initialize(foreground: nil, background: nil, inverse: nil, bold: nil, faint: nil, underline: nil, overline: nil, blink: nil, italic: nil, conceal: nil) ⇒ Style

Initialize a new Style with the given attributes

Examples:

Valid usage

Style.new(foreground: :red, bold: true)
Style.new(underline: :double, background: "#FF0000")

Positional arguments

Style.new(:red, :bold, :italic)          # foreground: :red, bold: true, italic: true
Style.new(:red, :yellow, :bold)          # foreground: :red, background: :yellow, bold: true
Style.new("#FF0000", "#00FF00")          # foreground: "#FF0000", background: "#00FF00"
Style[:blue, :bold]                      # foreground: :blue, bold: true

Invalid usage (raises ArgumentError)

Style.new(foreground: 123)        # Invalid color type
Style.new(bold: "true")           # Invalid boolean type
Style.new(underline: :invalid)    # Invalid underline option
Style.new(bold: true, faint: true) # Mutually exclusive options

Parameters:

  • (defaults to: nil)

    Foreground color. Accepts color names (:red, :green, :blue, etc.), :default for terminal default, :reset for composition clearing, or hex strings ("#FF0000", "FF0000")

  • (defaults to: nil)

    Background color. Same format as foreground

  • (defaults to: nil)

    Reverse foreground/background colors

  • (defaults to: nil)

    Bold text (mutually exclusive with faint)

  • (defaults to: nil)

    Faint/dim text (mutually exclusive with bold)

  • (defaults to: nil)

    Underline decoration

  • (defaults to: nil)

    Overline decoration

  • (defaults to: nil)

    Blinking text

  • (defaults to: nil)

    Italic text

  • (defaults to: nil)

    Hidden/concealed text

Raises:

  • If both bold and faint are true

  • If any parameter has invalid type or value



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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/tint_me/style.rb', line 173

def initialize(
  foreground: nil,
  background: nil,
  inverse: nil,
  bold: nil,
  faint: nil,
  underline: nil,
  overline: nil,
  blink: nil,
  italic: nil,
  conceal: nil
)
  # Schema validation
  result = Schema.call({
    foreground:,
    background:,
    inverse:,
    bold:,
    faint:,
    underline:,
    overline:,
    blink:,
    italic:,
    conceal:
  })

  raise ArgumentError, result.errors.to_h unless result.success?

  # Handle bold/faint mutual exclusion
  if bold && faint
    raise ArgumentError, "Cannot specify both bold and faint simultaneously"
  end

  # Pre-compute SGR sequences before freezing
  # (Data.define freezes the instance after super)
  sgr_builder = self.class.sgr_builder

  # Prepare color values
  foreground_color = foreground if foreground && foreground != :default
  background_color = background if background && background != :default

  # Handle underline effect
  underline_effect = case underline
                     when true then true
                     when :double then :double
                     when nil, false then nil
                     else raise ArgumentError, "Invalid underline value: #{underline.inspect}"
                     end

  # Calculate prefix once
  @prefix = sgr_builder.prefix_codes(
    foreground: foreground_color,
    background: background_color,
    bold: bold == true ? true : nil,
    faint: faint == true ? true : nil,
    italic: italic == true ? true : nil,
    underline: underline_effect,
    blink: blink == true ? true : nil,
    inverse: inverse == true ? true : nil,
    conceal: conceal == true ? true : nil,
    overline: overline == true ? true : nil
  )

  # Pre-compute reset code
  @reset_code = sgr_builder.reset_code

  super
end

Instance Attribute Details

#backgroundSymbol, String (readonly)

Returns The background color (same format as foreground).

Returns:

  • The background color (same format as foreground)



118
119
120
# File 'lib/tint_me/style.rb', line 118

def background
  @background
end

Returns Whether text blinks.

Returns:

  • Whether text blinks



136
137
138
# File 'lib/tint_me/style.rb', line 136

def blink
  @blink
end

#boldnil, ... (readonly)

Returns Whether text is bold (mutually exclusive with faint).

Returns:

  • Whether text is bold (mutually exclusive with faint)



124
125
126
# File 'lib/tint_me/style.rb', line 124

def bold
  @bold
end

#concealnil, ... (readonly)

Returns Whether text is hidden/concealed.

Returns:

  • Whether text is hidden/concealed



142
143
144
# File 'lib/tint_me/style.rb', line 142

def conceal
  @conceal
end

#faintnil, ... (readonly)

Returns Whether text is faint/dim (mutually exclusive with bold).

Returns:

  • Whether text is faint/dim (mutually exclusive with bold)



127
128
129
# File 'lib/tint_me/style.rb', line 127

def faint
  @faint
end

#foregroundSymbol, String (readonly)

Returns The foreground color (:red, :blue, :default, :reset, hex "#FF0000", etc.).

Returns:

  • The foreground color (:red, :blue, :default, :reset, hex "#FF0000", etc.)



115
116
117
# File 'lib/tint_me/style.rb', line 115

def foreground
  @foreground
end

#inversenil, ... (readonly)

Returns Whether to reverse foreground/background colors.

Returns:

  • Whether to reverse foreground/background colors



121
122
123
# File 'lib/tint_me/style.rb', line 121

def inverse
  @inverse
end

#italicnil, ... (readonly)

Returns Whether text is italic.

Returns:

  • Whether text is italic



139
140
141
# File 'lib/tint_me/style.rb', line 139

def italic
  @italic
end

#overlinenil, ... (readonly)

Returns Whether text has overline decoration.

Returns:

  • Whether text has overline decoration



133
134
135
# File 'lib/tint_me/style.rb', line 133

def overline
  @overline
end

#underlinenil, ... (readonly)

Returns Underline decoration type.

Returns:

  • Underline decoration type



130
131
132
# File 'lib/tint_me/style.rb', line 130

def underline
  @underline
end

Class Method Details

.sgr_builderSGRBuilder

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the singleton instance of SGRBuilder

Returns:

  • The SGR builder instance

API:

  • private



112
113
114
# File 'lib/tint_me/style.rb', line 112

def self.sgr_builder
  SGRBuilder.instance
end

Instance Method Details

#>>(other) ⇒ Style

Compose this style with another style, creating a new Style instance. The right-hand style takes precedence for non-nil values. Handles bold/faint mutual exclusion automatically.

Composition Rules

For a >> b, the resulting value for each attribute is determined by:

b's value Result Description
nil a's value Preserves the original value
:reset nil Explicitly resets to no styling
any other b's value Adopts the new value

This applies to all attributes: colors (foreground, background) and style attributes (bold, italic, underline, inverse, overline, blink, conceal).

Examples:

Basic composition

base = Style.new(foreground: :red, bold: true)
overlay = Style.new(background: :blue, underline: true)
result = base >> overlay  # => red text, blue background, bold and underlined

Using nil to preserve values

styled = Style.new(foreground: :red, bold: true)
partial = Style.new(foreground: nil, italic: true)  # nil preserves red
result = styled >> partial  # => foreground: :red, bold: true, italic: true

Using :reset to clear styles

styled = Style.new(foreground: :red, bold: true)
reset = Style.new(foreground: :reset, bold: :reset)
result = styled >> reset  # => foreground: nil, bold: nil

Bold/faint mutual exclusion

bold_style = Style.new(bold: true)
faint_style = Style.new(faint: true)
result1 = bold_style >> faint_style  # => faint wins (right-hand takes precedence)
result2 = faint_style >> bold_style  # => bold wins (right-hand takes precedence)

Parameters:

  • The style to compose with this one

Returns:

  • A new Style instance with composed attributes



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/tint_me/style.rb', line 315

def >>(other)
  # Handle bold/faint mutual exclusion in composition
  composed_bold = compose_attribute(bold, other.bold)
  composed_faint = compose_attribute(faint, other.faint)

  # If other explicitly sets bold, clear faint; if other explicitly sets faint, clear bold
  if other.bold == true
    composed_faint = false
  elsif other.faint == true
    composed_bold = false
  elsif other.bold == :reset
    composed_bold = nil
  elsif other.faint == :reset
    composed_faint = nil
  end

  Style.new(
    foreground: compose_attribute(foreground, other.foreground),
    background: compose_attribute(background, other.background),
    inverse: compose_attribute(inverse, other.inverse),
    bold: composed_bold,
    faint: composed_faint,
    underline: compose_attribute(underline, other.underline),
    overline: compose_attribute(overline, other.overline),
    blink: compose_attribute(blink, other.blink),
    italic: compose_attribute(italic, other.italic),
    conceal: compose_attribute(conceal, other.conceal)
  )
end

#call(text) ⇒ String Also known as: []

Apply the style to the given text using native ANSI escape sequences

Examples:

style = Style.new(foreground: :red, bold: true)
style.call("Hello")  # => "\e[31;1mHello\e[0m"
style["World"]       # => "\e[31;1mWorld\e[0m" (alias)

Parameters:

  • The text to apply styling to

Returns:

  • The styled text with ANSI escape codes, or original text if no styles are set



250
251
252
253
254
# File 'lib/tint_me/style.rb', line 250

def call(text)
  return text if @prefix.empty?

  "#{@prefix}#{text}#{@reset_code}"
end