Tone
Provides a customizable ANSI text colorizer for your terminal. This allows you to encode plain text that renders as colorized output. This is great for enhancing Command Line Interfaces (CLIs) and terminal output in general.
- Features
- Screenshots
- Requirements
- Setup
- Usage
- Encode
- Decode
- Defaults
- Codes
- Symbols
- Aliases
- Errors
- Testing
- Guidelines
- Development
- Tests
- License
- Security
- Code of Conduct
- Contributions
- Developer Certificate of Origin
- Versions
- Community
- Credits
Features
-
Provides default styles with multiple options for unique combinations.
-
Provides a single object for encoding and decoding colorized text.
-
Provides aliasing of color configurations.
-
Provides quick access to default and aliased styles.
Requirements
-
Ruby.
Setup
To install with security, run:
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install tone --trust-policy HighSecurity
To install without security, run:
gem install tone
You can also add the gem directly to your project:
bundle add tone
Once the gem is installed, you only need to require it:
require "tone"
Usage
Basic usage is as follows:
tone = Tone.new
tone["Success!", :green] # "\e[32mSuccess!\e[0m"
There is a lot more you can do with this gem so the following sections will delve into the specifics.
Encode
As you saw earlier, you can encode plain text as colorized text using #[]
. Use of the #[]
method is an alias to the longer #encode
method. This allows you to use minimal syntax to create colorized text. Here’s a few more examples:
tone = Tone.new
# With symbols.
tone["Success", :black, :on_green] # "\e[30;42mSuccess\e[0m"
# With strings.
tone["Success", "black", "on_green"] # "\e[30;42mSuccess\e[0m"
# With no styles.
tone["Success"] # "Success"
# With any object that responds to `#to_str` or `#to_s`.
tone[Object.new, :green] # "\e[32m#<Object:0x000000010f095668>\e[0m"
# With nil.
tone[nil] # ""
# With interspersed nils (nils are ignored).
tone["Success", nil, :green, nil] # "\e[32mSuccess\e[0m"
The first argument is the text you want to encode/colorize. This can be a word, phrase, paragraph, or entire document. All arguments that follow after the first argument are style arguments which allow you to style the color of your text as you see fit. In this case, the "Success"
text will use a black foreground on a green background. The styles available for you to use will be explained shortly, though. For now, know that #[]
is shorthand for #encode
so any of the above examples could be replaced with #encode
messages. Example:
tone = Tone.new
tone.encode "Success", :black, :on_green # "\e[30;42mSuccess\e[0m"
Both methods are available to use depending on your preference.
Decode
Once your text has been encoded with colors, it can be nice to decode the colorized text back to plain text along with additional metadata. This is helpful — as an example — for testing purposes since you might not always want to deal with the hard to read escape characters. If we build upon the examples from the Encode section, we can decode our colorized text into plain text with extra metadata:
tone = Tone.new
tone.decode "\e[30;42mSuccess\e[0m" # [["Success", :black, :on_green]]
tone.decode "\e[37;41mFailure\e[0m" # [["Failure", :white, :on_red]]
Notice we get an array of sub arrays which mimic the original arguments passed to #encode
. This allows you to encode and decode with minimal effort. Here’s a more complex example where a sentence is used and formatted with the Amazing Print gem:
tone = Tone.new
ap tone.decode("We turned a \e[37;41mfailure\e[0m into a \e[30;42msuccess\e[0m!")
# [
# [
# "We turned a "
# ],
# [
# "failure",
# :white,
# :on_red
# ],
# [
# " into a "
# ],
# [
# "success",
# :black,
# :on_green
# ],
# [
# "!"
# ]
# ]
For plain text, you get a single element array but for colorized text, it will be broken down into an array of arguments. This allows you to easily iterate over this structure for parsing, transformation, or pattern matching purposes.
Here’s another example where a paragraph is used:
tone = Tone.new
paragraph = <<~CONTENT.strip
Yesterday \e[30;42mwent well\e[0m
but tomorrow will be \e[37;41mmore challenging\e[0m.
CONTENT
ap tone.decode(paragraph)
# [
# [
# "Yesterday "
# ],
# [
# "went well",
# :black,
# :on_green
# ],
# [
# "\nbut tomorrow will be "
# ],
# [
# "more challenging",
# :white,
# :on_red
# ],
# [
# "."
# ]
# ]
Defaults
To display defaults, use:
tone = Tone.new
tone.defaults
The above will output something similar to what you see below (minus the categorization) of key and value which will allow you to pick and choose the style or combination of styles you desire.
-
Styles
-
clear
-
bold
-
dim
-
italic
-
underline
-
inverse
-
hidden
-
strikethrough
-
-
Foregrounds
-
black
-
red
-
green
-
yellow
-
blue
-
purple
-
cyan
-
white
-
bright_black
-
bright_red
-
bright_green
-
bright_yellow
-
bright_blue
-
bright_purple
-
bright_cyan
-
bright_white
-
-
Backgrounds
-
on_black
-
on_red
-
on_green
-
on_yellow
-
on_blue
-
on_purple
-
on_cyan
-
on_white
-
on_bright_black
-
on_bright_red
-
on_bright_green
-
on_bright_yellow
-
on_bright_blue
-
on_bright_purple
-
on_bright_cyan
-
on_bright_white
-
These are the defaults for which you can mix-n-match as desired to produce colorful output. For example, if you want black text on a green background with an underline, you could use:
tone = Tone.new
puts tone["Success!", :black, :on_green, :strikethrough]
Codes
For situations where you’d like to find a code (or codes) for a symbol you can use the following:
tone = Tone.new
tone.find_code :green # 32
tone.find_code :bogus # nil
tone.find_codes :green # [32]
tone.find_codes :red, :green, :blue # [31, 32, 34]
tone.find_codes :bogus, :invalid # [nil, nil]
Symbols
Much like with the codes, mentioned above, you can find a symbol (or symbols) for a code too:
tone = Tone.new
tone.find_symbol 32 # :green
tone.find_symbol 666 # nil
tone.find_symbols 32 # [:green]
tone.find_symbols 31, 32, 34 # [:red, :green, :blue]
tone.find_symbols 666, 999 # [nil, nil]
Aliases
You can alias combinations of default styles with a descriptive name for shorthand reuse. This allows you to reduce duplicated effort and speed up your workflow. Here are a few examples:
tone = Tone.new
tone.add_alias :success, :black, :on_green
tone.add_alias :failure, :white, :on_red
tone["Success!", :success] # "\e[30;42mSuccess!\e[0m"
tone["Failure!", :failure] # "\e[37;41mFailure!\e[0m"
Notice that the first argument is your alias and all arguments after the first argument is the list of styles. Once added, both the :success
and :failure
aliases can immediately be used. You can also add multiple aliases, at once, by chaining your messages:
tone = Tone.new
.add_alias(:success, :black, :on_green)
.add_alias :failure, :white, :on_red
tone["Success!", :success] # "\e[30;42mSuccess!\e[0m"
tone["Failure!", :failure] # "\e[37;41mFailure!\e[0m"
Aliases — and associated styles — can be symbols or strings. The following, despite using strings, is identical to the above:
tone = Tone.new
.add_alias("success", "black", "on_green")
.add_alias "failure", "white", "on_red"
tone["Success!", :success] # "\e[30;42mSuccess!\e[0m"
tone["Failure!", :failure] # "\e[37;41mFailure!\e[0m"
To see the list of all aliases added, use:
tone = Tone.new.add_alias(:success, :black, :on_green).add_alias :failure, :white, :on_red
ap tone.aliases
# {
# :success => [
# :black,
# :on_green
# ],
# :failure => [
# :white,
# :on_red
# ]
# }
To get a specific alias, use:
tone = Tone.new.add_alias :success, :black, :on_green
tone.get_alias :success
# [:black, :on_green]
In the case of a default, you’ll only get back the given key:
Tone.new.get_alias :green # :green
Errors
There are several checks performed which might result in a Tone::Error
if not properly used. Here’s a few examples of what you might see.
tone = Tone.new
tone.add_alias :bogus
# Alias must have styles: :bogus. (Tone::Error)
tone.add_alias :bogus, nil
# Alias must have styles: :bogus. (Tone::Error)
tone.add_alias :red, :red
# Alias mustn't duplicate (override) default: :red. (Tone::Error)
tone.add_alias :bogus, :invalid
# Invalid style (:invalid) for key (:bogus). (Tone::Error)
tone.add_alias :success, :black, :on_green
tone.add_alias :success, :black, :on_green
# Duplicate alias detected (already exists): :success. (Tone::Error)
tone.get_alias nil
# Invalid alias or default: nil. (Tone::Error)
tone.get_alias :bogus
# Invalid alias or default: :bogus. (Tone::Error)
Testing
When using this gem in your project, you might find it convenient to use the have_color
RSpec matcher. This matcher is optional and must be manually required for use in your spec helper:
# spec_helper.rb
require "tone/rspec/matchers/have_color"
Once required, you can leverage the matcher in any spec as follows:
RSpec.describe DemoPresenter do
subject(:presenter) { DemoPresenter.new color: }
let(:color) { Tone.new }
describe "#to_s" do
it "renders colored text" do
expect(presenter.to_s).to have_color(color, ["Test 0.0.0: A test.", :bold])
end
end
end
The first argument must be an instanced of Tone because you might have custom aliases which must be known in order to validate your spec. All subsequent arguments (one to many) that follow after the first argument can be a list of decoded tuples as would normally be answered by Tone#decode
.
In situations where the spec fails, you’ll get a formatted error so you can quickly fix as necessary:
expected "\e[37mtest\e[0m\n" to have color decoded as: ["text", :blue], ["\n"] but actually is: ["test", :white], ["\n"]
Guidelines
The following are worth considering, when using this gem, to help keep your implementation consistent.
Order your arguments by style, foreground, and background when encoding:
# No
tone["test, :underline, :on_black, :white]
tone["test, :white, :underline, :on_black]
tone["test, :on_black, :white, :underline]
# Yes
tone["test, :underline, :white, :on_black]
Order your arguments by style, foreground, and background when adding aliases:
# No
tone.add_alias :demo, :underline, :on_black, :white
tone.add_alias :demo, :white, :underline, :on_black
tone.add_alias :demo, :on_black, :white, :underline
# Yes
tone.add_alias :demo, :underline, :white, :on_black
These are not hard requirements but these little touches will help improve readability. 🎉
Development
To contribute, run:
git clone https://github.com/bkuhlmann/tone
cd tone
bin/setup
You can use the IRB console for direct access to all objects:
bin/console
Lastly, there is a bin/demo
script which displays the default styles for quick visual reference. This is the same script used to generate the screenshots shown at the top of this document.
bin/demo
Tests
To test, run:
bin/rake
Credits
-
Built with Gemsmith.
-
Engineered by Brooke Kuhlmann.