tone

A customizable ANSI text terminal colorizer.

https://github.com/bkuhlmann/tone

Science Score: 44.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.0%) to scientific vocabulary

Keywords

ansi color terminal
Last synced: 6 months ago · JSON representation ·

Repository

A customizable ANSI text terminal colorizer.

Basic Info
Statistics
  • Stars: 5
  • Watchers: 3
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Topics
ansi color terminal
Created almost 3 years ago · Last pushed 6 months ago
Metadata Files
Readme Funding License Citation

README.adoc

:toc: macro
:toclevels: 5
:figure-caption!:

:amazing_print_link: link:https://github.com/amazing-print/amazing_print[Amazing Print]
:pattern_matching_link: link:https://alchemists.io/articles/ruby_pattern_matching[pattern matching]
:rspec_link: link:https://rspec.info[RSpec]

= Tone

Provides a customizable link:https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences[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.

toc::[]

== 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.

== Screenshots

image::https://alchemists.io/images/projects/tone/screenshot.png[A screenshot of colors,width=302,height=1026,role=focal_point]

== Requirements

. link:https://www.ruby-lang.org[Ruby].

== Setup

To install _with_ security, run:

[source,bash]
----
# 💡 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:

[source,bash]
----
gem install tone
----

You can also add the gem directly to your project:

[source,bash]
----
bundle add tone
----

Once the gem is installed, you only need to require it:

[source,ruby]
----
require "tone"
----

== Usage

Basic usage is as follows:

[source,ruby]
----
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:

[source,ruby]
----
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#\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:

[source,ruby]
----
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:

[source,ruby]
----
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_link} gem:

[source,ruby]
----
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_link} purposes.

Here's another example where a paragraph is used:

[source,ruby]
----
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:

``` ruby
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:

[source,ruby]
----
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:

[source,ruby]
----
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:

[source,ruby]
----
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:

[source,ruby]
----
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:

``` ruby
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:

[source,ruby]
----
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:

[source,ruby]
----
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:

[source,ruby]
----
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:

[source,ruby]
----
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.

[source,ruby]
----
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_link} matcher. This matcher is optional and _must be manually required_ for use in your spec helper:

[source,ruby]
----
# spec_helper.rb
require "tone/rspec/matchers/have_color"
----

Once required, you can leverage the matcher in any spec as follows:

[source,ruby]
----
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:

[source,ruby]
----
# 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:

[source,ruby]
----
# 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:

[source,bash]
----
git clone https://github.com/bkuhlmann/tone
cd tone
bin/setup
----

You can use the IRB console for direct access to all objects:

[source,bash]
----
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.

[source,bash]
----
bin/demo
----

== Tests

To test, run:

[source,bash]
----
bin/rake
----

== link:https://alchemists.io/policies/license[License]

== link:https://alchemists.io/policies/security[Security]

== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://alchemists.io/policies/contributions[Contributions]

== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]

== link:https://alchemists.io/projects/tone/versions[Versions]

== link:https://alchemists.io/community[Community]

== Credits

* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].

Owner

  • Name: Brooke Kuhlmann
  • Login: bkuhlmann
  • Kind: user
  • Location: Boulder, CO USA
  • Company: Alchemists

Quality over quantity.

Citation (CITATION.cff)

cff-version: 1.2.0
message: Please use the following metadata when citing this project in your work.
title: Tone
abstract: A customizable ANSI text terminal colorizer.
version: 2.4.0
license: Hippocratic-2.1
date-released: 2025-07-19
authors:
  - family-names: Kuhlmann
    given-names: Brooke
    affiliation: Alchemists
    orcid: https://orcid.org/0000-0002-5810-6268
keywords:
 - ruby
repository-code: https://github.com/bkuhlmann/tone
repository-artifact: https://rubygems.org/gems/tone
url: https://alchemists.io/projects/tone

GitHub Events

Total
  • Watch event: 1
  • Delete event: 44
  • Push event: 33
  • Create event: 10
Last Year
  • Watch event: 1
  • Delete event: 44
  • Push event: 33
  • Create event: 10

Issues and Pull Requests

Last synced: 6 months ago

All Time
  • Total issues: 0
  • Total pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Total issue authors: 0
  • Total pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
Pull Request Authors
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • rubygems 62,048 total
  • Total dependent packages: 4
  • Total dependent repositories: 1
  • Total versions: 26
  • Total maintainers: 1
rubygems.org: tone

A customizable ANSI text terminal colorizer.

  • Versions: 26
  • Dependent Packages: 4
  • Dependent Repositories: 1
  • Downloads: 62,048 Total
Rankings
Dependent packages count: 3.3%
Dependent repos count: 21.7%
Average: 23.1%
Stargazers count: 25.6%
Forks count: 31.6%
Downloads: 33.1%
Maintainers (1)
Funding
  • https://github.com/sponsors/bkuhlmann
Last synced: 6 months ago

Dependencies

Gemfile rubygems
  • amazing_print ~> 1.4 development
  • caliber ~> 0.25 development
  • debug ~> 1.7 development
  • git-lint ~> 5.0 development
  • guard-rspec ~> 4.7 development
  • rake ~> 13.0 development
  • reek ~> 6.1 development
  • rspec ~> 3.12 development
  • simplecov ~> 0.22 development
tone.gemspec rubygems
  • refinements ~> 10.0
  • zeitwerk ~> 2.6