pastel 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+
3
+ module Pastel
4
+ # Responsible for parsing color symbols out of text with color escapes
5
+ #
6
+ # Used internally by {Color}.
7
+ #
8
+ # @api private
9
+ class ColorParser
10
+ include ANSI
11
+
12
+ ESC = "\x1b".freeze
13
+ CSI = "\[".freeze
14
+
15
+ # Parse color escape sequences into a list of hashes
16
+ # corresponding to the color attributes being set by these
17
+ # sequences
18
+ #
19
+ # @example
20
+ # parse("\e[32mfoo\e[0m") # => [{colors: [:green], text: 'foo'}
21
+ #
22
+ # @param [String] text
23
+ # the text to parse for presence of color ansi codes
24
+ #
25
+ # @return [Array[Hash[Symbol,String]]]
26
+ #
27
+ # @api public
28
+ def self.parse(text)
29
+ scanner = StringScanner.new(text)
30
+ state = {}
31
+ result = []
32
+ ansi_stack = []
33
+ text_chunk = ''
34
+
35
+ until scanner.eos?
36
+ char = scanner.getch
37
+ # match control
38
+ if char == ESC && (delim = scanner.getch) == CSI
39
+ if scanner.scan(/^0m/)
40
+ unpack_ansi(ansi_stack) { |attr, name| state[attr] = name }
41
+ ansi_stack = []
42
+ elsif scanner.scan(/^([1-9;:]+)m/)
43
+ # ansi codes separated by text
44
+ if !text_chunk.empty? && !ansi_stack.empty?
45
+ unpack_ansi(ansi_stack) { |attr, name| state[attr] = name }
46
+ ansi_stack = []
47
+ end
48
+ scanner[1].split(/:|;/).each do |code|
49
+ ansi_stack << code
50
+ end
51
+ end
52
+
53
+ if !text_chunk.empty?
54
+ state[:text] = text_chunk
55
+ result.push(state)
56
+ state = {}
57
+ text_chunk = ''
58
+ end
59
+ elsif char == ESC # broken escape
60
+ text_chunk << char + delim.to_s
61
+ else
62
+ text_chunk << char
63
+ end
64
+ end
65
+
66
+ if !text_chunk.empty?
67
+ state[:text] = text_chunk
68
+ end
69
+ if !ansi_stack.empty?
70
+ unpack_ansi(ansi_stack) { |attr, name| state[attr] = name}
71
+ end
72
+ if state.values.any? { |val| !val.empty? }
73
+ result.push(state)
74
+ end
75
+ result
76
+ end
77
+
78
+ # Remove from current stack all ansi codes
79
+ #
80
+ # @param [Array[Integer]] ansi_stack
81
+ # the stack with all the ansi codes
82
+ #
83
+ # @yield [Symbol, Symbol] attr, name
84
+ #
85
+ # @api private
86
+ def self.unpack_ansi(ansi_stack)
87
+ ansi_stack.each do |ansi|
88
+ name = ansi_for(ansi)
89
+ attr = attribute_for(ansi)
90
+ yield attr, name
91
+ end
92
+ end
93
+
94
+ # Decide attribute name for ansi
95
+ #
96
+ # @param [Integer] ansi
97
+ # the ansi escape code
98
+ #
99
+ # @return [Symbol]
100
+ #
101
+ # @api private
102
+ def self.attribute_for(ansi)
103
+ if ANSI.foreground?(ansi)
104
+ :foreground
105
+ elsif ANSI.background?(ansi)
106
+ :background
107
+ elsif ANSI.style?(ansi)
108
+ :style
109
+ end
110
+ end
111
+
112
+ # @api private
113
+ def self.ansi_for(ansi)
114
+ ATTRIBUTES.key(ansi.to_i)
115
+ end
116
+ end # Parser
117
+ end # Pastel
@@ -7,6 +7,8 @@ module Pastel
7
7
  #
