paintbrush 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +17 -1
  4. data/Makefile +9 -0
  5. data/README.md +3 -0
  6. data/lib/paintbrush.rb +21 -11
  7. data/lib/{paintbrush → paintbrush_support}/bounded_color_element.rb +1 -1
  8. data/lib/{paintbrush → paintbrush_support}/color_element.rb +1 -1
  9. data/lib/{paintbrush → paintbrush_support}/colorized_string.rb +8 -5
  10. data/lib/paintbrush_support/colors.rb +60 -0
  11. data/lib/paintbrush_support/configuration.rb +40 -0
  12. data/lib/{paintbrush → paintbrush_support}/element_tree.rb +1 -1
  13. data/lib/{paintbrush → paintbrush_support}/escapes.rb +1 -1
  14. data/lib/paintbrush_support/hex_color_code.rb +40 -0
  15. data/lib/paintbrush_support/version.rb +5 -0
  16. data/paintbrush.gemspec +3 -2
  17. data/rspec-documentation/pages/000-Introduction.md +23 -0
  18. data/rspec-documentation/pages/010-Colors.md +52 -0
  19. data/rspec-documentation/pages/020-Examples/010-Basic Usage.md +19 -0
  20. data/rspec-documentation/pages/020-Examples/020-Bright Colors.md +15 -0
  21. data/rspec-documentation/pages/020-Examples/030-Nested Colors.md +13 -0
  22. data/rspec-documentation/pages/020-Examples/040-RGB Colors.md +29 -0
  23. data/rspec-documentation/pages/020-Examples/050-Dynamic Usage.md +29 -0
  24. data/rspec-documentation/pages/020-Examples/060-Without Including.md +15 -0
  25. data/rspec-documentation/pages/020-Examples.md +20 -0
  26. data/rspec-documentation/pages/030-Configuration.md +63 -0
  27. data/rspec-documentation/pages/040-How It Works.md +83 -0
  28. data/rspec-documentation/spec_helper.rb +8 -0
  29. metadata +25 -12
  30. data/lib/paintbrush/colors.rb +0 -27
  31. data/lib/paintbrush/version.rb +0 -5
  32. data/rspec-documentation/bundle/introduction.html +0 -312
  33. data/rspec-documentation/pages/Introduction.md +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb568b4656208e4b1d5e134b9a6982a50be63f9da4a776aa7210a302f65a59ce
4
- data.tar.gz: cc92c16814e12eb90e089c5affaa6c4739ecbec1f9121093739c6e776e2aaf0f
3
+ metadata.gz: 69b5f9f4807975898e5e3c9ee9a96b183df59a1e57cd642305710479c43e2482
4
+ data.tar.gz: d2b507b2d2ce9a562bdba5066b8d5331faa9f8e1c11dbeb7e75320bb46dac26d
5
5
  SHA512:
6
- metadata.gz: dbc3a7c11aedf89c5191e1a538b902235c84e7a540b7d155ee008c1ad58e56a2eb4f139f151d681f06ba93fc7345221130ba0972d7d331ac6e793ef8b7752172
7
- data.tar.gz: c2bc6bb3a2e09ec5c9aa2d25c47f1b118cf01e2d10eef340e430806531c05421b11e7292d677d96f12112f1d252d64cb1c673a1f50bcf99d6ac8c0a493a98116
6
+ metadata.gz: 321564b1aaa6befd52e1b2703b6ac3345824188434f3634f333c39577b1b50fc7517a91c3490a0e34b865b63bb5e55e101ce287fb570a5e22a763c22cf2aa92a
7
+ data.tar.gz: 77e3ab29dd45a4bf28a6b6a1a6e6afdc49414de62b58d801eb5d453167a0d87e359613642ca4b9bcb4345565ae619ba56f0002bdc1c09b46057d669c18a5e003
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ gem 'rake', '~> 13.0'
9
9
 
10
10
  gem 'devpack', '~> 0.4.1'
11
11
  gem 'rspec', '~> 3.0'
12
+ gem 'rspec-documentation', '~> 0.0.2'
12
13
  gem 'rubocop', '~> 1.51'
13
14
  gem 'rubocop-rake', '~> 0.6.0'
14
15
  gem 'rubocop-rspec', '~> 2.22'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- paintbrush (0.1.1)
