From ad75bc1faef232294a04bbdc2c91d8a109ef6a1d Mon Sep 17 00:00:00 2001 From: Gustavo Ribeiro Date: Sun, 8 Dec 2024 09:45:18 -0300 Subject: [PATCH] Apply code review suggestions - Changed the API to `stylize("string").bold.blue.on_white` - Moved magic numbers to constants --- docsite/source/styling-your-output.html.md | 52 ++-- lib/dry/cli/styles.rb | 295 ++++++++++++--------- spec/integration/rendering_spec.rb | 34 +++ spec/support/fixtures/foo | 2 +- spec/support/fixtures/styles | 52 ++-- spec/unit/dry/cli/styles_spec.rb | 17 -- 6 files changed, 260 insertions(+), 192 deletions(-) delete mode 100644 spec/unit/dry/cli/styles_spec.rb diff --git a/docsite/source/styling-your-output.html.md b/docsite/source/styling-your-output.html.md index 931657e..bcec9dc 100644 --- a/docsite/source/styling-your-output.html.md +++ b/docsite/source/styling-your-output.html.md @@ -20,32 +20,32 @@ module StylesDemo # rubocop:disable Metrics/AbcSize def call demo = <<~DEMO - `bold` #{bold("This is bold")} - `dim` #{dim("This is dim")} - `italic` #{italic("This is italic")} - `underline` #{underline("This is underline")} - `blink` #{blink("This blinks")} - `reverse` #{reverse("This was reversed")} - `invisible` #{invisible("This is invisible")} (you can't see it, right?) - `black` #{black("This is black")} - `red` #{red("This is red")} - `green` #{green("This is green")} - `yellow` #{yellow("This is yellow")} - `blue` #{blue("This is blue")} - `magenta` #{magenta("This is magenta")} - `cyan` #{cyan("This is cyan")} - `white` #{white("This is white")} - `on_black` #{on_black("This is black")} - `on_red` #{on_red("This is red")} - `on_green` #{on_green("This is green")} - `on_yellow` #{on_yellow("This is yellow")} - `on_blue` #{on_blue("This is blue")} - `on_magenta` #{on_magenta("This is magenta")} - `on_cyan` #{on_cyan("This is cyan")} - `on_white` #{on_white("This is white")} - `bold`+`red`: #{bold(red("This is bold red"))} - `bold`+`on_green`: #{bold(on_green("This is bold on green"))} - `bold`+`red`+`on_green`: #{bold(red(on_green("This is bold red on green")))} + `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 diff --git a/lib/dry/cli/styles.rb b/lib/dry/cli/styles.rb index 4df7fa3..03e6809 100644 --- a/lib/dry/cli/styles.rb +++ b/lib/dry/cli/styles.rb @@ -2,134 +2,185 @@ module Dry class CLI - # Collection of functions to style text. + # Collection of functions to style text # # @since 1.3.0 module Styles - # since 1.3.0 - def bold(text) - ensure_clean_sequence("\e[1m#{text}") - end - - # since 1.3.0 - def dim(text) - ensure_clean_sequence("\e[2m#{text}") - end - - # since 1.3.0 - def italic(text) - ensure_clean_sequence("\e[3m#{text}") - end - - # since 1.3.0 - def underline(text) - ensure_clean_sequence("\e[4m#{text}") - end - - # since 1.3.0 - def blink(text) - ensure_clean_sequence("\e[5m#{text}") - end - - # since 1.3.0 - def reverse(text) - ensure_clean_sequence("\e[7m#{text}") - end - - # since 1.3.0 - def invisible(text) - ensure_clean_sequence("\e[8m#{text}") - end - - # since 1.3.0 - def black(text) - ensure_clean_sequence("\e[30m#{text}") - end - - # since 1.3.0 - def red(text) - ensure_clean_sequence("\e[31m#{text}") - end - - # since 1.3.0 - def green(text) - ensure_clean_sequence("\e[32m#{text}") - end - - # since 1.3.0 - def yellow(text) - ensure_clean_sequence("\e[33m#{text}") - end - - # since 1.3.0 - def blue(text) - ensure_clean_sequence("\e[34m#{text}") - end - - # since 1.3.0 - def magenta(text) - ensure_clean_sequence("\e[35m#{text}") - end - - # since 1.3.0 - def cyan(text) - ensure_clean_sequence("\e[36m#{text}") - end - - # since 1.3.0 - def white(text) - ensure_clean_sequence("\e[37m#{text}") - end - - # since 1.3.0 - def on_black(text) - ensure_clean_sequence("\e[40m#{text}") - end - - # since 1.3.0 - def on_red(text) - ensure_clean_sequence("\e[41m#{text}") - end - - # since 1.3.0 - def on_green(text) - ensure_clean_sequence("\e[42m#{text}") - end - - # since 1.3.0 - def on_yellow(text) - ensure_clean_sequence("\e[43m#{text}") - end - - # since 1.3.0 - def on_blue(text) - ensure_clean_sequence("\e[44m#{text}") - end - - # since 1.3.0 - def on_magenta(text) - ensure_clean_sequence("\e[45m#{text}") - end - - # since 1.3.0 - def on_cyan(text) - ensure_clean_sequence("\e[46m#{text}") - end - - # since 1.3.0 - def on_white(text) - ensure_clean_sequence("\e[47m#{text}") + 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 - private - + # Styled text + # # @since 1.3.0 - # @api private - def ensure_clean_sequence(text) - clen_text = text - clear = "\e[0m" - clen_text += clear unless text.end_with?(clear) - clen_text + class StyledText + def initialize(text) + @text = text + end + + # Makes `StyledText` printable + # + # @since 1.3.0 + def to_s + @text + end + + # since 1.3.0 + def bold + chainable_update!("\e[#{BOLD}m#{@text}") + end + + # since 1.3.0 + def dim + chainable_update!("\e[#{DIM}m#{@text}") + end + + # since 1.3.0 + def italic + chainable_update!("\e[#{ITALIC}m#{@text}") + end + + # since 1.3.0 + def underline + chainable_update!("\e[#{UNDERLINE}m#{@text}") + end + + # since 1.3.0 + def blink + chainable_update!("\e[#{BLINK}m#{@text}") + end + + # since 1.3.0 + def reverse + chainable_update!("\e[#{REVERSE}m#{@text}") + end + + # since 1.3.0 + def invisible + chainable_update!("\e[#{INVISIBLE}m#{@text}") + end + + # since 1.3.0 + def black + chainable_update!("\e[#{BLACK}m#{@text}") + end + + # since 1.3.0 + def red + chainable_update!("\e[#{RED}m#{@text}") + end + + # since 1.3.0 + def green + chainable_update!("\e[#{GREEN}m#{@text}") + end + + # since 1.3.0 + def yellow + chainable_update!("\e[#{YELLOW}m#{@text}") + end + + # since 1.3.0 + def blue + chainable_update!("\e[#{BLUE}m#{@text}") + end + + # since 1.3.0 + def magenta + chainable_update!("\e[#{MAGENTA}m#{@text}") + end + + # since 1.3.0 + def cyan + chainable_update!("\e[#{CYAN}m#{@text}") + end + + # since 1.3.0 + def white + chainable_update!("\e[#{WHITE}m#{@text}") + end + + # since 1.3.0 + def on_black + chainable_update!("\e[#{ON_BLACK}m#{@text}") + end + + # since 1.3.0 + def on_red + chainable_update!("\e[#{ON_RED}m#{@text}") + end + + # since 1.3.0 + def on_green + chainable_update!("\e[#{ON_GREEN}m#{@text}") + end + + # since 1.3.0 + def on_yellow + chainable_update!("\e[#{ON_YELLOW}m#{@text}") + end + + # since 1.3.0 + def on_blue + chainable_update!("\e[#{ON_BLUE}m#{@text}") + end + + # since 1.3.0 + def on_magenta + chainable_update!("\e[#{ON_MAGENTA}m#{@text}") + end + + # since 1.3.0 + def on_cyan + chainable_update!("\e[#{ON_CYAN}m#{@text}") + end + + # since 1.3.0 + def on_white + chainable_update!("\e[#{ON_WHITE}m#{@text}") + end + + private + + # @since 1.3.0 + # @api private + def chainable_update!(new_text) + clen_text = new_text + clear = "\e[#{RESET}m" + clen_text += clear unless clen_text.end_with?(clear) + @text = clen_text + self + end end end end diff --git a/spec/integration/rendering_spec.rb b/spec/integration/rendering_spec.rb index 3b0209d..9bf70f6 100644 --- a/spec/integration/rendering_spec.rb +++ b/spec/integration/rendering_spec.rb @@ -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 diff --git a/spec/support/fixtures/foo b/spec/support/fixtures/foo index 9ee4b46..7ad392b 100755 --- a/spec/support/fixtures/foo +++ b/spec/support/fixtures/foo @@ -35,7 +35,7 @@ module Foo ] def call(engine: nil, **) - puts blue(bold("console - engine: ")), magenta(engine.to_s) + puts stylize("console - engine: ").blue.bold, stylize(engine.to_s).magenta end end diff --git a/spec/support/fixtures/styles b/spec/support/fixtures/styles index 638bffa..b71c0ba 100755 --- a/spec/support/fixtures/styles +++ b/spec/support/fixtures/styles @@ -13,32 +13,32 @@ module StylesDemo # rubocop:disable Metrics/AbcSize def call demo = <<~DEMO - `bold` #{bold("This is bold")} - `dim` #{dim("This is dim")} - `italic` #{italic("This is italic")} - `underline` #{underline("This is underline")} - `blink` #{blink("This blinks")} - `reverse` #{reverse("This was reversed")} - `invisible` #{invisible("This is invisible")} (you can't see it, right?) - `black` #{black("This is black")} - `red` #{red("This is red")} - `green` #{green("This is green")} - `yellow` #{yellow("This is yellow")} - `blue` #{blue("This is blue")} - `magenta` #{magenta("This is magenta")} - `cyan` #{cyan("This is cyan")} - `white` #{white("This is white")} - `on_black` #{on_black("This is black")} - `on_red` #{on_red("This is red")} - `on_green` #{on_green("This is green")} - `on_yellow` #{on_yellow("This is yellow")} - `on_blue` #{on_blue("This is blue")} - `on_magenta` #{on_magenta("This is magenta")} - `on_cyan` #{on_cyan("This is cyan")} - `on_white` #{on_white("This is white")} - `bold`+`red`: #{bold(red("This is bold red"))} - `bold`+`on_green`: #{bold(on_green("This is bold on green"))} - `bold`+`red`+`on_green`: #{bold(red(on_green("This is bold red on green")))} + `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 diff --git a/spec/unit/dry/cli/styles_spec.rb b/spec/unit/dry/cli/styles_spec.rb deleted file mode 100644 index 7cff1d9..0000000 --- a/spec/unit/dry/cli/styles_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "Styles" do - class Dummy - include Dry::CLI::Styles - - def bold_and_black - black(bold("two")) - end - end - - it "doesn't duplicate clean escape sequences" do - bold_and_black = Dummy.new.bold_and_black - expect(bold_and_black.end_with?("\e[0m")).to eq(true) - expect(bold_and_black.end_with?("\e[0m\e[0m")).to eq(false) - end -end