8
8
  # @api private
9
9
  class ColorResolver
10
+ # The color instance
11
+ # @api public
10
12
  attr_reader :color
11
13
 
12
14
  # Initialize ColorResolver
@@ -14,7 +16,7 @@ module Pastel
14
16
  # @param [Color] color
15
17
  #
16
18
  # @api private
17
- def initialize(color = Color.new)
19
+ def initialize(color)
18
20
  @color = color
19
21
  end
20
22
 
@@ -10,7 +10,10 @@ module Pastel
10
10
  include Equatable
11
11
 
12
12
  def_delegators '@resolver.color', :valid?, :styles, :strip, :decorate,
13
- :enabled?, :colored?, :alias_color
13
+ :enabled?, :colored?, :alias_color, :lookup
14
+
15
+ def_delegators ColorParser, :parse
16
+ alias_method :undecorate, :parse
14
17
 
15
18
  # Create Delegator
16
19
  #
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Pastel
4
- VERSION = "0.5.3"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -15,11 +15,12 @@ Gem::Specification.new do |spec|
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.test_files = spec.files.grep(%r{^spec/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "equatable", "~> 0.5.0"
22
- spec.add_dependency "tty-screen", "~> 0.4.3"
21
+ spec.add_dependency 'equatable', '~> 0.5.0'
22
+ spec.add_dependency 'tty-color', '~> 0.3.0'
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency 'bundler', '>= 1.5.0', '< 2.0'
25
+ spec.add_development_dependency 'rake'
25
26
  end
@@ -1,8 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe Pastel, '.alias_color' do
3
+ RSpec.describe Pastel, '#alias_color' do
6
4
 
7
5
  subject(:pastel) { described_class.new(enabled: true) }
8
6
 
@@ -1,31 +1,28 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe Pastel::AliasImporter, '.import' do
3
+ RSpec.describe Pastel::AliasImporter, '#import' do
6
4
  let(:color) { spy(:color, alias_color: true) }
7
5
  let(:output) { StringIO.new }
8
6
 
9
- subject(:importer) { described_class.new(color, output) }
10
-
11
7
  it "imports aliases from environment" do
12
8
  color_aliases = "funky=red,base=bright_yellow"
13
- allow(ENV).to receive(:[]).with('PASTEL_COLORS_ALIASES').
14
- and_return(color_aliases)
9
+ env = {'PASTEL_COLORS_ALIASES' => color_aliases}
10
+ importer = described_class.new(color, env)
15
11
 
16
12
  importer.import
17
13
 
18
- expect(color).to have_received(:alias_color).twice
14
+ expect(color).to have_received(:alias_color).with(:funky, :red)
15
+ expect(color).to have_received(:alias_color).with(:base, :bright_yellow)
19
16
  end
20
17
 
21
18
  it "fails to import incorrectly formatted colors" do
22
19
  color_aliases = "funky red,base=bright_yellow"
23
- allow(ENV).to receive(:[]).with('PASTEL_COLORS_ALIASES').
24
- and_return(color_aliases)
20
+ env = {'PASTEL_COLORS_ALIASES' => color_aliases}
21
+ importer = described_class.new(color, env, output)
22
+ output.rewind
25
23
 
26
24
  importer.import
27
25
 
28
- output.rewind
29
26
  expect(output.string).to eq("Bad color mapping `funky red`\n")
30
27
  expect(color).to have_received(:alias_color).with(:base, :bright_yellow)
31
28
  end
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
3
  RSpec.describe Pastel::Color, '.alias_color' do
6
4
 
7
5
  subject(:color) { described_class.new(enabled: true) }
@@ -1,8 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe Pastel::Color, '.code' do
3
+ RSpec.describe Pastel::Color, '#code' do
6
4
  let(:string) { "This is a \e[1m\e[34mbold blue text\e[0m" }
7
5
 
8
6
  subject(:color) { described_class.new(enabled: true) }
@@ -1,8 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe Pastel::Color, '.colored?' do
3
+ RSpec.describe Pastel::Color, '#colored?' do
6
4
  subject(:color) { described_class.new(enabled: true) }
7
5
 
8
6
  it "checks if string has color codes" do
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
3
  RSpec.describe Pastel::Color, '.decorate' do
6
4
  let(:string) { 'string' }
7
5
 
@@ -16,6 +14,10 @@ RSpec.describe Pastel::Color, '.decorate' do
16
14
  expect(color.decorate('')).to eq('')
17
15
  end
18
16
 
17
+ it "doesn't decorate without color" do
18
+ expect(color.decorate(string)).to eq(string)
19
+ end
20
+
19
21
  it 'applies green text to string' do
20
22
  expect(color.decorate(string, :green)).to eq("\e[32m#{string}\e[0m")
21
23
  end
@@ -35,7 +37,7 @@ RSpec.describe Pastel::Color, '.decorate' do
35
37
 
36
38
  it "applies styles to nested text" do
37
39
  decorated = color.decorate(string + color.decorate(string, :red) + string, :green)
38
- expect(decorated).to eq("\e[32m#{string}\e[31m#{string}\e[32m#{string}\e[0m")
40
+ expect(decorated).to eq("\e[32m#{string}\e[31m#{string}\e[0m\e[32m#{string}\e[0m")
39
41
  end
40
42
 
41
43
  it "decorates multiline string as regular by default" do
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
3
  RSpec.describe Pastel::Color, '#==' do
6
4
  it "is true with the same enabled and eachline attributes" do
7
5
  expect(Pastel::Color.new(enabled: false, eachline: "\n")).
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe Pastel::Color, '#lookup' do
4
+ it "looksup colors" do
5
+ color = described_class.new(enabled: true)
6
+ expect(color.lookup(:red, :on_green, :bold)).to eq("\e[31;42;1m")
7
+ end
8
+
9
+ it "caches color lookups" do
10
+ color = described_class.new(enabled: true)
11
+ allow(color).to receive(:code).and_return([31])
12
+ color.lookup(:red, :on_green)
13
+ color.lookup(:red, :on_green)
14
+ color.lookup(:red, :on_green)
15
+ expect(color).to have_received(:code).once
16
+ end
17
+ end
@@ -1,24 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe Pastel::Color, '.new' do
6
- it "is immutable" do
7
- expect(described_class.new).to be_frozen
8
- end
9
-
3
+ RSpec.describe Pastel::Color, '::new' do
10
4
  it "allows to disable coloring" do
11
5
  color = described_class.new(enabled: false)
12
6
 
13
7
  expect(color.enabled?).to eq(false)
14
8
  expect(color.decorate("Unicorn", :red)).to eq("Unicorn")
15
9
  end
16
-
17
- it "invokes screen dependency to check color support" do
18
- allow(TTY::Screen).to receive(:color?).and_return(true)
19
- color = described_class.new
20
-
21
- expect(color.enabled?).to eq(true)
22
- expect(TTY::Screen).to have_received(:color?)
23
- end
24
10
  end
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
3
  RSpec.describe Pastel::Color, '.strip' do
6
4
 
7
5
  subject(:color) { described_class.new(enabled: true) }
@@ -18,7 +16,7 @@ RSpec.describe Pastel::Color, '.strip' do
18
16
 
19
17
  it 'preserves movement characters' do
20
18
  # [176A - move cursor up n lines
21
- expect(color.strip("foo\e[176Abar")).to eq('foobar')
19
+ expect(color.strip("foo\e[176Abar")).to eq("foo\e[176Abar")
22
20
  end
23
21
 
24
22
  it 'strips reset/setfg/setbg/italics/strike/underline sequence' do
@@ -28,7 +26,7 @@ RSpec.describe Pastel::Color, '.strip' do
28
26
 
29
27
  it 'strips octal in encapsulating brackets' do
30
28
  string = "\[\033[01;32m\]u@h \[\033[01;34m\]W $ \[\033[00m\]"
31
- expect(color.strip(string)).to eq('u@h W $ ')
29
+ expect(color.strip(string)).to eq('[]u@h []W $ []')
32
30
  end
33
31
 
34
32
  it 'strips octal codes without brackets' do
@@ -37,7 +35,7 @@ RSpec.describe Pastel::Color, '.strip' do
37
35
  end
38
36
 
39
37
  it 'strips octal with multiple colors' do
40
- string = "\e[3;0;0;tfoo\e[8;50;0t"
38
+ string = "\e[3;0;0;mfoo\e[8;50;0m"
41
39
  expect(color.strip(string)).to eq('foo')
42
40
  end
43
41
 
@@ -1,8 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
- RSpec.describe Pastel::Color do
3
+ RSpec.describe Pastel::Color, '#styles' do
6
4
 
7
5
  subject(:color) { described_class.new(enabled: true) }
8
6
 
@@ -1,7 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
-
5
3
  RSpec.describe Pastel::Color, '.valid?' do
6
4
  it "detects valid colors" do
7
5
  color = described_class.new
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe Pastel::ColorParser, '::parse' do
4
+ subject(:parser) { described_class }
5
+
6
+ it "parses string with no color" do
7
+ expect(parser.parse("foo")).to eq([{text: 'foo'}])
8
+ end
9
+
10
+ it "parses simple color" do
11
+ expect(parser.parse("\e[32mfoo\e[0m")).to eq([
12
+ {foreground: :green, text: 'foo'}
13
+ ])
14
+ end
15
+
16
+ it "parses simple color and style" do
17
+ expect(parser.parse("\e[32;1mfoo\e[0m")).to eq([
18
+ {foreground: :green, style: :bold, text: 'foo'}
19
+ ])
20
+ end
21
+
22
+ it "parses chained colors in shorthand syntax" do
23
+ expect(parser.parse("\e[32;44mfoo\e[0m")).to eq([
24
+ {foreground: :green, background: :on_blue, text: 'foo'}
25
+ ])
26
+ end
27
+
28
+ it "parses chained colors in regular syntax" do
29
+ expect(parser.parse("\e[32m\e[44mfoo\e[0m")).to eq([
30
+ {foreground: :green, background: :on_blue, text: 'foo'}
31
+ ])
32
+ end
33
+
34
+ it "parses many colors" do
35
+ expect(parser.parse("\e[32mfoo\e[0m \e[31mbar\e[0m")).to eq([
36
+ {foreground: :green, text: 'foo'},
37
+ {text: ' '},
38
+ {foreground: :red, text: 'bar'}
39
+ ])
40
+ end
41
+
42
+ it "parses nested colors with one reset" do
43
+ expect(parser.parse("\e[32mfoo\e[31mbar\e[0m")).to eq([
44
+ {foreground: :green, text: 'foo'},
45
+ {foreground: :red, text: 'bar'}
46
+ ])
47
+ end
48
+
49
+ it "parses nested colors with two resets" do
50
+ expect(parser.parse("\e[32mfoo\e[31mbar\e[0m\e[0m")).to eq([
51
+ {foreground: :green, text: 'foo'},
52
+ {foreground: :red, text: 'bar'}
53
+ ])
54
+ end
55
+
56
+ it "parses unrest color" do
57
+ expect(parser.parse("\e[32mfoo")).to eq([
58
+ {foreground: :green, text: 'foo'}
59
+ ])
60
+ end
61
+
62
+ it "parses malformed control sequence" do
63
+ expect(parser.parse("\eA foo bar ESC\e")).to eq([
64
+ {text: "\eA foo bar ESC\e"}
65
+ ])
66
+ end
67
+ end