Skip to content

Commit

Permalink
Add support for overline and underline with
Browse files Browse the repository at this point in the history
different types and colors. Also cleaning up some legacy code.
  • Loading branch information
flori committed Jun 21, 2024
1 parent 792d21a commit 3b33035
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 82 deletions.
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GemHadar do
executables.merge Dir['bin/*'].map { |x| File.basename(x) }

dependency 'tins', '~>1.0'
dependency 'mize'
development_dependency 'simplecov'
development_dependency 'test-unit'
development_dependency 'utils'
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.9.0
1.10.0
2 changes: 1 addition & 1 deletion examples/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class String

symbols = Term::ANSIColor::attributes
print red { bold { "All supported attributes = " } },
symbols.map { |s| __send__(s, s.inspect) } * ', ', "\n\n"
symbols.map { |s| __send__(s, s.inspect) } * ",\n", "\n\n"

print "Send symbols to strings:".send(:red).send(:bold), "\n"
print symbols[12, 8].map { |c| c.to_s.send(c) } * '', "\n\n"
Expand Down
48 changes: 12 additions & 36 deletions lib/term/ansicolor.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'tins/xt/full'
require 'mize'

module Term

Expand All @@ -18,23 +19,7 @@ module ANSIColor
include Term::ANSIColor::Movement
require 'term/ansicolor/hyperlink'
include Term::ANSIColor::Hyperlink

# :stopdoc:
ATTRIBUTE_NAMES = Attribute.named_attributes.map(&:name)
# :startdoc:

# Returns true if Term::ANSIColor supports the +feature+.
#
# The feature :clear, that is mixing the clear color attribute into String,
# is only supported on ruby implementations, that do *not* already
# implement the String#clear method. It's better to use the reset color
# attribute instead.
def support?(feature)
case feature
when :clear
!String.instance_methods(false).map(&:to_sym).include?(:clear)
end
end
include Term::ANSIColor::Attribute::Underline

# Returns true, if the coloring function of this module
# is switched on, false otherwise.
Expand Down Expand Up @@ -64,19 +49,6 @@ def self.true_coloring=(val)
end
self.true_coloring = false

def self.create_color_method(color_name, color_value)
module_eval <<-EOT
def #{color_name}(string = nil, **options, &block)
apply_attribute(:#{color_name}, string, **options, &block)
end
EOT
self
end

for attribute in Attribute.named_attributes
create_color_method(attribute.name, attribute.code)
end

# Regular expression that is used to scan for ANSI-Attributes while
# uncoloring strings.
COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9]|[34]8;(5;\d{1,3}|2;\d{1,3}(;\d{1,3}){2})|4:\d|53)?m/
Expand All @@ -97,11 +69,9 @@ def uncolor(string = nil) # :yields:

alias uncolored uncolor

def apply_attribute(name, string = nil, &block)
attribute = Attribute[name] or
raise ArgumentError, "unknown attribute #{name.inspect}"
def apply_code(code, string = nil, &block)
result = ''
result << "\e[#{attribute.code}m" if Term::ANSIColor.coloring?
result << "\e[#{code}m" if Term::ANSIColor.coloring?
if block_given?
result << yield.to_s
elsif string.respond_to?(:to_str)
Expand All @@ -115,6 +85,12 @@ def apply_attribute(name, string = nil, &block)
result.extend(Term::ANSIColor)
end

def apply_attribute(name, string = nil, &block)
attribute = Attribute[name] or
raise ArgumentError, "unknown attribute #{name.inspect}"
apply_code(attribute.code, string, &block)
end

# Return +string+ or the result string of the given +block+ colored with
# color +name+. If string isn't a string only the escape sequence to switch
# on the color +name+ is returned.
Expand All @@ -135,8 +111,8 @@ def on_color(name, string = nil, &block)

class << self
# Returns an array of all Term::ANSIColor attributes as symbols.
def term_ansicolor_attributes
::Term::ANSIColor::ATTRIBUTE_NAMES
memoize method: def term_ansicolor_attributes
::Term::ANSIColor::Attribute.attributes.map(&:name)
end

alias attributes term_ansicolor_attributes
Expand Down
25 changes: 20 additions & 5 deletions lib/term/ansicolor/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ module ANSIColor
class Attribute
@__store__ = {}

