Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions to style text (colors, weight, etc) #140

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docsite/source/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ sections:
- variadic-arguments
- commands-with-subcommands-and-params
- callbacks
- styling-your-output
---

`dry-cli` is a general-purpose framework for developing Command Line Interface (CLI) applications. It represents commands as objects that can be registered and offers support for arguments, options and forwarding variadic arguments to a sub-command.
Expand Down
59 changes: 59 additions & 0 deletions docsite/source/styling-your-output.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Styling your output
layout: gem-single
name: dry-cli
---

`dry-cli` comes with some functions to help you style text in the terminal. The program bellow demonstrate all available styles:

```ruby
#!/usr/bin/env ruby
require "bundler/setup"
require "dry/cli"

module StylesDemo
extend Dry::CLI::Registry

class Print < Dry::CLI::Command
desc "Demonstrate all available styles"

# rubocop:disable Metrics/AbcSize
def call
demo = <<~DEMO
`stylize("This is bold").bold` #=> #{stylize("This is bold").bold}
`stylize("This is dim").dim` #=> #{stylize("This is dim").dim}
`stylize("This is italic").italic` #=> #{stylize("This is italic").italic}
`stylize("This is underline").underline` #=> #{stylize("This is underline").underline}
`stylize("This blinks").blink` #=> #{stylize("This blinks").blink}
`stylize("This was reversed").reverse` #=> #{stylize("This was reversed").reverse}
`stylize("This is invisible").invisible` #=> #{stylize("This is invisible").invisible} (you can't see it, right?)
`stylize("This is black").black` #=> #{stylize("This is black").black}
`stylize("This is red").red` #=> #{stylize("This is red").red}
`stylize("This is green").green` #=> #{stylize("This is green").green}
`stylize("This is yellow").yellow` #=> #{stylize("This is yellow").yellow}
`stylize("This is blue").blue` #=> #{stylize("This is blue").blue}
`stylize("This is magenta").magenta` #=> #{stylize("This is magenta").magenta}
`stylize("This is cyan").cyan` #=> #{stylize("This is cyan").cyan}
`stylize("This is white").white` #=> #{stylize("This is white").white}
`stylize("This is black").on_black` #=> #{stylize("This is black").on_black}
`stylize("This is red").on_red` #=> #{stylize("This is red").on_red}
`stylize("This is green").on_green` #=> #{stylize("This is green").on_green}
`stylize("This is yellow").on_yellow` #=> #{stylize("This is yellow").on_yellow}
`stylize("This is blue").on_blue` #=> #{stylize("This is blue").on_blue}
`stylize("This is magenta").on_magenta` #=> #{stylize("This is magenta").on_magenta}
`stylize("This is cyan").on_cyan` #=> #{stylize("This is cyan").on_cyan}
`stylize("This is white").on_white` #=> #{stylize("This is white").on_white}
`stylize("This is bold red").bold.red #=> #{stylize("This is bold red").bold.red}
`stylize("This is bold on green").bold.on_green` #=> #{stylize("This is bold on green").bold.on_green}
`stylize("This is bold red on green").bold.red.on_green` #=> #{stylize("This is bold red on green").bold.red.on_green}
DEMO
puts demo
end
# rubocop:enable Metrics/AbcSize
end

register "print", Print
end