4
+ paintbrush (0.1.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,23 +10,38 @@ GEM
10
10
  concurrent-ruby (1.2.2)
11
11
  devpack (0.4.1)
12
12
  diff-lcs (1.5.0)
13
+ htmlbeautifier (1.4.2)
13
14
  i18n (1.13.0)
14
15
  concurrent-ruby (~> 1.0)
15
16
  json (2.6.3)
17
+ kramdown (2.4.0)
18
+ rexml
19
+ kramdown-parser-gfm (1.1.0)
20
+ kramdown (~> 2.0)
16
21
  paint (2.3.0)
17
22
  parallel (1.23.0)
18
23
  parser (3.2.2.1)
19
24
  ast (~> 2.4.1)
20
25
  rainbow (3.1.1)
21
26
  rake (13.0.6)
27
+ redcarpet (3.6.0)
22
28
  regexp_parser (2.8.0)
23
29
  rexml (3.2.5)
30
+ rouge (4.1.1)
24
31
  rspec (3.12.0)
25
32
  rspec-core (~> 3.12.0)
26
33
  rspec-expectations (~> 3.12.0)
27
34
  rspec-mocks (~> 3.12.0)
28
35
  rspec-core (3.12.2)
29
36
  rspec-support (~> 3.12.0)
37
+ rspec-documentation (0.0.2)
38
+ htmlbeautifier (~> 1.4)
39
+ kramdown (~> 2.4)
40
+ kramdown-parser-gfm (~> 1.1)
41
+ paintbrush (~> 0.1.1)
42
+ redcarpet (~> 3.6)
43
+ rouge (~> 4.1)
44
+ rspec (~> 3.12)
30
45
  rspec-expectations (3.12.3)
31
46
  diff-lcs (>= 1.2.0, < 2.0)
32
47
  rspec-support (~> 3.12.0)
@@ -70,6 +85,7 @@ DEPENDENCIES
70
85
  paintbrush!
71
86
  rake (~> 13.0)
72
87
  rspec (~> 3.0)
88
+ rspec-documentation (~> 0.0.2)
73
89
  rubocop (~> 1.51)
74
90
  rubocop-rake (~> 0.6.0)
75
91
  rubocop-rspec (~> 2.22)
data/Makefile CHANGED
@@ -1,5 +1,14 @@
1
+ project=paintbrush
2
+
1
3
  .PHONY: test
2
4
  test:
3
5
  bundle exec rspec
4
6
  bundle exec rubocop
5
7
  bundle exec strong_versions
8
+ bundle exec rspec-documentation
9
+
10
+ .PHONY: publish
11
+ publish:
12
+ @RSPEC_DOCUMENTATION_URL_ROOT='/$(project)' bundle exec rspec-documentation
13
+ @rsync --delete -r rspec-documentation/bundle/ docs01.bob.frl:/mnt/docs/$(project)/
14
+ @echo 'Published.'
data/README.md CHANGED
@@ -42,11 +42,14 @@ Include the `Paintbrush` module anywhere and call `#paintbrush` to generate a co
42
42
  * `#white`
43
43
  * `#default`
44
44
 
45
+ Hex colors are also available as `#hex_ff00ff` and `#hex_f0f`, allowing a much wider range of colors.
46
+
45
47
  Use [string interpolation](https://docs.ruby-lang.org/en/3.2/syntax/literals_rdoc.html#label-String+Literals) to nest multiple colors:
46
48
 
47
49
  ```ruby
48
50
  include Paintbrush
49
51
  puts paintbrush { green "some green text, #{yellow "some yellow text"} and some green again" }
52
+ puts paintbrush { hex_ff00ff "some magenta #{hex_ffff00 "and some yellow"} and magenta again" }
50
53
  ```
51
54
 
52
55
  ## Alternatives
data/lib/paintbrush.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'paintbrush/version'
4
- require_relative 'paintbrush/escapes'
5
- require_relative 'paintbrush/colors'
6
- require_relative 'paintbrush/colorized_string'
7
- require_relative 'paintbrush/color_element'
8
- require_relative 'paintbrush/bounded_color_element'
9
- require_relative 'paintbrush/element_tree'
3
+ require_relative 'paintbrush_support/version'
4
+ require_relative 'paintbrush_support/configuration'
5
+ require_relative 'paintbrush_support/escapes'
6
+ require_relative 'paintbrush_support/colors'
7
+ require_relative 'paintbrush_support/colorized_string'
8
+ require_relative 'paintbrush_support/color_element'
9
+ require_relative 'paintbrush_support/hex_color_code'
10
+ require_relative 'paintbrush_support/bounded_color_element'
11
+ require_relative 'paintbrush_support/element_tree'
10
12
 
11
13
  # Colorizes a string, provides `#paintbrush`. When included/extended in a class, call
12
14
  # `#paintbrush` and pass a block to use the provided dynamically defined methods, e.g.:
@@ -20,11 +22,19 @@ require_relative 'paintbrush/element_tree'
20
22
  # end
21
23
  # ```
22
24
  module Paintbrush
23
- def self.paintbrush(&block)
24
- ColorizedString.new(&block).colorized
25
+ def self.paintbrush(colorize: nil, &block)
26
+ PaintbrushSupport::ColorizedString.new(colorize: colorize, &block).colorized
25
27
  end
26
28
 
27
- def paintbrush(&block)
28
- Paintbrush.paintbrush(&block)
29
+ def self.configure
30
+ yield PaintbrushSupport::Configuration
31
+ end
32
+
33
+ def self.with_configuration(**options, &block)
34
+ PaintbrushSupport::Configuration.with_configuration(**options, &block)
35
+ end
36
+
37
+ def paintbrush(colorize: nil, &block)
38
+ Paintbrush.paintbrush(colorize: colorize, &block)
29
39
  end
30
40
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Paintbrush
3
+ module PaintbrushSupport
4
4
  # Wraps a Paintbrush::ColorElement instance and maps its start and end boundaries within a
5
5
  # compiled escaped string by matching specific unique (indexed) escape codes. Provides
6
6
  # `#surround?` for detecting if another element exists within the current element's boundaries.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Paintbrush
3
+ module PaintbrushSupport
4
4
  # Provides a substring enclosed in unique escape codes for later colorization when the full
5
5
  # string has been created and all interpolation is completed. Adds itself to a provided stack
6
6
  # of ColorElement objects on initialization.
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Paintbrush
3
+ module PaintbrushSupport
4
4
  # Core string colorization, provides various methods for colorizing a string, uses escape
5
5
  # sequences to store references to start and end of each coloring method to allow nested
6
6
  # colorizing with string interpolation within each individual call to `paintbrush`.
7
7
  class ColorizedString
8
- def initialize(&block)
8
+ def initialize(colorize:, &block)
9
+ @colorize = colorize
9
10
  @block = block
10
11
  @stack = []
11
12
  end
@@ -14,7 +15,9 @@ module Paintbrush
14
15
  # method in the provided block, rebuilds the string and returns the value with regular ANSI
15
16
  # color codes ready to be output to a console.
16
17
  def colorized
17
- colorized_string
18
+ Configuration.with_configuration(colorize: @colorize) do
19
+ colorized_string
20
+ end
18
21
  end
19
22
 
20
23
  private
@@ -53,8 +56,8 @@ module Paintbrush
53
56
 
54
57
  def context
55
58
  eval('self', block.binding, __FILE__, __LINE__).dup.tap do |context|
56
- context.send(:include, Paintbrush::Colors) if context.respond_to?(:include)
57
- context.send(:extend, Paintbrush::Colors) if context.respond_to?(:extend)
59
+ context.send(:include, PaintbrushSupport::Colors) if context.respond_to?(:include)
60
+ context.send(:extend, PaintbrushSupport::Colors) if context.respond_to?(:extend)
58
61
  context.send(:instance_variable_set, :@__stack, stack)
59
62
  end
60
63
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaintbrushSupport
4
+ # Provides methods that are temporarily injected into block context. Each method returns an
5
+ # escaped string including the current stack size with start and end escape codes, allowing the
6
+ # string to be reconstituted afterwards with nested strings restoring the previous color once
7
+ # they have terminated.
8
+ module Colors
9
+ COLOR_CODES = {
10
+ black: '30',
11
+ red: '31',
12
+ green: '32',
13
+ yellow: '33',
14
+ blue: '34',
15
+ purple: '35',
16
+ cyan: '36',
17
+ white: '37',
18
+ default: '39',
19
+ black_b: '90',
20
+ red_b: '91',
21
+ green_b: '92',
22
+ yellow_b: '93',
23
+ blue_b: '94',
24
+ purple_b: '95',
25
+ cyan_b: '96',
26
+ white_b: '97'
27
+ }.freeze
28
+
29
+ HEX_CODE_REGEXP = /(?:hex_[a-fA-F0-9]{3}){1,2}/.freeze
30
+
31
+ COLOR_CODES.each do |name, code|
32
+ define_method name do |string|
33
+ if Configuration.colorize?
34
+ ColorElement.new(stack: @__stack, code: code, string: string).to_s
35
+ else
36
+ string
37
+ end
38
+ end
39
+ end
40
+
41
+ def method_missing(method_name, *args)
42
+ return super unless method_name.match?(HEX_CODE_REGEXP)
43
+ return string unless Configuration.colorize?
44
+
45
+ return unless Configuration.colorize?
46
+
47
+ ColorElement.new(
48
+ stack: @__stack,
49
+ code: HexColorCode.new(hex_code: method_name).escape_sequence,
50
+ string: args.first
51
+ ).to_s
52
+ end
53
+
54
+ def respond_to_missing?(*)
55
+ return super unless method_name.match?(HEX_CODE_REGEXP)
56
+
57
+ true
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaintbrushSupport
4
+ # Provides a configuration interface for Paintbrush features, allows disabling colorization.
5
+ #
6
+ # Usage:
7
+ #
8
+ # ```ruby
9
+ # Paintbrush::Configuration.colorize = false
10
+ # ```
11
+ module Configuration
12
+ @defaults = {
13
+ colorize: true
14
+ }
15
+
16
+ @configuration = {}
17
+
18
+ class << self
19
+ attr_reader :configuration, :defaults
20
+
21
+ def colorize=(val)
22
+ configuration[:colorize] = val
23
+ end
24
+
25
+ def colorize?
26
+ configuration.fetch(:colorize, defaults[:colorize])
27
+ end
28
+
29
+ def reset
30
+ @configuration = {}
31
+ end
32
+
33
+ def with_configuration(**options, &block)
34
+ previous = configuration.dup
35
+ options.compact.each { |key, value| configuration[key] = value unless configuration.key?(key) }
36
+ block.call.tap { @configuration = previous }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Paintbrush
3
+ module PaintbrushSupport
4
4
  # A tree of BoundedColorElement objects. Used to build a full tree of colorized substrings in
5
5
  # order to allow discovery of parent substrings and use their color code to restore to when the
6
6
  # substring is terminated. Allows deeply-nested colorized strings.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Paintbrush
3
+ module PaintbrushSupport
4
4
  # Provides an authority on escape code generation. Provides `.close` and `.open`, both of which
5
5
  # receive an index (i.e. the current size of the stack). Used for escape code insertion and comparison.
6
6
  module Escapes
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaintbrushSupport
4
+ # Translates a string in format `hex_ff00ff` or `hex_f0f` into an RGB escape code sequence.
5
+ # Allows calling e.g.:
6
+ #
7
+ # paintbrush { hex_ff0ff 'hello in magenta' }
8
+ #
9
+ class HexColorCode
10
+ def initialize(hex_code:)
11
+ @hex_code = hex_code
12
+ end
13
+
14
+ def escape_sequence
15
+ (%w[38 2] + encoded_pairs).join(';')
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :hex_code
21
+
22
+ def encoded_pairs
23
+ pairs.map { |pair| pair.to_i(16).to_s }
24
+ end
25
+
26
+ def pairs
27
+ normalized_hex_string.chars.each_slice(2).map(&:join)
28
+ end
29
+
30
+ def hex_string
31
+ @hex_string ||= hex_code.to_s.partition('hex_').last
32
+ end
33
+
34
+ def normalized_hex_string
35
+ return hex_string if hex_string.size == 6
36
+
37
+ hex_string.chars.map { |char| "#{char}#{char}" }.join
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaintbrushSupport
4
+ VERSION = '0.1.3'
5
+ end
data/paintbrush.gemspec CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/paintbrush/version'
3
+ require_relative 'lib/paintbrush_support/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'paintbrush'
7
- spec.version = Paintbrush::VERSION
7
+ spec.version = PaintbrushSupport::VERSION
8
8
  spec.authors = ['Bob Farrell']
9
9
  spec.email = ['git@bob.frl']
10
+ spec.licenses = ['MIT']
10
11
 
11
12
  spec.summary = 'Hassle-free text coloring for console applications.'
12
13
  spec.description = 'Provides a set of encapsulated methods for nested colorization of strings.'
@@ -0,0 +1,23 @@
1
+ # Introduction
2
+
3
+ Simple and concise string colorization for _Ruby_ without overloading `String` methods or requiring verbose class/method invocation.
4
+
5
+ _Paintbrush_ has zero dependencies and does not pollute any namespaces or objects outside of the `#paintbrush` method wherever you include the `Paintbrush` module.
6
+
7
+ Nesting is supported, allowing you to use multiple colors within the same string. The previous color is automatically restored.
8
+
9
+ ## Quick Example
10
+
11
+ ```rspec:ansi
12
+ require 'paintbrush'
13
+
14
+ include Paintbrush
15
+
16
+ subject { paintbrush { purple "You used #{green 'four'} #{blue "(#{cyan '4'})"} #{yellow 'colors'} today!" } }
17
+
18
+ it 'outputs simple colorized strings' do
19
+ expect(subject).to eql "\e[35mYou used \e[32mfour\e[0m\e[35m " \
20
+ "\e[34m(\e[36m4\e[0m\e[34m)\e[0m\e[35m " \
21
+ "\e[33mcolors\e[0m\e[35m today!\e[0m\e[0m"
22
+ end
23
+ ```
@@ -0,0 +1,52 @@
1
+ # Colors
2
+
3
+ _Paintbrush_ defines the following colors. Each color is avaiable as a method call inside a block passed to the `paintbrush` method.
4
+
5
+ ## Basic Colors
6
+
7
+ * `black`
8
+ * `red`
9
+ * `green`
10
+ * `yellow`
11
+ * `blue`
12
+ * `purple`
13
+ * `cyan`
14
+ * `white`
15
+ * `default`
16
+
17
+ ```rspec:ansi
18
+ subject { paintbrush { purple 'a purple string' } }
19
+
20
+ it { is_expected.to include "\e[35ma purple string" }
21
+ ```
22
+
23
+ ## Bright Colors
24
+
25
+ Add the `_b` suffix to get the "bright" version of the relevant color.
26
+
27
+ * `black_b`
28
+ * `red_b`
29
+ * `green_b`
30
+ * `yellow_b`
31
+ * `blue_b`
32
+ * `purple_b`
33
+ * `cyan_b`
34
+ * `white_b`
35
+
36
+ ```rspec:ansi
37
+ subject { paintbrush { purple_b 'a bright purple string' } }
38
+
39
+ it { is_expected.to include "\e[95ma bright purple string" }
40
+ ```
41
+
42
+ ## RGB Colors
43
+
44
+ As well as the colors listed below, any RGB hex color code (e.g. `ff00ff`) is available as a method prefixed with `hex_`, e.g. use `hex_ff00ff` for magenta.
45
+
46
+ The shorthand versions `#hex_f0f` are also provided, i.e. `#hex_f0f` is equivalent to `#hex_ff00ff`.
47
+
48
+ ```rspec:ansi
49
+ subject { paintbrush { hex_f0f 'a magenta string' } }
50
+
51
+ it { is_expected.to include "\e[38;2;255;0;255ma magenta string" }
52
+ ```
@@ -0,0 +1,19 @@
1
+ # Basic Usage
2
+
3
+ The most basic usage of _Paintbrush_ is with no colorization at all:
4
+
5
+ ```rspec:ansi
6
+ subject { paintbrush { 'an uncolorized string' } }
7
+
8
+ it { is_expected.to eql 'an uncolorized string' }
9
+ ```
10
+
11
+ As you can see, a duplicate of the original string is returned intact with no modifications.
12
+
13
+ The second-most basic usage of _Paintbrush_ is with a single color:
14
+
15
+ ```rspec:ansi
16
+ subject { paintbrush { green 'some green text' } }
17
+
18
+ it { is_expected.to include 'some green text' }
19
+ ```
@@ -0,0 +1,15 @@
1
+ # Bright Colors
2
+
3
+ Use the `_b` suffix for a color name to make a **bright** color. See [Colors](../colors.html) for more details.
4
+
5
+ ```rspec:ansi
6
+ subject { paintbrush { blue_b "bright blue" } }
7
+
8
+ it { is_expected.to include 'bright blue' }
9
+ ```
10
+
11
+ ```rspec:ansi
12
+ subject { paintbrush { "#{blue "some blue text"} #{blue_b "and some bright blue text"}" } }
13
+
14
+ it { is_expected.to include 'bright blue' }
15
+ ```
@@ -0,0 +1,13 @@
1
+ # Nested Colors
2
+
3
+ _Paintbrush_ supports unlimited levels of nesting, allowing you to use one color inside another color and have the text revert back to its previous color. This lets you create complex colorized strings without having to manually revert back manually.
4
+
5
+ ```rspec:ansi
6
+ subject do
7
+ paintbrush do
8
+ green "green, #{blue "blue, #{cyan "cyan #{yellow "and yellow"}, back to cyan"}, back to blue"}, and back to green"
9
+ end
10
+ end
11
+
12
+ it { is_expected.to include "and yellow" }
13
+ ```
@@ -0,0 +1,29 @@
1
+ # RGB Colors
2
+
3
+ Using _RGB_ colors gives you the full range of over 16 Million colors to use in your terminal. We won't provide an example for each color but here are a few to give you an idea of how it works.
4
+
5
+ ```rspec:ansi
6
+ subject do
7
+ paintbrush do
8
+ "#{hex_f00 'R'}#{hex_ffa500 'A'}#{hex_ff0 'I'}#{hex_080 'N'}" \
9
+ "#{hex_00f 'B'}#{hex_4b0082 'O'}#{hex_ee82ee 'W'}"
10
+ end
11
+ end
12
+
13
+ it 'outputs a rainbow' do
14
+ expect(subject).to eql(
15
+ "\e[38;2;255;0;0mR\e[0m\e[0m\e[38;2;255;165;0mA\e[0m\e[0m" \
16
+ "\e[38;2;255;255;0mI\e[0m\e[0m\e[38;2;0;136;0mN\e[0m\e[0m" \
17
+ "\e[38;2;0;0;255mB\e[0m\e[0m\e[38;2;75;0;130mO\e[0m\e[0m" \
18
+ "\e[38;2;238;130;238mW\e[0m\e[0m"
19
+ )
20
+ end
21
+ ```
22
+
23
+ Note that both the full hex code and the abbreviated versions are supported, i.e. `hex_ff0` produces the same output as `hex_ffff00`:
24
+
25
+ ```rspec:ansi
26
+ subject { paintbrush { hex_ff0 'yellow' } }
27
+
28
+ it { is_expected.to eql(paintbrush { hex_ffff00 'yellow' }) }
29
+ ```
@@ -0,0 +1,29 @@
1
+ # Dynamic Usage
2
+
3
+ You may have situations where the color you wish to use depends on some state that is unknown until your application is running, and may change between each invocation.
4
+
5
+ Since _Paintbrush_ colors are regular methods, you can call `public_send` within the block passed to `paintbrush`.
6
+
7
+
8
+ ```rspec:ansi
9
+ subject do
10
+ paintbrush { blue "The action #{public_send outcome_color, outcome} this time!" }
11
+ end
12
+
13
+ let(:outcome_color) { :green }
14
+ let(:outcome) { 'succeeded' }
15
+
16
+ it { is_expected.to include "\e[32msucceeded\e[0m" }
17
+
18
+ ```
19
+
20
+ ```rspec:ansi
21
+ subject do
22
+ paintbrush { blue "The action #{public_send outcome_color, outcome} this time!" }
23
+ end
24
+
25
+ let(:outcome_color) { :red }
26
+ let(:outcome) { 'failed' }
27
+
28
+ it { is_expected.to include "\e[31mfailed\e[0m" }
29
+ ```
@@ -0,0 +1,15 @@
1
+ # Usage Without `include Paintbrush`
2
+
3
+ _Paintbrush_ is designed to minimize namespace pollution when used with `include`.
4
+
5
+ Only one instance method (`#paintbrush`) is defined on the module, so only one method will be reached by your object's method resolver.
6
+
7
+ _Paintbrush_ also uses a separate namespace for its internals, `PaintbrushSupport`, to minimize adding unwanted constants into an instance's namespace when using `include Paintbrush`.
8
+
9
+ You may still prefer to not include _Paintbrush_ into your namespace. Simply call `Paintbrush.paintbrush` instead.
10
+
11
+ ```rspec:ansi
12
+ subject { Paintbrush.paintbrush { red "I prefer not to #{cyan "include"} Paintbrush" } }
13
+
14
+ it { is_expected.to include "I prefer not" }
15
+ ```
@@ -0,0 +1,20 @@
1
+ # Examples
2
+
3
+ See the individual example pages for usage patterns. Examples are provided to cover basic usage as well as more complex nested colors with multiple color variants. Remember to `include Paintbrush` anywhere you wish to use it.
4
+
5
+ Experiment with _Paintbrush_ from a terminal to get a feel for it:
6
+
7
+ ```irb
8
+ irb(main):001:0> require 'paintbrush'
9
+ => true
10
+ irb(main):002:0> include Paintbrush
11
+ => Object
12
+ irb(main):003:0> puts(paintbrush { green "hello #{cyan 'new'} #{blue 'Paintbrush'} user" })
13
+ hello new Paintbrush user
14
+ ```
15
+
16
+ ```rspec:ansi
17
+ subject { paintbrush { green "hello #{cyan 'new'} #{blue 'Paintbrush'} user" } }
18
+
19
+ it { is_expected.to include 'Paintbrush' }
20
+ ```
@@ -0,0 +1,63 @@
1
+ # Configuration
2
+
3
+ _Paintbrush_ provides one simple configuration option, allowing you to enable or disable colorization either globally, per-invocation, or within a block.
4
+
5
+ Note that global configuration overrides block configuration, and block configuration overrides per-invocation configuration. i.e. any method-level configurations have no impact if a global configuration is set.
6
+
7
+ ## Global Configuration
8
+
9
+ Disable colorization globally by adding the following code somewhere near the beginning of your application's start-up process (e.g. in _Rails_, an initializer is a good place to put this):
10
+
11
+ ```ruby
12
+ # config/initializers/paintbrush.rb
13
+
14
+ Paintbrush.configure do |config|
15
+ config.colorize = false if Rails.env.production?
16
+ end
17
+ ```
18
+
19
+ All calls to `#paintbrush` will now return a regular, uncolorized string.
20
+
21
+ ## Block Configuration
22
+
23
+ _Paintbrush_ can be configured for the duration of a block by calling `Paintbrush.with_configuration`. e.g. you may want to disable colorization in your tests to make testing output a bit easier. If you're using _RSpec_ you can add the following to `spec/spec_helper.rb`:
24
+
25
+ ```ruby
26
+ # spec/spec_helper.rb
27
+
28
+ RSpec.configure do |config|
29
+ config.around { |example| Paintbrush.with_configuration(colorize: false) { example.call } }
30
+ end
31
+ ```
32
+
33
+ ```rspec:ansi
34
+ subject do
35
+ Paintbrush.with_configuration(colorize: false) { paintbrush { red 'An uncolorized string' } }
36
+ end
37
+
38
+ it { is_expected.to eql 'An uncolorized string' }
39
+ ```
40
+
41
+ ## Method Invocation Configuration
42
+
43
+ Pass `colorize: false` to `paintbrush` directly to disable colorization for a single call. This is especially useful when generating a message in two or more contexts. For example, you may want to render the same message to a development log as well as returning that message in a web response, one with colorization and one without:
44
+
45
+ ```rspec:ansi
46
+ def my_message(colorize: true)
47
+ paintbrush(colorize: colorize) { cyan "A log message with colors in some contexts." }
48
+ end
49
+
50
+ subject { my_message(colorize: true) }
51
+
52
+ it { is_expected.to include "\e[36m" }
53
+ ```
54
+
55
+ ```rspec:ansi
56
+ def my_message(colorize: true)
57
+ paintbrush(colorize: colorize) { cyan "A log message with colors in some contexts." }
58
+ end
59
+
60
+ subject { my_message(colorize: false) }
61
+
62
+ it { is_expected.not_to include "\e[36m" }
63
+ ```
@@ -0,0 +1,83 @@
1
+ # How It Works
2
+
3
+ _Paintbrush_ can be broken down into four components:
4
+
5
+ 1. Context manipulation.
6
+ 1. String encoding.
7
+ 1. Color code tree construction.
8
+ 1. Re-encoding into a colorized string.
9
+
10
+ ## Context Manipulation
11
+
12
+ _Paintbrush_ cares about namespace pollution. It avoids adding methods and constants into a namespace where possible, and it does not overload or extend any other objects. _Paintbrush's_ methods are only available within the block passed to the `#paintbrush` method.
13
+
14
+ To achieve this, _Paintbrush_ duplicates the binding of the current block (i.e. the namespace that invoked `#paintbrush`), injects a module `PaintbrushSupport::Colors` (which provides the color methods like `#cyan`) into the duplicated context's `self`, and also adds a `@__stack` instance variable which is unique to each invocation of `#paintbrush`.
15
+
16
+ Each call to `#green`, `#blue`, etc. adds a new `PaintbrushSupport::ColorElement` to the stack, which stores the name of the invoked color method, the string it received, and its index in the current stack.
17
+
18
+ The `ColorElement` object is then returned so _Ruby_ can interpolate it, calling `ColorElement#to_s` which returns an encoded string.
19
+
20
+ ## String Encoding
21
+
22
+ Each encoded string includes the following:
23
+
24
+ * An escape code indicating the beginning of the string.
25
+ * The index of the item in the current stack.
26
+ * The original string received to the color method.
27
+ * An escape code indicating the end of the string.
28
+
29
+ The index is encoded to both the start end end boundaries of the substring, which allows nested colorized strings. The structure is similar to open and close tags in _XML_, with each tag having a unique identifier attribute.
30
+
31
+ The raw encoded string looks like this (slightly formatted to allow text wrapping):
32
+
33
+ ```rspec
34
+ subject do
35
+ PaintbrushSupport::ColorizedString.new(colorize: true) { red "red #{green "green #{blue "blue"}"}" }
36
+ .send(:escaped_output)
37
+ .gsub("CLOSE", "CLOSE ")
38
+ end
39
+
40
+ it { is_expected.to include "green" }
41
+ ```
42
+ This encoded string could be represented in _XML_, for example:
43
+
44
+ ```xml
45
+ <color code="red" id="2">
46
+ red
47
+ <color code="green" id="1">
48
+ green
49
+ <color code="blue" id="0">
50
+ blue
51
+ </color>
52
+ </color>
53
+ </color>
54
+ ```
55
+ However, since the leaf nodes are generated first, and each leaf node does not know where it will appear in the final string, the tree data is built back-to-front and the tree needs to be constructed from the encoded string. Leaves can't attach themselves to parents that don't exist yet, but the resulting string contains information for each node's start/end points and its index in the stack.
56
+
57
+ ## Color Code Tree Construction
58
+
59
+ Once the encoded string has been generated, a tree structure is built by identifying the start and end points of each substring, finding the largest non-overlapping ranges as 1st-generation children, and then repeating the same algorithm using each parent's boundaries to identify direct descendants, until no direct descendants exist (i.e. we have found a leaf node).
60
+
61
+ ## Re-encoding into a colorized string
62
+
63
+ Once the final string has been decoded into a tree structure, each leaf node can inspect its parent to identify which color code should be restored. e.g. if a leaf node has color "yellow" and its parent has color "green", the resulting substring will start with the escape code for yellow, then the string's original value, then the escape code for green.
64
+
65
+ This process is repeated recursively back up the tree until the root is found, at which point the color is reset to the terminal's default.
66
+
67
+ ## Summary
68
+
69
+ The constraints of using string interpolation to create nested colorized strings made developing _Paintbrush_ quite an interesting challenge. Each invocation of a color method receives only the string passed directly to it, and the return value of each method must be an object that _Ruby_ can interpolate into another string, removing the option to pass around objects with complex state and forcing everything to be encoded into the string.
70
+
71
+ The strings received to each call can be stored elsewhere, but the final string structure must define all start and end points of each colorization so that the stack can match each substring to a color code. As each string is received, it does not know where in the final string it will appear.
72
+
73
+ The result is what appears to be a robust model with unlimited nesting. Please [create an issue](https://github.com/bobf/paintbrush/issues) if you are able to break it.
74
+
75
+ ```rspec:ansi
76
+ subject do
77
+ paintbrush do
78
+ "nesting with #{blue 'foo'} #{green "bar #{cyan "baz"} with #{cyan 'qux'} and quux"} and #{red "corge"}"
79
+ end
80
+ end
81
+
82
+ it { is_expected.to include 'baz' }
83
+ ```
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec::Documentation.configure do |config|
4
+ config.context do
5
+ require 'paintbrush'
6
+ include Paintbrush
7
+ end
8
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paintbrush
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Farrell
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-26 00:00:00.000000000 Z
11
+ date: 2023-06-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Provides a set of encapsulated methods for nested colorization of strings.
14
14
  email:
@@ -28,19 +28,32 @@ files:
28
28
  - Rakefile
29
29
  - doc/example.png
30
30
  - lib/paintbrush.rb
31
- - lib/paintbrush/bounded_color_element.rb
32
- - lib/paintbrush/color_element.rb
33
- - lib/paintbrush/colorized_string.rb
34
- - lib/paintbrush/colors.rb
35
- - lib/paintbrush/element_tree.rb
36
- - lib/paintbrush/escapes.rb
37
- - lib/paintbrush/version.rb
31
+ - lib/paintbrush_support/bounded_color_element.rb
32
+ - lib/paintbrush_support/color_element.rb
33
+ - lib/paintbrush_support/colorized_string.rb
34
+ - lib/paintbrush_support/colors.rb
35
+ - lib/paintbrush_support/configuration.rb
36
+ - lib/paintbrush_support/element_tree.rb
37
+ - lib/paintbrush_support/escapes.rb
38
+ - lib/paintbrush_support/hex_color_code.rb
39
+ - lib/paintbrush_support/version.rb
38
40
  - paintbrush.gemspec
39
- - rspec-documentation/bundle/introduction.html
40
- - rspec-documentation/pages/Introduction.md
41
+ - rspec-documentation/pages/000-Introduction.md
42
+ - rspec-documentation/pages/010-Colors.md
43
+ - rspec-documentation/pages/020-Examples.md
44
+ - rspec-documentation/pages/020-Examples/010-Basic Usage.md
45
+ - rspec-documentation/pages/020-Examples/020-Bright Colors.md
46
+ - rspec-documentation/pages/020-Examples/030-Nested Colors.md
47
+ - rspec-documentation/pages/020-Examples/040-RGB Colors.md
48
+ - rspec-documentation/pages/020-Examples/050-Dynamic Usage.md
49
+ - rspec-documentation/pages/020-Examples/060-Without Including.md
50
+ - rspec-documentation/pages/030-Configuration.md
51
+ - rspec-documentation/pages/040-How It Works.md
52
+ - rspec-documentation/spec_helper.rb
41
53
  - sig/paintbrush.rbs
42
54
  homepage: https://github.com/bobf/paintbrush
43
- licenses: []
55
+ licenses:
56
+ - MIT
44
57
  metadata:
45
58
  homepage_uri: https://github.com/bobf/paintbrush
46
59
  source_code_uri: https://github.com/bobf/paintbrush
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paintbrush
4
- # Provides methods that are temporarily injected into block context. Each method returns an
5
- # escaped string including the current stack size with start and end escape codes, allowing the
6
- # string to be reconstituted afterwards with nested strings restoring the previous color once
7
- # they have terminated.
8
- module Colors
9
- COLOR_CODES = {
10
- black: '30',
11
- red: '31',
12
- green: '32',
13
- yellow: '33',
14
- blue: '34',
15
- purple: '35',
16
- cyan: '36',
17
- white: '37',
18
- default: '39'
19
- }.freeze
20
-
21
- COLOR_CODES.each do |name, code|
22
- define_method name do |string|
23
- ColorElement.new(stack: @__stack, code: code, string: string).to_s
24
- end
25
- end
26
- end
27
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Paintbrush
4
- VERSION = '0.1.1'
5
- end
@@ -1,312 +0,0 @@
1
- <html>
2
- <head>
3
- <link rel="stylesheet"
4
- href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/css/bootstrap.min.css"
5
- integrity="sha512-SbiR/eusphKoMVVXysTKG/7VseWii+Y3FdHrt0EpKgpToZeemhqHeZeLWLhJutz/2ut2Vw1uQEj2MbRF+TVBUA=="
6
- crossorigin="anonymous"
7
- referrerpolicy="no-referrer" />
8
- <style>
9
-
10
- .code {
11
- font-family: monospace;
12
- max-height: 30rem;
13
- overflow-y: auto;
14
- }
15
-
16
- h1.title {
17
- display: inline;
18
- }
19
-
20
- .header .separator {
21
- display: inline-block;
22
- height: 2.5rem;
23
- }
24
-
25
- .version {
26
- color: #bbb;
27
- font-size: 2rem;
28
- }
29
-
30
- .ansi-html {
31
- display: inline-block;
32
- background-color: #2e2e2e;
33
- border: 5px solid #2e2e2e;
34
- border-radius: 10px;
35
- }
36
-
37
- .ansi-html .ansi-color-0 { color: #555555; }
38
- .ansi-html .ansi-color-1 { color: #ff0000; }
39
- .ansi-html .ansi-color-2 { color: #47ff47; }
40
- .ansi-html .ansi-color-3 { color: #e9e947; }
41
- .ansi-html .ansi-color-4 { color: #49a0dd; }
42
- .ansi-html .ansi-color-5 { color: #8d7eeb; }
43
- .ansi-html .ansi-color-6 { color: #2eecff; }
44
- .ansi-html .ansi-color-7 { color: #ffffff; }
45
- .ansi-html .ansi-color-9 { color: #606060; }
46
- .ansi-html .ansi-color-reset { color: #dddddd; }
47
-
48
- .highlight table td { padding: 5px; }
49
- .highlight table pre { margin: 0; }
50
- .highlight, .highlight .w {
51
- color: #24292f;
52
- background-color: #f6f8fa;
53
- }
54
- .highlight .k, .highlight .kd, .highlight .kn, .highlight .kp, .highlight .kr, .highlight .kt, .highlight .kv {
55
- color: #cf222e;
56
- }
57
- .highlight .gr {
58
- color: #f6f8fa;
59
- }
60
- .highlight .gd {
61
- color: #82071e;
62
- background-color: #ffebe9;
63
- }
64
- .highlight .nb {
65
- color: #953800;
66
- }
67
- .highlight .nc {
68
- color: #953800;
69
- }
70
- .highlight .no {
71
- color: #953800;
72
- }
73
- .highlight .nn {
74
- color: #953800;
75
- }
76
- .highlight .sr {
77
- color: #116329;
78
- }
79
- .highlight .na {
80
- color: #116329;
81
- }
82
- .highlight .nt {
83
- color: #116329;
84
- }
85
- .highlight .gi {
86
- color: #116329;
87
- background-color: #dafbe1;
88
- }
89
- .highlight .kc {
90
- color: #0550ae;
91
- }
92
- .highlight .l, .highlight .ld, .highlight .m, .highlight .mb, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .il, .highlight .mo, .highlight .mx {
93
- color: #0550ae;
94
- }
95
- .highlight .sb {
96
- color: #0550ae;
97
- }
98
- .highlight .bp {
99
- color: #0550ae;
100
- }
101
- .highlight .ne {
102
- color: #0550ae;
103
- }
104
- .highlight .nl {
105
- color: #0550ae;
106
- }
107
- .highlight .py {
108
- color: #0550ae;
109
- }
110
- .highlight .nv, .highlight .vc, .highlight .vg, .highlight .vi, .highlight .vm {
111
- color: #0550ae;
112
- }
113
- .highlight .o, .highlight .ow {
114
- color: #0550ae;
115
- }
116
- .highlight .gh {
117
- color: #0550ae;
118
- font-weight: bold;
119
- }
120
- .highlight .gu {
121
- color: #0550ae;
122
- font-weight: bold;
123
- }
124
- .highlight .s, .highlight .sa, .highlight .sc, .highlight .dl, .highlight .sd, .highlight .s2, .highlight .se, .highlight .sh, .highlight .sx, .highlight .s1, .highlight .ss {
125
- color: #0a3069;
126
- }
127
- .highlight .nd {
128
- color: #8250df;
129
- }
130
- .highlight .nf, .highlight .fm {
131
- color: #8250df;
132
- }
133
- .highlight .err {
134
- color: #f6f8fa;
135
- background-color: #82071e;
136
- }
137
- .highlight .c, .highlight .ch, .highlight .cd, .highlight .cm, .highlight .cp, .highlight .cpf, .highlight .c1, .highlight .cs {
138
- color: #6e7781;
139
- }
140
- .highlight .gl {
141
- color: #6e7781;
142
- }
143
- .highlight .gt {
144
- color: #6e7781;
145
- }
146
- .highlight .ni {
147
- color: #24292f;
148
- }
149
- .highlight .si {
150
- color: #24292f;
151
- }
152
- .highlight .ge {
153
- color: #24292f;
154
- font-style: italic;
155
- }
156
- .highlight .gs {
157
- color: #24292f;
158
- font-weight: bold;
159
- }
160
-
161
- </style>
162
- </head>
163
-
164
- <body>
165
- <div class="container m-3 p-3">
166
- <div class="header p-3 m-3 w-90 text-end">
167
- <h1 class="title">paintbrush</h1>
168
- <span class="separator border-end pb-2 ms-2 me-2"></span>
169
- <span class="version">0.1.0</span>
170
- </div>
171
-
172
- <hr/>
173
-
174
-
175
- <div class="row">
176
- <div class="col-auto page-tree fs-6 mt-1 ms-3 me-3">
177
-
178
- <li>
179
-
180
- <a href='/home/bob/dev/paintbrush/rspec-documentation/bundle/introduction.html'>Introduction</a>
181
-
182
- </li>
183
-
184
- <ul>
185
-
186
- </ul>
187
-
188
- </div>
189
-
190
- <div class="col">
191
- <h1 id="paintbrush">Paintbrush</h1>
192
-
193
- <p>Simple and concise string colorization for <em>Ruby</em> without overloading <code>String</code> methods or requiring verbose class/method invocation.</p>
194
-
195
- <p><em>Paintbrush</em> has zero dependencies and does not pollute any namespaces or objects outside of the <code>#paintbrush</code> method wherever you include the <code>Paintbrush</code> module.</p>
196
-
197
- <p>Nesting is supported, allowing you to use multiple colors within the same string. The previous color is automatically restored.</p>
198
-
199
- <div location="9"><button type="button" class="btn btn-primary float-end" data-bs-toggle="modal" data-bs-target="#modal-15cae418-087d-45aa-b936-ea2c34033c01-side-by-side"> View Side-by-side </button> <div class="modal fade" id="modal-15cae418-087d-45aa-b936-ea2c34033c01-side-by-side" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
200
- <div class="modal-dialog modal-xl">
201
- <div class="modal-content">
202
- <div class="modal-header">
203
- <h5 class="modal-title" id="modal-15cae418-087d-45aa-b936-ea2c34033c01-side-by-side-label">Side-by-side view</h5>
204
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
205
- </div>
206
- <div class="modal-body">
207
- <div class="container p-3">
208
- <div class="row border">
209
- <div class="col border">
210
- <div class="p-3 mb-5 code highlight">
211
- <span class="nb">require</span> <span class="s1">'paintbrush'</span><br /><br /><span class="kp">extend</span> <span class="no">Paintbrush</span><br /><br /><span class="n">output</span> <span class="o">=</span> <span class="n">paintbrush</span> <span class="p">{</span> <span class="n">purple</span> <span class="s2">"You used </span><span class="si">#{</span><span class="n">green</span> <span class="s1">'four'</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">blue</span> <span class="s2">"(</span><span class="si">#{</span><span class="n">cyan</span> <span class="s1">'4'</span><span class="si">}</span><span class="s2">)"</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">yellow</span> <span class="s1">'colors'</span><span class="si">}</span><span class="s2"> today!"</span> <span class="p">}</span><br /><span class="n">it_documents</span> <span class="n">output</span> <span class="k">do</span><br />  <span class="n">expect</span><span class="p">(</span><span class="n">output</span><span class="p">).</span><span class="nf">to</span> <span class="n">eql</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">[35mYou used </span><span class="se">\e</span><span class="s2">[32mfour</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[35m "</span> <span class="p">\</span><br />                        <span class="s2">"</span><span class="se">\e</span><span class="s2">[34m(</span><span class="se">\e</span><span class="s2">[36m4</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[34m)</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[35m "</span> <span class="p">\</span><br />                        <span class="s2">"</span><span class="se">\e</span><span class="s2">[33mcolors</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[35m today!</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[0m"</span><br /><span class="k">end</span><br />
212
- </div>
213
- </div>
214
- <div class="col border">
215
- <div class="p-3 mb-5 code highlight">
216
- <div class="ansi-html border m-1 p-4">
217
- <span></span><span class="ansi-color-5">You used </span><span class="ansi-color-2">four</span><span class="ansi-color-reset"></span><span class="ansi-color-5"> </span><span class="ansi-color-4">(</span><span class="ansi-color-6">4</span><span class="ansi-color-reset"></span><span class="ansi-color-4">)</span><span class="ansi-color-reset"></span><span class="ansi-color-5"> </span><span class="ansi-color-3">colors</span><span class="ansi-color-reset"></span><span class="ansi-color-5"> today!</span><span class="ansi-color-reset"></span><span class="ansi-color-reset"></span>
218
- </div>
219
- </div>
220
- </div>
221
- </div>
222
- </div>
223
- </div>
224
- <div class="modal-footer">
225
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
226
- </div>
227
- </div>
228
- </div>
229
- </div>
230
- <ul class="nav nav-tabs" id="html-tabs-15cae418-087d-45aa-b936-ea2c34033c01" role="tablist">
231
- <li class="nav-item" role="presentation"><button class="nav-link active" id="code-source-15cae418-087d-45aa-b936-ea2c34033c01-tab" data-bs-toggle="tab" data-bs-target="#code-source-15cae418-087d-45aa-b936-ea2c34033c01" type="button" role="tab" aria-controls="code-source" aria-selected="true">Code</button></li>
232
- <li class="nav-item" role="presentation"><button class="nav-link" id="rendered-15cae418-087d-45aa-b936-ea2c34033c01-tab" data-bs-toggle="tab" data-bs-target="#rendered-15cae418-087d-45aa-b936-ea2c34033c01" type="button" role="tab" aria-controls="rendered" aria-selected="false">Output</button></li>
233
- </ul>
234
- <div class="tab-content" id="html-tab-content-15cae418-087d-45aa-b936-ea2c34033c01">
235
- <div class="tab-pane fade show active" id="code-source-15cae418-087d-45aa-b936-ea2c34033c01" role="tabpanel" aria-labelledby="code-source-15cae418-087d-45aa-b936-ea2c34033c01-tab">
236
- <div class="p-3 mb-5 code highlight">
237
- <span class="nb">require</span> <span class="s1">'paintbrush'</span><br /><br /><span class="kp">extend</span> <span class="no">Paintbrush</span><br /><br /><span class="n">output</span> <span class="o">=</span> <span class="n">paintbrush</span> <span class="p">{</span> <span class="n">purple</span> <span class="s2">"You used </span><span class="si">#{</span><span class="n">green</span> <span class="s1">'four'</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">blue</span> <span class="s2">"(</span><span class="si">#{</span><span class="n">cyan</span> <span class="s1">'4'</span><span class="si">}</span><span class="s2">)"</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">yellow</span> <span class="s1">'colors'</span><span class="si">}</span><span class="s2"> today!"</span> <span class="p">}</span><br /><span class="n">it_documents</span> <span class="n">output</span> <span class="k">do</span><br />  <span class="n">expect</span><span class="p">(</span><span class="n">output</span><span class="p">).</span><span class="nf">to</span> <span class="n">eql</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">[35mYou used </span><span class="se">\e</span><span class="s2">[32mfour</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[35m "</span> <span class="p">\</span><br />                        <span class="s2">"</span><span class="se">\e</span><span class="s2">[34m(</span><span class="se">\e</span><span class="s2">[36m4</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[34m)</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[35m "</span> <span class="p">\</span><br />                        <span class="s2">"</span><span class="se">\e</span><span class="s2">[33mcolors</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[35m today!</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[0m"</span><br /><span class="k">end</span><br />
238
- </div>
239
- </div>
240
- <div class="tab-pane fade" id="rendered-15cae418-087d-45aa-b936-ea2c34033c01" role="tabpanel" aria-labelledby="rendered-15cae418-087d-45aa-b936-ea2c34033c01-tab">
241
- <div class="p-3 mb-5 code highlight">
242
- <div class="ansi-html border m-1 p-4">
243
- <span></span><span class="ansi-color-5">You used </span><span class="ansi-color-2">four</span><span class="ansi-color-reset"></span><span class="ansi-color-5"> </span><span class="ansi-color-4">(</span><span class="ansi-color-6">4</span><span class="ansi-color-reset"></span><span class="ansi-color-4">)</span><span class="ansi-color-reset"></span><span class="ansi-color-5"> </span><span class="ansi-color-3">colors</span><span class="ansi-color-reset"></span><span class="ansi-color-5"> today!</span><span class="ansi-color-reset"></span><span class="ansi-color-reset"></span>
244
- </div>
245
- </div>
246
- </div>
247
- </div>
248
- </div>
249
-
250
- <div location="22"><button type="button" class="btn btn-primary float-end" data-bs-toggle="modal" data-bs-target="#modal-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-side-by-side"> View Side-by-side </button> <div class="modal fade" id="modal-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-side-by-side" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
251
- <div class="modal-dialog modal-xl">
252
- <div class="modal-content">
253
- <div class="modal-header">
254
- <h5 class="modal-title" id="modal-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-side-by-side-label">Side-by-side view</h5>
255
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
256
- </div>
257
- <div class="modal-body">
258
- <div class="container p-3">
259
- <div class="row border">
260
- <div class="col border">
261
- <div class="p-3 mb-5 code highlight">
262
- <span class="nb">require</span> <span class="s1">'paintbrush'</span><br /><br /><span class="kp">extend</span> <span class="no">Paintbrush</span><br /><br /><span class="n">output</span> <span class="o">=</span> <span class="n">paintbrush</span> <span class="k">do</span><br />  <span class="s2">"</span><span class="si">#{</span><span class="n">blue</span> <span class="s1">'foo'</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">green</span> <span class="s2">"bar </span><span class="si">#{</span><span class="n">cyan</span> <span class="sx">%w[foo bar baz]</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span><span class="si">}</span><span class="s2"> with </span><span class="si">#{</span><span class="n">cyan</span> <span class="s1">'qux'</span><span class="si">}</span><span class="s2"> and quux"</span><span class="si">}</span><span class="s2"> and corge"</span><br /><span class="k">end</span><br /><br /><span class="n">it_documents</span> <span class="n">output</span> <span class="k">do</span><br />  <span class="n">expect</span><span class="p">(</span><span class="n">output</span><span class="p">).</span><span class="nf">to</span> <span class="n">eql</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">[34mfoo</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[0m </span><span class="se">\e</span><span class="s2">[32mbar </span><span class="se">\e</span><span class="s2">[36mfoo, bar, baz"</span> <span class="p">\</span><br />                        <span class="s2">"</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[32m with </span><span class="se">\e</span><span class="s2">[36mqux</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[32m "</span><span class="p">\</span><br />                        <span class="s2">"and quux</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[0m and corge"</span><br /><span class="k">end</span><br />
263
- </div>
264
- </div>
265
- <div class="col border">
266
- <div class="p-3 mb-5 code highlight">
267
- <div class="ansi-html border m-1 p-4">
268
- <span></span><span class="ansi-color-4">foo</span><span class="ansi-color-reset"></span><span class="ansi-color-reset"> </span><span class="ansi-color-2">bar </span><span class="ansi-color-6">foo, bar, baz</span><span class="ansi-color-reset"></span><span class="ansi-color-2"> with </span><span class="ansi-color-6">qux</span><span class="ansi-color-reset"></span><span class="ansi-color-2"> and quux</span><span class="ansi-color-reset"></span><span class="ansi-color-reset"> and corge</span>
269
- </div>
270
- </div>
271
- </div>
272
- </div>
273
- </div>
274
- </div>
275
- <div class="modal-footer">
276
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
277
- </div>
278
- </div>
279
- </div>
280
- </div>
281
- <ul class="nav nav-tabs" id="html-tabs-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0" role="tablist">
282
- <li class="nav-item" role="presentation"><button class="nav-link active" id="code-source-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-tab" data-bs-toggle="tab" data-bs-target="#code-source-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0" type="button" role="tab" aria-controls="code-source" aria-selected="true">Code</button></li>
283
- <li class="nav-item" role="presentation"><button class="nav-link" id="rendered-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-tab" data-bs-toggle="tab" data-bs-target="#rendered-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0" type="button" role="tab" aria-controls="rendered" aria-selected="false">Output</button></li>
284
- </ul>
285
- <div class="tab-content" id="html-tab-content-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0">
286
- <div class="tab-pane fade show active" id="code-source-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0" role="tabpanel" aria-labelledby="code-source-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-tab">
287
- <div class="p-3 mb-5 code highlight">
288
- <span class="nb">require</span> <span class="s1">'paintbrush'</span><br /><br /><span class="kp">extend</span> <span class="no">Paintbrush</span><br /><br /><span class="n">output</span> <span class="o">=</span> <span class="n">paintbrush</span> <span class="k">do</span><br />  <span class="s2">"</span><span class="si">#{</span><span class="n">blue</span> <span class="s1">'foo'</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">green</span> <span class="s2">"bar </span><span class="si">#{</span><span class="n">cyan</span> <span class="sx">%w[foo bar baz]</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">', '</span><span class="p">)</span><span class="si">}</span><span class="s2"> with </span><span class="si">#{</span><span class="n">cyan</span> <span class="s1">'qux'</span><span class="si">}</span><span class="s2"> and quux"</span><span class="si">}</span><span class="s2"> and corge"</span><br /><span class="k">end</span><br /><br /><span class="n">it_documents</span> <span class="n">output</span> <span class="k">do</span><br />  <span class="n">expect</span><span class="p">(</span><span class="n">output</span><span class="p">).</span><span class="nf">to</span> <span class="n">eql</span> <span class="s2">"</span><span class="se">\e</span><span class="s2">[34mfoo</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[0m </span><span class="se">\e</span><span class="s2">[32mbar </span><span class="se">\e</span><span class="s2">[36mfoo, bar, baz"</span> <span class="p">\</span><br />                        <span class="s2">"</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[32m with </span><span class="se">\e</span><span class="s2">[36mqux</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[32m "</span><span class="p">\</span><br />                        <span class="s2">"and quux</span><span class="se">\e</span><span class="s2">[0m</span><span class="se">\e</span><span class="s2">[0m and corge"</span><br /><span class="k">end</span><br />
289
- </div>
290
- </div>
291
- <div class="tab-pane fade" id="rendered-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0" role="tabpanel" aria-labelledby="rendered-c3e2bbb8-9a13-4400-93a1-0e6c9847cba0-tab">
292
- <div class="p-3 mb-5 code highlight">
293
- <div class="ansi-html border m-1 p-4">
294
- <span></span><span class="ansi-color-4">foo</span><span class="ansi-color-reset"></span><span class="ansi-color-reset"> </span><span class="ansi-color-2">bar </span><span class="ansi-color-6">foo, bar, baz</span><span class="ansi-color-reset"></span><span class="ansi-color-2"> with </span><span class="ansi-color-6">qux</span><span class="ansi-color-reset"></span><span class="ansi-color-2"> and quux</span><span class="ansi-color-reset"></span><span class="ansi-color-reset"> and corge</span>
295
- </div>
296
- </div>
297
- </div>
298
- </div>
299
- </div>
300
-
301
- </div>
302
- </div>
303
- <hr/>
304
-
305
- </div>
306
-
307
- <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.bundle.min.js"
308
- integrity="sha512-i9cEfJwUwViEPFKdC1enz4ZRGBj8YQo6QByFTF92YXHi7waCqyexvRD75S5NVTsSiTv7rKWqG9Y5eFxmRsOn0A=="
309
- crossorigin="anonymous"
310
- referrerpolicy="no-referrer"></script>
311
- </body>
312
- </html>
@@ -1,36 +0,0 @@
1
- # Paintbrush
2
-
3
- Simple and concise string colorization for _Ruby_ without overloading `String` methods or requiring verbose class/method invocation.
4
-
5
- _Paintbrush_ has zero dependencies and does not pollute any namespaces or objects outside of the `#paintbrush` method wherever you include the `Paintbrush` module.
6
-
7
- Nesting is supported, allowing you to use multiple colors within the same string. The previous color is automatically restored.
8
-
9
- ```rspec:ansi
10
- require 'paintbrush'
11
-
12
- extend Paintbrush
13
-
14
- output = paintbrush { purple "You used #{green 'four'} #{blue "(#{cyan '4'})"} #{yellow 'colors'} today!" }
15
- it_documents output do
16
- expect(output).to eql "\e[35mYou used \e[32mfour\e[0m\e[35m " \
17
- "\e[34m(\e[36m4\e[0m\e[34m)\e[0m\e[35m " \
18
- "\e[33mcolors\e[0m\e[35m today!\e[0m\e[0m"
19
- end
20
- ```
21
-
22
- ```rspec:ansi
23
- require 'paintbrush'
24
-
25
- extend Paintbrush
26
-
27
- output = paintbrush do
28
- "#{blue 'foo'} #{green "bar #{cyan %w[foo bar baz].join(', ')} with #{cyan 'qux'} and quux"} and corge"
29
- end
30
-
31
- it_documents output do
32
- expect(output).to eql "\e[34mfoo\e[0m\e[0m \e[32mbar \e[36mfoo, bar, baz" \
33
- "\e[0m\e[32m with \e[36mqux\e[0m\e[32m "\
34
- "and quux\e[0m\e[0m and corge"
35
- end
36
- ```