def self.set(name, code, options = {})
def self.set(name, code, **options)
name = name.to_sym
result = @__store__[name] = new(name, code, options)
@rgb_colors = nil
unless options[:skip_definition]
::Term::ANSIColor.class_eval do
define_method(name) do |string = nil, &block|
apply_attribute(name, string, &block)
end
end
end
result
end

Expand Down Expand Up @@ -44,7 +50,7 @@ def self.get(name)
end

def self.rgb_colors(options = {}, &block)
colors = @rgb_colors ||= attributes.select(&:rgb_color?)
colors = attributes.select(&:rgb_color?)
if options.key?(:gray) && !options[:gray]
colors = colors.reject(&:gray?)
end
Expand Down Expand Up @@ -85,9 +91,12 @@ def initialize(name, code, options = {})
if rgb = options[:true_color]
@true_color = true
@rgb = rgb
elsif rgb = options[:color8]
@color8 = true
@rgb = RGBTriple.from_html(rgb)
elsif html = options[:html]
@rgb = RGBTriple.from_html(html)
elsif !options.empty?
elsif options.slice(:red, :green, :blue).size == 3
@rgb = RGBTriple.from_hash(options)
else
@rgb = nil # prevent instance variable not initialized warnings
Expand All @@ -101,6 +110,8 @@ def code
background? ? "48;2;#{@rgb.to_a * ?;}" : "38;2;#{@rgb.to_a * ?;}"
elsif rgb_color?
background? ? "48;5;#{@code}" : "38;5;#{@code}"
elsif color8?
background? ? (@code.to_i + 10).to_s : @code
else
@code
end
Expand All @@ -114,12 +125,16 @@ def background?
!!@background
end

def color8?
!!@color8
end

attr_writer :background

attr_reader :rgb

def rgb_color?
!!@rgb && !@true_color
!!@rgb && !@true_color && !@color8
end

def true_color?
Expand Down
32 changes: 16 additions & 16 deletions lib/term/ansicolor/attribute/color8.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ module Term
module ANSIColor
class Attribute
class Color8
Attribute.set :black, 30
Attribute.set :red, 31
Attribute.set :green, 32
Attribute.set :yellow, 33
Attribute.set :blue, 34
Attribute.set :magenta, 35
Attribute.set :cyan, 36
Attribute.set :white, 37
Attribute.set :black, 30, color8: '#000000'
Attribute.set :red, 31, color8: '#800000'
Attribute.set :green, 32, color8: '#008000'
Attribute.set :yellow, 33, color8: '#808000'
Attribute.set :blue, 34, color8: '#000080'
Attribute.set :magenta, 35, color8: '#800080'
Attribute.set :cyan, 36, color8: '#008080'
Attribute.set :white, 37, color8: '#c0c0c0'

Attribute.set :on_black, 40
Attribute.set :on_red, 41
Attribute.set :on_green, 42
Attribute.set :on_yellow, 43
Attribute.set :on_blue, 44
Attribute.set :on_magenta, 45
Attribute.set :on_cyan, 46
Attribute.set :on_white, 47
Attribute.set :on_black, 40, color8: '#000000'
Attribute.set :on_red, 41, color8: '#800000'
Attribute.set :on_green, 42, color8: '#008000'
Attribute.set :on_yellow, 43, color8: '#808000'
Attribute.set :on_blue, 44, color8: '#000080'
Attribute.set :on_magenta, 45, color8: '#800080'
Attribute.set :on_cyan, 46, color8: '#008080'
Attribute.set :on_white, 47, color8: '#808080'
end
end
end
Expand Down
34 changes: 18 additions & 16 deletions lib/term/ansicolor/attribute/text.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
require 'term/ansicolor/attribute/underline'