Dry.CLI(StylesDemo).call
```
3 changes: 3 additions & 0 deletions lib/dry/cli/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

require "forwardable"
require "dry/cli/option"
require "dry/cli/styles"

module Dry
class CLI
# Base class for commands
#
# @since 0.1.0
class Command
include Styles

# @since 0.1.0
# @api private
def self.inherited(base)
Expand Down
192 changes: 192 additions & 0 deletions lib/dry/cli/styles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# frozen_string_literal: true

module Dry
class CLI
# Collection of functions to style text
#
# @since 1.3.0
module Styles
RESET = 0
BOLD = 1
DIM = 2
ITALIC = 3
UNDERLINE = 4
BLINK = 5
REVERSE = 7
INVISIBLE = 8
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
ON_BLACK = 40
ON_RED = 41
ON_GREEN = 42
ON_YELLOW = 43
ON_BLUE = 44
ON_MAGENTA = 45
ON_CYAN = 46
ON_WHITE = 47

# Returns a text that can be styled
#
# @param text [String] text to be styled
#
# @since 1.3.0
def stylize(text)
StyledText.new(text)
end

# Styled text
#
# @since 1.3.0
class StyledText
def initialize(text)
@text = text
end

# Makes `StyledText` printable
#
# @since 1.3.0
def to_s
text + select_graphic_rendition(RESET)
end

# since 1.3.0
def bold
chainable_update!(BOLD, text)
end

# since 1.3.0
def dim
chainable_update!(DIM, text)
end

# since 1.3.0
def italic
chainable_update!(ITALIC, text)
end

# since 1.3.0
def underline
chainable_update!(UNDERLINE, text)
end

# since 1.3.0
def blink
chainable_update!(BLINK, text)
end

# since 1.3.0
def reverse
chainable_update!(REVERSE, text)
end

# since 1.3.0
def invisible
chainable_update!(INVISIBLE, text)
end

# since 1.3.0
def black
chainable_update!(BLACK, text)
end

# since 1.3.0
def red
chainable_update!(RED, text)
end

# since 1.3.0
def green
chainable_update!(GREEN, text)
end

# since 1.3.0
def yellow
chainable_update!(YELLOW, text)
end

# since 1.3.0
def blue
chainable_update!(BLUE, text)
end

# since 1.3.0
def magenta
chainable_update!(MAGENTA, text)
end

# since 1.3.0
def cyan
chainable_update!(CYAN, text)
end

# since 1.3.0
def white
chainable_update!(WHITE, text)
end

# since 1.3.0
def on_black
chainable_update!(ON_BLACK, text)
end

# since 1.3.0
def on_red
chainable_update!(ON_RED, text)
end

# since 1.3.0
def on_green
chainable_update!(ON_GREEN, text)
end

# since 1.3.0
def on_yellow
chainable_update!(ON_YELLOW, text)
end

# since 1.3.0
def on_blue
chainable_update!(ON_BLUE, text)
end

# since 1.3.0
def on_magenta
chainable_update!(ON_MAGENTA, text)
end

# since 1.3.0
def on_cyan
chainable_update!(ON_CYAN, text)
end

# since 1.3.0
def on_white
chainable_update!(ON_WHITE, text)
end

private

gustavothecoder marked this conversation as resolved.
Show resolved Hide resolved
attr_reader :text

# @since 1.3.0
# @api private
def chainable_update!(style, new_text)
@text = select_graphic_rendition(style) + new_text
self
end

# @since 1.3.0
# @api private
def select_graphic_rendition(code)
"\e[#{code}m"
end
end
end
end
end
34 changes: 34 additions & 0 deletions spec/integration/rendering_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,38 @@

expect(stderr).to eq(expected)
end

it "prints styled text" do
stdout, = Open3.capture3("styles print")

expected = <<~OUT
`stylize(\"This is bold\").bold` #=> \e[1mThis is bold\e[0m
`stylize(\"This is dim\").dim` #=> \e[2mThis is dim\e[0m
`stylize(\"This is italic\").italic` #=> \e[3mThis is italic\e[0m
`stylize(\"This is underline\").underline` #=> \e[4mThis is underline\e[0m
`stylize(\"This blinks\").blink` #=> \e[5mThis blinks\e[0m
`stylize(\"This was reversed\").reverse` #=> \e[7mThis was reversed\e[0m
`stylize(\"This is invisible\").invisible` #=> \e[8mThis is invisible\e[0m (you can't see it, right?)
`stylize(\"This is black\").black` #=> \e[30mThis is black\e[0m
`stylize(\"This is red\").red` #=> \e[31mThis is red\e[0m
`stylize(\"This is green\").green` #=> \e[32mThis is green\e[0m
`stylize(\"This is yellow\").yellow` #=> \e[33mThis is yellow\e[0m
`stylize(\"This is blue\").blue` #=> \e[34mThis is blue\e[0m
`stylize(\"This is magenta\").magenta` #=> \e[35mThis is magenta\e[0m
`stylize(\"This is cyan\").cyan` #=> \e[36mThis is cyan\e[0m
`stylize(\"This is white\").white` #=> \e[37mThis is white\e[0m
`stylize(\"This is black\").on_black` #=> \e[40mThis is black\e[0m
`stylize(\"This is red\").on_red` #=> \e[41mThis is red\e[0m
`stylize(\"This is green\").on_green` #=> \e[42mThis is green\e[0m
`stylize(\"This is yellow\").on_yellow` #=> \e[43mThis is yellow\e[0m
`stylize(\"This is blue\").on_blue` #=> \e[44mThis is blue\e[0m
`stylize(\"This is magenta\").on_magenta` #=> \e[45mThis is magenta\e[0m
`stylize(\"This is cyan\").on_cyan` #=> \e[46mThis is cyan\e[0m
`stylize(\"This is white\").on_white` #=> \e[47mThis is white\e[0m
`stylize(\"This is bold red\").bold.red #=> \e[31m\e[1mThis is bold red\e[0m
`stylize(\"This is bold on green\").bold.on_green` #=> \e[42m\e[1mThis is bold on green\e[0m
`stylize(\"This is bold red on green\").bold.red.on_green` #=> \e[42m\e[31m\e[1mThis is bold red on green\e[0m
OUT
expect(stdout).to eq(expected)
end
end
2 changes: 1 addition & 1 deletion spec/support/fixtures/foo
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module Foo
]

def call(engine: nil, **)
puts "console - engine: #{engine}"
puts stylize("console - engine: ").blue.bold, stylize(engine.to_s).magenta
end
end

Expand Down
51 changes: 51 additions & 0 deletions spec/support/fixtures/styles
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

$LOAD_PATH.unshift "#{__dir__}/../../../lib"
require "dry/cli"

module StylesDemo
extend Dry::CLI::Registry

class Print < Dry::CLI::Command
desc "Demonstrate all available styles"

# rubocop:disable Metrics/AbcSize
def call
demo = <<~DEMO
`stylize("This is bold").bold` #=> #{stylize("This is bold").bold}
`stylize("This is dim").dim` #=> #{stylize("This is dim").dim}
`stylize("This is italic").italic` #=> #{stylize("This is italic").italic}
`stylize("This is underline").underline` #=> #{stylize("This is underline").underline}
`stylize("This blinks").blink` #=> #{stylize("This blinks").blink}
`stylize("This was reversed").reverse` #=> #{stylize("This was reversed").reverse}
`stylize("This is invisible").invisible` #=> #{stylize("This is invisible").invisible} (you can't see it, right?)
`stylize("This is black").black` #=> #{stylize("This is black").black}
`stylize("This is red").red` #=> #{stylize("This is red").red}
`stylize("This is green").green` #=> #{stylize("This is green").green}
`stylize("This is yellow").yellow` #=> #{stylize("This is yellow").yellow}
`stylize("This is blue").blue` #=> #{stylize("This is blue").blue}
`stylize("This is magenta").magenta` #=> #{stylize("This is magenta").magenta}
`stylize("This is cyan").cyan` #=> #{stylize("This is cyan").cyan}
`stylize("This is white").white` #=> #{stylize("This is white").white}
`stylize("This is black").on_black` #=> #{stylize("This is black").on_black}
`stylize("This is red").on_red` #=> #{stylize("This is red").on_red}
`stylize("This is green").on_green` #=> #{stylize("This is green").on_green}
`stylize("This is yellow").on_yellow` #=> #{stylize("This is yellow").on_yellow}
`stylize("This is blue").on_blue` #=> #{stylize("This is blue").on_blue}
`stylize("This is magenta").on_magenta` #=> #{stylize("This is magenta").on_magenta}
`stylize("This is cyan").on_cyan` #=> #{stylize("This is cyan").on_cyan}
`stylize("This is white").on_white` #=> #{stylize("This is white").on_white}
`stylize("This is bold red").bold.red #=> #{stylize("This is bold red").bold.red}
`stylize("This is bold on green").bold.on_green` #=> #{stylize("This is bold on green").bold.on_green}
`stylize("This is bold red on green").bold.red.on_green` #=> #{stylize("This is bold red on green").bold.red.on_green}
DEMO
puts demo
end
# rubocop:enable Metrics/AbcSize
end

register "print", Print
end

Dry.CLI(StylesDemo).call