module Term
module ANSIColor
class Attribute
class Text
Attribute.set :clear, 0 # String#clear already used in String
Attribute.set :reset, 0 # synonym for :clear
Attribute.set :bold, 1
Attribute.set :dark, 2
Attribute.set :faint, 2
Attribute.set :italic, 3 # not widely implemented
Attribute.set :underline, 4
Attribute.set :underscore, 4 # synonym for :underline
Attribute.set :blink, 5
Attribute.set :rapid_blink, 6 # not widely implemented
Attribute.set :reverse, 7 # String#reverse already used in String
Attribute.set :negative, 7 # synonym for :reverse
Attribute.set :concealed, 8
Attribute.set :conceal, 8 # synonym for :concealed
Attribute.set :strikethrough, 9 # not widely implemented
Attribute.set :overline, 53 # not widely implemented
Attribute.set :clear, 0 # String#clear already used in String
Attribute.set :reset, 0 # synonym for :clear
Attribute.set :bold, 1
Attribute.set :dark, 2
Attribute.set :faint, 2
Attribute.set :italic, 3 # not widely implemented
Attribute.set :blink, 5
Attribute.set :rapid_blink, 6 # not widely implemented
Attribute.set :reverse, 7 # String#reverse already used in String
Attribute.set :negative, 7 # synonym for :reverse
Attribute.set :concealed, 8
Attribute.set :conceal, 8 # synonym for :concealed
Attribute.set :strikethrough, 9 # not widely implemented
Attribute.set :overline, 53

include Term::ANSIColor::Attribute::Underline
end
end
end
Expand Down
34 changes: 34 additions & 0 deletions lib/term/ansicolor/attribute/underline.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Term
module ANSIColor
class Attribute
module Underline
Attribute.set :underline, 4, skip_definition: true
Attribute.set :underscore, 4, skip_definition: true # synonym for :underline

def underline(string = nil, color: nil, type: nil, &block)
code = {
nil => 4,
default: '4:1',
double: '4:2',
curly: '4:3',
dotted: '4:4',
dashed: '4:5',
}.fetch(type) { raise ArgumentError, "invalid line type" }
if color
a = Term::ANSIColor::Attribute[color]
color_code =
if a.true_color? || a.rgb_color? || a.color8?
color_code = "\e[58;2;#{a.rgb.to_a * ?;}"
else
raise ArgumentError, "invalid color #{a.name.inspect}"
end
code = "#{code}m#{color_code}"
end
apply_code(code, string, &block)
end

alias underscore underline
end
end
end
end
2 changes: 1 addition & 1 deletion lib/term/ansicolor/version.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Term::ANSIColor
# Term::ANSIColor version
VERSION = '1.9.0'
VERSION = '1.10.0'
VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
Expand Down
9 changes: 5 additions & 4 deletions term-ansicolor.gemspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- encoding: utf-8 -*-
# stub: term-ansicolor 1.9.0 ruby lib
# stub: term-ansicolor 1.10.0 ruby lib

Gem::Specification.new do |s|
s.name = "term-ansicolor".freeze
s.version = "1.9.0".freeze
s.version = "1.10.0".freeze

s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
Expand All @@ -12,8 +12,8 @@ Gem::Specification.new do |s|
s.description = "This library uses ANSI escape sequences to control the attributes of terminal output".freeze
s.email = "[email protected]".freeze
s.executables = ["term_cdiff".freeze, "term_colortab".freeze, "term_decolor".freeze, "term_display".freeze, "term_mandel".freeze, "term_snow".freeze]
s.extra_rdoc_files = ["README.md".freeze, "lib/term/ansicolor.rb".freeze, "lib/term/ansicolor/attribute.rb".freeze, "lib/term/ansicolor/attribute/color256.rb".freeze, "lib/term/ansicolor/attribute/color8.rb".freeze, "lib/term/ansicolor/attribute/intense_color8.rb".freeze, "lib/term/ansicolor/attribute/text.rb".freeze, "lib/term/ansicolor/hsl_triple.rb".freeze, "lib/term/ansicolor/hyperlink.rb".freeze, "lib/term/ansicolor/movement.rb".freeze, "lib/term/ansicolor/ppm_reader.rb".freeze, "lib/term/ansicolor/rgb_color_metrics.rb".freeze, "lib/term/ansicolor/rgb_triple.rb".freeze, "lib/term/ansicolor/version.rb".freeze]
s.files = [".all_images.yml".freeze, ".gitignore".freeze, ".utilsrc".freeze, "CHANGES".freeze, "COPYING".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/term_cdiff".freeze, "bin/term_colortab".freeze, "bin/term_decolor".freeze, "bin/term_display".freeze, "bin/term_mandel".freeze, "bin/term_snow".freeze, "examples/example.rb".freeze, "examples/lambda-red-plain.ppm".freeze, "examples/lambda-red.png".freeze, "examples/lambda-red.ppm".freeze, "lib/term/ansicolor.rb".freeze, "lib/term/ansicolor/.keep".freeze, "lib/term/ansicolor/attribute.rb".freeze, "lib/term/ansicolor/attribute/color256.rb".freeze, "lib/term/ansicolor/attribute/color8.rb".freeze, "lib/term/ansicolor/attribute/intense_color8.rb".freeze, "lib/term/ansicolor/attribute/text.rb".freeze, "lib/term/ansicolor/hsl_triple.rb".freeze, "lib/term/ansicolor/hyperlink.rb".freeze, "lib/term/ansicolor/movement.rb".freeze, "lib/term/ansicolor/ppm_reader.rb".freeze, "lib/term/ansicolor/rgb_color_metrics.rb".freeze, "lib/term/ansicolor/rgb_triple.rb".freeze, "lib/term/ansicolor/version.rb".freeze, "term-ansicolor.gemspec".freeze, "tests/ansicolor_test.rb".freeze, "tests/attribute_test.rb".freeze, "tests/hsl_triple_test.rb".freeze, "tests/hyperlink_test.rb".freeze, "tests/ppm_reader_test.rb".freeze, "tests/rgb_color_metrics_test.rb".freeze, "tests/rgb_triple_test.rb".freeze, "tests/test_helper.rb".freeze]
s.extra_rdoc_files = ["README.md".freeze, "lib/term/ansicolor.rb".freeze, "lib/term/ansicolor/attribute.rb".freeze, "lib/term/ansicolor/attribute/color256.rb".freeze, "lib/term/ansicolor/attribute/color8.rb".freeze, "lib/term/ansicolor/attribute/intense_color8.rb".freeze, "lib/term/ansicolor/attribute/text.rb".freeze, "lib/term/ansicolor/attribute/underline.rb".freeze, "lib/term/ansicolor/hsl_triple.rb".freeze, "lib/term/ansicolor/hyperlink.rb".freeze, "lib/term/ansicolor/movement.rb".freeze, "lib/term/ansicolor/ppm_reader.rb".freeze, "lib/term/ansicolor/rgb_color_metrics.rb".freeze, "lib/term/ansicolor/rgb_triple.rb".freeze, "lib/term/ansicolor/version.rb".freeze]
s.files = [".all_images.yml".freeze, ".gitignore".freeze, ".utilsrc".freeze, "CHANGES".freeze, "COPYING".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/term_cdiff".freeze, "bin/term_colortab".freeze, "bin/term_decolor".freeze, "bin/term_display".freeze, "bin/term_mandel".freeze, "bin/term_snow".freeze, "examples/example.rb".freeze, "examples/lambda-red-plain.ppm".freeze, "examples/lambda-red.png".freeze, "examples/lambda-red.ppm".freeze, "lib/term/ansicolor.rb".freeze, "lib/term/ansicolor/.keep".freeze, "lib/term/ansicolor/attribute.rb".freeze, "lib/term/ansicolor/attribute/color256.rb".freeze, "lib/term/ansicolor/attribute/color8.rb".freeze, "lib/term/ansicolor/attribute/intense_color8.rb".freeze, "lib/term/ansicolor/attribute/text.rb".freeze, "lib/term/ansicolor/attribute/underline.rb".freeze, "lib/term/ansicolor/hsl_triple.rb".freeze, "lib/term/ansicolor/hyperlink.rb".freeze, "lib/term/ansicolor/movement.rb".freeze, "lib/term/ansicolor/ppm_reader.rb".freeze, "lib/term/ansicolor/rgb_color_metrics.rb".freeze, "lib/term/ansicolor/rgb_triple.rb".freeze, "lib/term/ansicolor/version.rb".freeze, "term-ansicolor.gemspec".freeze, "tests/ansicolor_test.rb".freeze, "tests/attribute_test.rb".freeze, "tests/hsl_triple_test.rb".freeze, "tests/hyperlink_test.rb".freeze, "tests/ppm_reader_test.rb".freeze, "tests/rgb_color_metrics_test.rb".freeze, "tests/rgb_triple_test.rb".freeze, "tests/test_helper.rb".freeze]
s.homepage = "https://github.com/flori/term-ansicolor".freeze
s.licenses = ["Apache-2.0".freeze]
s.rdoc_options = ["--title".freeze, "Term-ansicolor - Ruby library that colors strings using ANSI escape sequences".freeze, "--main".freeze, "README.md".freeze]
Expand All @@ -28,4 +28,5 @@ Gem::Specification.new do |s|
s.add_development_dependency(%q<test-unit>.freeze, [">= 0".freeze])
s.add_development_dependency(%q<utils>.freeze, [">= 0".freeze])
s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.0".freeze])
s.add_runtime_dependency(%q<mize>.freeze, [">= 0".freeze])
end
47 changes: 45 additions & 2 deletions tests/ansicolor_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,22 @@ def test_uncolor

def test_attributes
foo = 'foo'
for a in Term::ANSIColor.attributes
attributes = %i[
clear reset bold dark faint italic underline underscore
blink rapid_blink reverse negative concealed conceal strikethrough black
red green yellow blue magenta cyan white on_black on_red on_green on_yellow
on_blue on_magenta on_cyan on_white intense_black bright_black intense_red
bright_red intense_green bright_green intense_yellow bright_yellow
intense_blue bright_blue intense_magenta bright_magenta intense_cyan
bright_cyan intense_white bright_white on_intense_black on_bright_black
on_intense_red on_bright_red on_intense_green on_bright_green
on_intense_yellow on_bright_yellow on_intense_blue on_bright_blue
on_intense_magenta on_bright_magenta on_intense_cyan on_bright_cyan
on_intense_white on_bright_white
]
for a in attributes
# skip :clear and :reverse b/c Ruby implements them on string
if a != :clear && a != :reverse || Term::ANSIColor.support?(:clear)
if a != :clear && a != :reverse
refute_equal foo, foo_colored = foo.__send__(a)
assert_equal foo, foo_colored.uncolor
end
Expand All @@ -121,6 +134,36 @@ def test_attributes
assert_equal Term::ANSIColor.attributes, 'foo'.attributes
end

def test_underline
foo = 'foo'
assert_equal "\e[4mfoo\e[0m", Term::ANSIColor.underline { foo }
assert_equal "\e[4m\e[58;2;255;135;95mfoo\e[0m", Term::ANSIColor.underline(color: '#ff8040') { foo }
end

def test_underline_double
foo = 'foo'
assert_equal "\e[4:2mfoo\e[0m", Term::ANSIColor.underline(type: :double) { foo }
assert_equal "\e[4:2m\e[58;2;255;135;95mfoo\e[0m", Term::ANSIColor.underline(type: :double, color: '#ff8040') { foo }
end

def test_underline_curly
foo = 'foo'
assert_equal "\e[4:3mfoo\e[0m", Term::ANSIColor.underline(type: :curly) { foo }
assert_equal "\e[4:3m\e[58;2;255;135;95mfoo\e[0m", Term::ANSIColor.underline(type: :curly, color: '#ff8040') { foo }
end

def test_underline_dotted
foo = 'foo'
assert_equal "\e[4:4mfoo\e[0m", Term::ANSIColor.underline(type: :dotted) { foo }
assert_equal "\e[4:4m\e[58;2;255;135;95mfoo\e[0m", Term::ANSIColor.underline(type: :dotted, color: '#ff8040') { foo }
end

def test_underline_dashed
foo = 'foo'
assert_equal "\e[4:5mfoo\e[0m", Term::ANSIColor.underline(type: :dashed) { foo }
assert_equal "\e[4:5m\e[58;2;255;135;95mfoo\e[0m", Term::ANSIColor.underline(type: :dashed, color: '#ff8040') { foo }
end

def test_move_to
string_23_23 = "\e[23;23Hred"
assert_equal string_23_23, string.move_to(23, 23)
Expand Down

0 comments on commit 3b33035

Please sign in to comment.