terminal-layout 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: effa639a6efd09195fc644fe6d4b325191ca7905
4
- data.tar.gz: 187b259dcc98d8584948c4856772d440c6895564
3
+ metadata.gz: 97802dee983f8248f8d8406cf323754b8768613a
4
+ data.tar.gz: 7af99e4ca121f6cf67668985954e2f52bb26aa87
5
5
  SHA512:
6
- metadata.gz: e2538c3ca865a7d81c0bc562d1a6c6c71da80fc345785ad3f3c138bb1622c0917369b36081244b48e309a7dc1fd63d4399e876341fe0b7834150a5d59c92d982
7
- data.tar.gz: 5e57352329d7cf5cecd6b09d98d6d6d65fb8931b32dc0fdc7db510a8428842ec07a6fd58a45b77aee3a803aeaa8ce775a39eda2635a3d9e19b17411752aac86f
6
+ metadata.gz: 5698f4d780074e3b2d10540a7dbbb27f7d0f6046ac93826ca86be379aa43266c997826b5723c0605788bc9b67080c9f4715cc647dd021d2f35e4eef8a08762e7
7
+ data.tar.gz: cf51538f560efbd170bb726cef284adebbede5599ae418f626f011447a2fcbadefe745c501c0a8187770c1104c344f2907238ff70ecfab5a19043e3d4fcd687e
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- terminal-layout (0.4.0)
4
+ terminal-layout (0.4.1)
5
+ ansi_string (~> 0.1)
5
6
  highline (~> 1.7, >= 1.7.8)
6
7
  ruby-terminfo (~> 0.1.1)
7
8
  ruby-termios (~> 0.9.6)
@@ -10,6 +11,7 @@ PATH
10
11
  GEM
11
12
  remote: https://rubygems.org/
12
13
  specs:
14
+ ansi_string (0.1.0)
13
15
  byebug (5.0.0)
14
16
  columnize (= 0.9.0)
15
17
  coderay (1.1.0)
@@ -1,3 +1,3 @@
1
1
  module TerminalLayout
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -1,5 +1,5 @@
1
- require 'ansi_string'
2
1
  require 'ostruct'
2
+ require 'ansi_string'
3
3
  require 'treefell'
4
4
 
5
5
  module TerminalLayout
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.add_dependency 'ansi_string', '~> 0.1'
21
22
  spec.add_dependency "ruby-terminfo", "~> 0.1.1"
22
23
  spec.add_dependency "ruby-termios", "~> 0.9.6"
23
24
  spec.add_dependency 'highline', '~> 1.7', '>= 1.7.8'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminal-layout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Dennis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-27 00:00:00.000000000 Z
11
+ date: 2016-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ansi_string
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: ruby-terminfo
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -129,11 +143,9 @@ files:
129
143
  - README.md
130
144
  - Rakefile
131
145
  - block-flow.rb
132
- - lib/ansi_string.rb
133
146
  - lib/tasks/gem.rake
134
147
  - lib/terminal_layout.rb
135
148
  - lib/terminal_layout/version.rb
136
- - spec/ansi_string_spec.rb
137
149
  - spec/spec_helper.rb
138
150
  - spec/terminal_layout_spec.rb
139
151
  - terminal-layout.gemspec
@@ -163,7 +175,6 @@ signing_key:
163
175
  specification_version: 4
164
176
  summary: A terminal layout manager
165
177
  test_files:
166
- - spec/ansi_string_spec.rb
167
178
  - spec/spec_helper.rb
168
179
  - spec/terminal_layout_spec.rb
169
180
  has_rdoc:
data/lib/ansi_string.rb DELETED
@@ -1,371 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'forwardable'
4
-
5
- class ANSIString
6
- extend Forwardable
7
- attr_reader :raw, :without_ansi
8
-
9
- def_delegators :@without_ansi, :each_char, :each_byte, :index,
10
- :match, :=~
11
-
12
- def initialize(str)
13
- process_string raw_string_for(str)
14
- end
15
-
16
- def +(other)
17
- self.class.new @raw + raw_string_for(other)
18
- end
19
-
20
- def <<(other)
21
- range = length..length
22
- str = replace_in_string(range, other)
23
- process_string raw_string_for(str)
24
- self
25
- end
26
-
27
- def insert(position, string)
28
- if position < 0
29
- position = @without_ansi.length + position + 1
30
- end
31
- self[position...position] = string
32
- self
33
- end
34
-
35
- def empty?
36
- length == 0
37
- end
38
-
39
- def [](range)
40
- # convert numeric position to a range
41
- range = (range..range) if range.is_a?(Integer)
42
-
43
- range_begin = range.begin
44
- range_end = range.end
45
-
46
- if range.exclude_end?
47
- if range_begin == 0 && range_end == 0
48
- return ""
49
- else
50
- range_end -= 1
51
- end
52
- end
53
-
54
- range_begin = @without_ansi.length - range.begin.abs if range.begin < 0
55
- range_end = @without_ansi.length - range.end.abs if range.end < 0
56
-
57
- str = build_string_with_ansi_for(range_begin..range_end)
58
- ANSIString.new str if str
59
- end
60
-
61
- def []=(range, replacement_str)
62
- # convert numeric position to a range
63
- range = (range..range) if range.is_a?(Integer)
64
-
65
- range_begin = range.begin
66
- range_end = range.exclude_end? ? range.end - 1 : range.end
67
-
68
- range_begin = @without_ansi.length - range.begin.abs if range.begin < 0
69
- range_end = @without_ansi.length - range.end.abs if range.end < 0
70
-
71
- updated_string = replace_in_string(range_begin..range_end, replacement_str)
72
- process_string raw_string_for(updated_string)
73
- self
74
- end
75
-
76
- # See String#rindex for arguments
77
- def rindex(*args)
78
- @without_ansi.rindex(*args)
79
- end
80
-
81
- def replace(str)
82
- process_string raw_string_for(str)
83
- self
84
- end
85
-
86
- def reverse
87
- str = @ansi_sequence_locations.reverse.map do |location|
88
- [location[:start_ansi_sequence], location[:text].reverse, location[:end_ansi_sequence]].join
89
- end.join
90
- ANSIString.new str
91
- end
92
-
93
- def scan(pattern)
94
- results = []
95
- without_ansi.enum_for(:scan, pattern).each do
96
- md = Regexp.last_match
97
- if md.captures.any?
98
- results << md.captures.map.with_index do |_, i|
99
- # captures use 1-based indexing
100
- self[md.begin(i+1)..md.end(i+1)-1]
101
- end
102
- else
103
- results << self[md.begin(0)..md.end(0)-1]
104
- end
105
- end
106
- results
107
- end
108
-
109
- def slice(index, length=nil)
110
- return ANSIString.new("") if length == 0
111
- range = nil
112
- index = index.without_ansi if index.is_a?(ANSIString)
113
- index = Regexp.new Regexp.escape(index) if index.is_a?(String)
114
- if index.is_a?(Integer)
115
- length ||= 1
116
- range = (index..index+length-1)
117
- elsif index.is_a?(Range)
118
- range = index
119
- elsif index.is_a?(Regexp)
120
- md = @without_ansi.match(index)
121
- capture_group_index = length || 0
122
- if md
123
- capture_group = md.offset(capture_group_index)
124
- range = (capture_group.first..capture_group.last-1)
125
- end
126
- else
127
- raise(ArgumentError, "Must pass in at least an index or a range.")
128
- end
129
- self[range] if range
130
- end
131
-
132
- def split(*args)
133
- raw.split(*args).map { |s| ANSIString.new(s) }
134
- end
135
-
136
- def strip
137
- ANSIString.new raw.strip
138
- end
139
-
140
- def length
141
- @without_ansi.length
142
- end
143
-
144
- def lines
145
- result = []
146
- current_string = ""
147
- @ansi_sequence_locations.map do |location|
148
- if location[:text] == "\n"
149
- result << ANSIString.new(current_string + "\n")
150
- current_string = ""
151
- next
152
- end
153
-
154
- location[:text].scan(/.*(?:\n|$)/).each_with_index do |line, i|
155
- break if line == ""
156
-
157
- if i == 0
158
- current_string << [
159
- location[:start_ansi_sequence],
160
- line,
161
- location[:end_ansi_sequence]
162
- ].join
163
- else
164
- result << ANSIString.new(current_string)
165
- current_string = ""
166
- current_string << [
167
- location[:start_ansi_sequence],
168
- line,
169
- location[:end_ansi_sequence]
170
- ].join
171
- end
172
- end
173
-
174
- if location[:text].end_with?("\n")
175
- result << ANSIString.new(current_string)
176
- current_string = ""
177
- next
178
- end
179
- end
180
- result << ANSIString.new(current_string) if current_string.length > 0
181
- result
182
- end
183
-
184
- def dup
185
- ANSIString.new(@raw.dup)
186
- end
187
-
188
- def sub(pattern, replacement)
189
- str = ""
190
- count = 0
191
- max_count = 1
192
- index = 0
193
- @without_ansi.enum_for(:scan, pattern).each do
194
- md = Regexp.last_match
195
- str << build_string_with_ansi_for(index...(index + md.begin(0)))
196
- index = md.end(0)
197
- break if (count += 1) == max_count
198
- end
199
- if index != @without_ansi.length
200
- str << build_string_with_ansi_for(index..@without_ansi.length)
201
- end
202
- nstr = str.gsub /(\033\[[0-9;]*m)(.+?)\033\[0m\1/, '\1\2'
203
- ANSIString.new(nstr)
204
- end
205
-
206
- def to_s
207
- @raw.dup
208
- end
209
- alias :to_str :to_s
210
-
211
- def inspect
212
- to_s.inspect
213
- end
214
-
215
- def ==(other)
216
- (other.class == self.class && other.raw == @raw) || (other.kind_of?(String) && other == @raw)
217
- end
218
-
219
- def <=>(other)
220
- (other.class == self.class && @raw <=> other.raw)
221
- end
222
-
223
- private
224
-
225
- def raw_string_for(str)
226
- str.is_a?(ANSIString) ? str.raw : str.to_s
227
- end
228
-
229
- def process_string(raw_str)
230
- @without_ansi = ""
231
- @ansi_sequence_locations = []
232
- raw_str.enum_for(:scan, /(\e\[[0-9;]*m)?(.*?)(?=\e\[[0-9;]*m|\Z)/m ).each do
233
- md = Regexp.last_match
234
- ansi_sequence, text = md.captures
235
-
236
- previous_sequence_location = @ansi_sequence_locations.last
237
- if previous_sequence_location
238
- if ansi_sequence == "\e[0m"
239
- previous_sequence_location[:end_ansi_sequence] = ansi_sequence
240
- ansi_sequence = nil
241
- elsif previous_sequence_location[:start_ansi_sequence] == ansi_sequence
242
- previous_sequence_location[:text] << text
243
- previous_sequence_location[:ends_at] += text.length
244
- previous_sequence_location[:length] += text.length
245
- @without_ansi << text
246
- next
247
- end
248
- end
249
-
250
- if ansi_sequence.nil? && text.to_s.length == 0
251
- next
252
- end
253
-
254
- @ansi_sequence_locations.push(
255
- begins_at: @without_ansi.length,
256
- ends_at: [@without_ansi.length + text.length - 1, 0].max,
257
- length: text.length,
258
- text: text,
259
- start_ansi_sequence: ansi_sequence
260
- )
261
-
262
- @without_ansi << text
263
- end
264
-
265
- @raw = @ansi_sequence_locations.map do |location|
266
- [location[:start_ansi_sequence], location[:text], location[:end_ansi_sequence]].compact.join
267
- end.join
268
-
269
- @ansi_sequence_locations
270
- end
271
-
272
- def replace_in_string(range, replacement_str)
273
- raise RangeError, "#{range.inspect} out of range" if range.begin > length
274
- return replacement_str if @ansi_sequence_locations.empty?
275
-
276
- range = range.begin..(range.end - 1) if range.exclude_end?
277
- str = ""
278
- @ansi_sequence_locations.each_with_index do |location, j|
279
- # If the given range encompasses part of the location, then we want to
280
- # include the whole location
281
- if location[:begins_at] >= range.begin && location[:ends_at] <= range.end
282
- end_index = range.end - location[:begins_at] + 1
283
-
284
- str << [
285
- location[:start_ansi_sequence],
286
- replacement_str,
287
- location[:text][end_index..-1],
288
- location[:end_ansi_sequence]
289
- ].join
290
-
291
- # If the location falls within the given range then make sure we pull
292
- # out the bits that we want, and keep ANSI escape sequenece intact while
293
- # doing so.
294
- elsif location[:begins_at] <= range.begin && location[:ends_at] >= range.end
295
- start_index = range.begin - location[:begins_at]
296
- end_index = range.end - location[:begins_at] + 1
297
-
298
- str << [
299
- location[:start_ansi_sequence],
300
- location[:text][0...start_index],
301
- replacement_str,
302
- location[:text][end_index..-1],
303
- location[:end_ansi_sequence]
304
- ].join
305
-
306
- elsif location[:ends_at] == range.begin
307
- start_index = range.begin - location[:begins_at]
308
- end_index = range.end
309
- num_chars_to_remove_from_next_location = range.end - location[:ends_at]
310
-
311
- str << [
312
- location[:start_ansi_sequence],
313
- location[:text][location[:begins_at]...(location[:begins_at]+start_index)],
314
- replacement_str,
315
- location[:text][end_index..-1],
316
- location[:end_ansi_sequence],
317
- ].join
318
-
319
- if location=@ansi_sequence_locations[j+1]
320
- old = location.dup
321
- location[:text][0...num_chars_to_remove_from_next_location] = ""
322
- location[:begins_at] += num_chars_to_remove_from_next_location
323
- location[:ends_at] += num_chars_to_remove_from_next_location
324
- end
325
-
326
- # If we're pushing onto the end of the string
327
- elsif range.begin == length && location[:ends_at] == length - 1
328
- if replacement_str.is_a?(ANSIString)
329
- str << [location[:start_ansi_sequence], location[:text], location[:end_ansi_sequence], replacement_str].join
330
- else
331
- str << [location[:start_ansi_sequence], location[:text], replacement_str, location[:end_ansi_sequence]].join
332
- end
333
- else
334
- str << [location[:start_ansi_sequence], location[:text], location[:end_ansi_sequence]].join
335
- end
336
- end
337
-
338
- str
339
- end
340
-
341
- def build_string_with_ansi_for(range)
342
- return nil if range.begin > length
343
-
344
- str = ""
345
-
346
- if range.exclude_end?
347
- range = range.begin..(range.end - 1)
348
- end
349
-
350
- @ansi_sequence_locations.each do |location|
351
- # If the given range encompasses part of the location, then we want to
352
- # include the whole location
353
- if location[:begins_at] >= range.begin && location[:ends_at] <= range.end
354
- str << [location[:start_ansi_sequence], location[:text], location[:end_ansi_sequence]].join
355
-
356
- elsif location[:begins_at] >= range.begin && location[:begins_at] <= range.end
357
- str << [location[:start_ansi_sequence], location[:text][0..(range.end - location[:begins_at])], location[:end_ansi_sequence]].join
358
-
359
- # If the location falls within the given range then make sure we pull
360
- # out the bits that we want, and keep ANSI escape sequenece intact while
361
- # doing so.
362
- elsif (location[:begins_at] <= range.begin && location[:ends_at] >= range.end) || range.cover?(location[:ends_at])
363
- start_index = range.begin - location[:begins_at]
364
- end_index = range.end - location[:begins_at]
365
- str << [location[:start_ansi_sequence], location[:text][start_index..end_index], location[:end_ansi_sequence]].join
366
- end
367
- end
368
- str
369
- end
370
-
371
- end
@@ -1,701 +0,0 @@
1
- require 'spec_helper'
2
- require 'term/ansicolor'
3
-
4
- describe 'ANSIString' do
5
- include Term::ANSIColor
6
-
7
- describe "constructing" do
8
- it "can be constructed with a String" do
9
- ansi_string = ANSIString.new "this is a string"
10
- expect(ansi_string).to be
11
- end
12
-
13
- it "can be constructed with a String containing ANSI escape sequences" do
14
- ansi_string = ANSIString.new "this #{blue('is')} a string"
15
- expect(ansi_string).to be
16
- end
17
-
18
- it "can be constructed with UTF-8 characters" do;
19
- expect do
20
- ansi_string = ANSIString.new "this #{blue('ƒ')} a string"
21
- expect(ansi_string).to be
22
- end.to_not raise_error
23
- end
24
- end
25
-
26
- describe "redundant ANSI sequences" do
27
- it "strips out redundant ANSI sequences that are immediately next to each other" do
28
- ansi_string = ANSIString.new "this is\e[31m\e[31m a string"
29
- expect(ansi_string.to_s).to eq "this is\e[31m a string"
30
- end
31
-
32
- it "strips out redundant ANSI sequences that are not immediately next to each other" do
33
- ansi_string = ANSIString.new "this \e[31m a\e[31m string"
34
- expect(ansi_string.to_s).to eq "this \e[31m a string"
35
- end
36
-
37
- it "does not strip out ANSI sequences that differ" do
38
- ansi_string = ANSIString.new "this \e[31m a\e[32m string"
39
- expect(ansi_string.to_s).to eq "this \e[31m a\e[32m string"
40
- end
41
- end
42
-
43
- describe "#+ combining strings" do
44
- let(:blue_ansi_string){ ANSIString.new blue_string }
45
- let(:yellow_ansi_string){ ANSIString.new yellow_string }
46
- let(:blue_string){ blue("this is blue") }
47
- let(:yellow_string){ yellow("this is yellow") }
48
-
49
- it "returns a new string when combining two ANSIStrings" do
50
- expect(blue_ansi_string + yellow_ansi_string).to eq ANSIString.new(blue_string + yellow_string)
51
- end
52
-
53
- it "returns a new string when combining a ANIString with a String" do
54
- expect(blue_ansi_string + yellow_string).to eq ANSIString.new(blue_string + yellow_string)
55
- end
56
- end
57
-
58
- describe "#each_byte" do
59
- let(:blue_ansi_string){ ANSIString.new blue_string }
60
- let(:blue_string){ blue("this is blue") }
61
-
62
- it "iterates over each character ignoring ANSI sequences" do
63
- expected = "this is blue"
64
- actual = ""
65
- blue_ansi_string.each_byte { |ch| actual << ch }
66
- expect(actual).to eq(expected)
67
- end
68
- end
69
-
70
- describe "#each_char" do
71
- let(:blue_ansi_string){ ANSIString.new blue_string }
72
- let(:blue_string){ blue("this is blue") }
73
-
74
- it "iterates over each character ignoring ANSI sequences" do
75
- expected = "this is blue"
76
- actual = ""
77
- blue_ansi_string.each_char { |ch| actual << ch }
78
- expect(actual).to eq(expected)
79
- end
80
- end
81
-
82
- describe "#<<" do
83
- it "appends a String onto the end of the current ANSIString" do
84
- ansi_string = ANSIString.new ""
85
- ansi_string << "a"
86
- expect(ansi_string).to eq ANSIString.new("a")
87
-
88
- ansi_string << "b"
89
- expect(ansi_string).to eq ANSIString.new("ab")
90
-
91
- ansi_string << "cd"
92
- expect(ansi_string).to eq ANSIString.new("abcd")
93
- end
94
-
95
- it "appends an ANSIString onto the end of the current ANSIString" do
96
- ansi_string = ANSIString.new ""
97
- ansi_string << ANSIString.new(blue("a"))
98
- expect(ansi_string).to eq ANSIString.new("#{blue('a')}")
99
-
100
- ansi_string << ANSIString.new(yellow("b"))
101
- expect(ansi_string).to eq ANSIString.new("#{blue('a')}#{yellow('b')}")
102
-
103
- ansi_string << ANSIString.new(red("cd"))
104
- expect(ansi_string).to eq ANSIString.new("#{blue('a')}#{yellow('b')}#{red('cd')}")
105
- end
106
- end
107
-
108
- describe "#insert (see Ruby's String#insert for intent)" do
109
- it "insert a string into the ANSIString" do
110
- ansi_string = ANSIString.new "az"
111
- ansi_string.insert 1, "thru"
112
- expect(ansi_string).to eq ANSIString.new("athruz")
113
-
114
- ansi_string.insert 0, "_"
115
- expect(ansi_string).to eq ANSIString.new("_athruz")
116
-
117
- ansi_string.insert ansi_string.length, "_"
118
- expect(ansi_string).to eq ANSIString.new("_athruz_")
119
- end
120
-
121
- it "insert an ANSIString into an ANSIString" do
122
- ansi_string = ANSIString.new blue("az")
123
- ansi_string.insert 1, yellow("thru")
124
- expect(ansi_string).to eq ANSIString.new("\e[34ma\e[33mthru\e[0mz\e[0m")
125
- end
126
-
127
- it "inserts from the end with a negative position" do
128
- ansi_string = ANSIString.new blue("az")
129
- ansi_string.insert -2, yellow("thru")
130
- expect(ansi_string).to eq ANSIString.new("\e[34ma\e[33mthru\e[0mz\e[0m")
131
- end
132
- end
133
-
134
- describe "#length" do
135
- subject(:ansi_string){ ANSIString.new blue(string) }
136
- let(:string){ "this is blue" }
137
-
138
- it "returns the length string without ANSI escape sequences" do
139
- expect(ansi_string.length).to eq string.length
140
- end
141
- end
142
-
143
- describe "#empty?" do
144
- it "returns true when empty" do
145
- expect(ANSIString.new("").empty?).to be(true)
146
- end
147
-
148
- it "returns true when it only contains ANSI sequences" do
149
- expect(ANSIString.new(blue("")).empty?).to be(true)
150
- end
151
-
152
- it "returns false when there are non-ANSI characters" do
153
- expect(ANSIString.new("a").empty?).to be(false)
154
- expect(ANSIString.new(blue("a")).empty?).to be(false)
155
- end
156
-
157
- end
158
-
159
- describe "#index" do
160
- it "returns the index of the first occurrence of the given substring" do
161
- ansi_string = ANSIString.new("this is not blue")
162
- expect(ansi_string.index("b")).to eq 12
163
-
164
- ansi_string = ANSIString.new("this is #{blue('blue')}")
165
- expect(ansi_string.index("blu")).to eq 8
166
-
167
- ansi_string = ANSIString.new("this is #{blue('blue')} and this is #{yellow('yellow')}")
168
- expect(ansi_string.index("yellow")).to eq 25
169
- end
170
-
171
- it "returns the index starting on or after an optional start position" do
172
- ansi_string = ANSIString.new("this is not blue")
173
- expect(ansi_string.index("t", 0)).to eq 0
174
-
175
- ansi_string = ANSIString.new("this is #{blue('blue')}")
176
- expect(ansi_string.index("is", 3)).to eq 5
177
- expect(ansi_string.index("bl", 7)).to eq 8
178
- expect(ansi_string.index("bl", 9)).to eq nil
179
-
180
- ansi_string = ANSIString.new("this is #{blue('blue')} and this is #{yellow('yellow')}")
181
- expect(ansi_string.index("yel", 5)).to eq 25
182
- expect(ansi_string.index("yel", 25)).to eq 25
183
- expect(ansi_string.index("yel", 26)).to eq nil
184
- end
185
-
186
- it "returns the index of the first occurrence of the given regular expression" do
187
- ansi_string = ANSIString.new("this is not blue")
188
- expect(ansi_string.index(/b/)).to eq 12
189
-
190
- ansi_string = ANSIString.new("this is #{blue('blue')}")
191
- expect(ansi_string.index(/blu/)).to eq 8
192
-
193
- ansi_string = ANSIString.new("this is #{blue('blue')} and this is #{yellow('yellow')}")
194
- expect(ansi_string.index(/y.ll.w/)).to eq 25
195
- end
196
- end
197
-
198
- describe "#rindex" do
199
- it "returns the index of the last occurrence of the given substring" do
200
- ansi_string = ANSIString.new("this is not blue")
201
- expect(ansi_string.rindex("i")).to eq 5
202
-
203
- ansi_string = ANSIString.new("this is #{blue('blue')}")
204
- expect(ansi_string.rindex("blu")).to eq 8
205
-
206
- ansi_string = ANSIString.new("this is #{blue('blue')} and this is #{yellow('yellow')}")
207
- expect(ansi_string.rindex("yellow")).to eq 25
208
- end
209
-
210
- it "returns the index of the match on or after an optional stop position" do
211
- ansi_string = ANSIString.new("this is not blue")
212
- expect(ansi_string.rindex("t", 0)).to eq 0
213
- expect(ansi_string.rindex("is", 3)).to eq 2
214
- expect(ansi_string.rindex("bl", 12)).to eq 12
215
-
216
- ansi_string = ANSIString.new("this is #{blue('blue')}")
217
- expect(ansi_string.rindex("is", 0)).to eq nil
218
- expect(ansi_string.rindex("is", 3)).to eq 2
219
- expect(ansi_string.rindex("bl", 8)).to eq 8
220
- expect(ansi_string.rindex("bl", 12)).to eq 8
221
-
222
- ansi_string = ANSIString.new("this is #{blue('blue')} and this is #{yellow('yellow')}")
223
- expect(ansi_string.rindex("yel", 5)).to eq nil
224
- expect(ansi_string.rindex("yel", 25)).to eq 25
225
- expect(ansi_string.rindex("yel", 26)).to eq 25
226
- end
227
-
228
- it "returns the index of the last occurrence of the given regular expression" do
229
- ansi_string = ANSIString.new("this is not blue")
230
- expect(ansi_string.rindex(/b/)).to eq 12
231
-
232
- ansi_string = ANSIString.new("this is #{blue('blue')}")
233
- expect(ansi_string.rindex(/blu/)).to eq 8
234
-
235
- ansi_string = ANSIString.new("this is #{blue('blue')} and this is #{yellow('yellow')}")
236
- expect(ansi_string.rindex(/y.ll.w/)).to eq 25
237
- end
238
- end
239
-
240
- describe "#[]" do
241
- subject(:ansi_string){ ANSIString.new "#{blue_string}ABC#{yellow_string}" }
242
- let(:blue_string){ blue("this is blue") }
243
- let(:yellow_string){ yellow("this is yellow") }
244
-
245
- it "returns the full substring with the appropriate ANSI start and end sequence" do
246
- expect(ansi_string[0...12]).to eq ANSIString.new(blue("this is blue"))
247
- expect(ansi_string[15..-1]).to eq ANSIString.new(yellow("this is yellow"))
248
- end
249
-
250
- it "returns a partial substring with the appropriate ANSI start sequence and provides an end sequence" do
251
- expect(ansi_string[0..1]).to eq blue("th")
252
- expect(ansi_string[17..-5]).to eq yellow("is is ye")
253
- end
254
-
255
- it "returns the correct substring when location of an ANSI sequence comes before the end of the request" do
256
- s = ANSIString.new("ABC \e[7mGemfile.lock\e[0m LICENSE.txt README.md")
257
- expect(s[4...28]).to eq ANSIString.new("\e[7mGemfile.lock\e[0m LICENSE.txt")
258
- end
259
-
260
- it "returns text that is not ANSI escaped" do
261
- expect(ansi_string[12..14]).to eq "ABC"
262
- end
263
-
264
- it "returns dos thine" do
265
- ansi_string = ANSIString.new("ABC")
266
- expect(ansi_string[0...1]).to eq "A"
267
- end
268
-
269
- it "returns up to the end" do
270
- expect(ansi_string[-2..-1]).to eq yellow("ow")
271
- end
272
-
273
- context "and the range is around the ANSI sequence location in the string" do
274
- it "returns the string with the ANSI sequences within it intact" do
275
- ansi_string = ANSIString.new "abc#{green('def')}ghi"
276
- expect(ansi_string[0..-1]).to eq "abc#{green('def')}ghi"
277
- end
278
-
279
- it "returns the string with the ANSI sequences within it intact" do
280
- ansi_string = ANSIString.new "abc#{green('def')}ghi"
281
- expect(ansi_string[0..2]).to eq "abc"
282
- end
283
- end
284
-
285
- it "returns nil when the given range is beyond the length of the string" do
286
- ansi_string = ANSIString.new "abc"
287
- expect(ansi_string[4]).to be nil
288
- end
289
- end
290
-
291
- describe "#[]=" do
292
- subject(:ansi_string){ ANSIString.new blue(string) }
293
- let(:string){ "this is blue" }
294
-
295
- it "returns a new ANSIString with the string at the given index replaced with the new string" do
296
- ansi_string[1] = "Z"
297
- expect(ansi_string).to eq ANSIString.new(blue("tZis is blue"))
298
- end
299
-
300
- it "returns a new ANSIString with the string at the given range replaced with the new string" do
301
- ansi_string[1..2] = "ZYX"
302
- expect(ansi_string).to eq ANSIString.new(blue("tZYXs is blue"))
303
- end
304
-
305
- it "supports replacing with negative indexes at the front of the string" do
306
- ansi_string[0..-1] = "abc"
307
- expect(ansi_string).to eq ANSIString.new(blue("abc"))
308
- end
309
-
310
- it "supports replacing with negative indexes in the middle of the string" do
311
- ansi_string = ANSIString.new(blue("abc"))
312
- ansi_string[1..-2] = red("*")
313
-
314
- # Do not preserve reset sequences (e.g. "\e[0m") when inserting/replacing.
315
- # So no \e[0m before the replacement '*'
316
- expect(ansi_string).to eq ANSIString.new("\e[34ma\e[31m*\e[0mc\e[0m")
317
- end
318
-
319
- it "preserves coloring when part of the text with a String" do
320
- ansi_string[0..3] = "that"
321
- expect(ansi_string).to eq ANSIString.new(blue("that is blue"))
322
- end
323
-
324
- it "preserves coloring when replacing all of the text with a String" do
325
- ansi_string[0..11] = "foobar"
326
- expect(ansi_string).to eq ANSIString.new(blue("foobar"))
327
- end
328
-
329
- it "preserves coloring when part of the text with a String and we're not starting at an index of 0" do
330
- ansi_string[5..6] = "ain't"
331
- expect(ansi_string).to eq ANSIString.new(blue("this ain't blue"))
332
- end
333
-
334
- context "appending a string to the very end" do
335
- subject(:ansi_string){ ANSIString.new green("CircleCI pass") }
336
-
337
- it "combines when the ANSI sequences are the same" do
338
- ansi_string[13..15] = ANSIString.new green("ed")
339
- expect(ansi_string).to eq ANSIString.new(green("CircleCI passed"))
340
- end
341
-
342
- it "doesn't combine when the ANSI sequences are different" do
343
- ansi_string[13..15] = ANSIString.new red("ed")
344
- expect(ansi_string).to eq ANSIString.new(green("CircleCI pass") + red("ed"))
345
- end
346
- end
347
-
348
- context "replacing on newline boundaries" do
349
- subject(:ansi_string){ ANSIString.new "this\nthat" }
350
-
351
- it "keeps the new line intact" do
352
- ansi_string[2...4] = "IS"
353
- expect(ansi_string).to eq ANSIString.new("thIS\nthat")
354
- end
355
- end
356
-
357
- context "replacing the same location twice" do
358
- subject(:ansi_string){ ANSIString.new "this\nthat" }
359
-
360
- it "keeps the new line intact" do
361
- ansi_string[2...4] = blue("IS")
362
- ansi_string[2...4] = blue("IS")
363
- expect(ansi_string).to eq ANSIString.new("th#{blue('IS')}\nthat")
364
- end
365
- end
366
-
367
- context "replacing a substring that goes across ANSI sequence boundaries" do
368
- subject(:ansi_string){ ANSIString.new "this#{blue('that')}" }
369
-
370
- it "moves the boundaries when using positive indexes and a regular String replacement" do
371
- ansi_string[3..4] = yellow("SORRY")
372
- expect(ansi_string).to eq ANSIString.new("thi#{yellow('SORRY')}#{blue('hat')}")
373
- end
374
-
375
- it "moves the boundaries when using positive indexes and an ANSIString replacement" do
376
- ansi_string[3..4] = yellow("SORRY")
377
- expect(ansi_string).to eq ANSIString.new("thi#{yellow('SORRY')}#{blue('hat')}")
378
- end
379
-
380
- it "moves the boundaries when using negatives indexes and a regular String replacement" do
381
- ansi_string[-5..4] = "SORRY"
382
- expect(ansi_string).to eq ANSIString.new("thiSORRY#{blue('hat')}")
383
- end
384
-
385
- it "moves the boundaries when using negatives indexes and an ANSIString replacement" do
386
- ansi_string[-5..4] = yellow("SORRY")
387
- expect(ansi_string).to eq ANSIString.new("thi#{yellow('SORRY')}#{blue('hat')}")
388
- end
389
- end
390
-
391
- context "clearing the string" do
392
- subject(:ansi_string){ ANSIString.new "this\nthat" }
393
-
394
- it "clears the string" do
395
- ansi_string[0..-1] = ""
396
- expect(ansi_string).to eq ANSIString.new("")
397
- end
398
- end
399
-
400
- context "expanding a string" do
401
- subject(:ansi_string){ ANSIString.new "" }
402
-
403
- it "expands the string" do
404
- ansi_string[0..-1] = ANSIString.new(blue("HI"))
405
- expect(ansi_string).to eq ANSIString.new(blue("HI"))
406
- end
407
- end
408
-
409
- it "raises an error out of index" do
410
- expect {
411
- ansi_string[14..15] = string
412
- }.to raise_error(RangeError, "14..15 out of range")
413
- end
414
-
415
- context "replacing a substring that comes entirely after an ANSI sequence" do
416
- subject(:ansi_string){ ANSIString.new "this #{blue('is')} your television screen." }
417
-
418
- it "places the substring in the correct location" do
419
- ansi_string[14..15] = "YO YO"
420
- expect(ansi_string).to eq ANSIString.new "this #{blue('is')} your tYO YOevision screen."
421
- end
422
- end
423
- end
424
-
425
- describe "#dup" do
426
- subject(:ansi_string){ ANSIString.new blue(string) }
427
- let(:string){ "this is blue" }
428
-
429
- it "returns a dup'd version of itself" do
430
- duped = ansi_string.dup
431
- expect(duped).to be_kind_of(ANSIString)
432
- expect(duped.raw).to eq(ansi_string.raw)
433
- end
434
- end
435
-
436
- describe "#lines" do
437
- subject(:ansi_string){ ANSIString.new blue(string) }
438
- let(:string){ "this\nis\nblue" }
439
-
440
- it "returns lines" do
441
- expect(ansi_string.lines).to eq [
442
- ANSIString.new(blue("this\n")),
443
- ANSIString.new(blue("is\n")),
444
- ANSIString.new(blue("blue"))
445
- ]
446
- end
447
-
448
- it "returns lines" do
449
- ansi_string = ANSIString.new blue("abc") + "\n" + red("d\nef") + "hi\n" + yellow("foo")
450
- expect(ansi_string.lines).to eq [
451
- ANSIString.new(blue("abc") + "\n"),
452
- ANSIString.new(red("d\n")),
453
- ANSIString.new(red("ef") + "hi\n"),
454
- ANSIString.new(yellow("foo"))
455
- ]
456
- end
457
- end
458
-
459
- describe "#==" do
460
- subject(:ansi_string){ ANSIString.new blue(string) }
461
- let(:string){ "this is blue" }
462
-
463
- it "returns true when comparing against itself" do
464
- expect(ansi_string).to eq ansi_string
465
- end
466
-
467
- it "returns true when comparing against another ANSIString with the same contents" do
468
- expect(ansi_string).to eq ANSIString.new(blue(string))
469
- end
470
-
471
- it "returns false when comparing against another ANSIString with differnent contents" do
472
- expect(ansi_string).to_not eq ANSIString.new(blue("other stuff"))
473
- end
474
-
475
- it "returns true when comparing against a String with the same raw contents" do
476
- expect(ansi_string).to eq blue(string)
477
- end
478
-
479
- it "returns true when comparing against a String that doesn't match its raw contents" do
480
- expect(ansi_string).to_not eq "asfsd"
481
- end
482
- end
483
-
484
- describe "<=>" do
485
- let(:string_1){ ANSIString.new blue("abc") }
486
- let(:string_2){ ANSIString.new blue("def") }
487
-
488
- it "behaves the same as a normal string" do
489
- expect(string_1 <=> string_2).to eq(-1)
490
- expect(string_1 <=> string_1).to eq(0)
491
- expect(string_2 <=> string_1).to eq(1)
492
- end
493
- end
494
-
495
- describe "#match" do
496
- it "matches on a string pattren" do
497
- string = "apples are bananas are they not?"
498
- ansi_string = ANSIString.new("app#{red('les are bananas')} are they not?")
499
- expect(ansi_string.match("are")).to eq(string.match("are"))
500
- end
501
-
502
- it "matches on a regex pattren" do
503
- string = "apples are bananas are they not?"
504
- ansi_string = ANSIString.new("app#{red('les are bananas')} are they not?")
505
- expect(ansi_string.match(/are/)).to eq(string.match(/are/))
506
- end
507
- end
508
-
509
- describe "#=~" do
510
- it "matches on a regex pattren" do
511
- string = "apples are bananas are they not?"
512
- ansi_string = ANSIString.new("app#{red('les are bananas')} are they not?")
513
- expect(ansi_string =~ /are/).to eq(string =~ /are/)
514
- end
515
- end
516
-
517
- describe "#scan" do
518
- it "scans without capture groups" do
519
- string = "567"
520
- ansi_string = ANSIString.new("1234#{red('5678')}90")
521
- expect(ansi_string.scan(/.{2}/)).to eq([
522
- ANSIString.new("12"),
523
- ANSIString.new("34"),
524
- ANSIString.new("#{red('56')}"),
525
- ANSIString.new("#{red('78')}"),
526
- ANSIString.new("90")
527
- ])
528
- end
529
-
530
- it "scans with capture groups" do
531
- string = "567"
532
- ansi_string = ANSIString.new("1234#{red('5678')}90")
533
- expect(ansi_string.scan(/(.)./)).to eq([
534
- [ANSIString.new("1")],
535
- [ANSIString.new("3")],
536
- [ANSIString.new("#{red('5')}")],
537
- [ANSIString.new("#{red('7')}")],
538
- [ANSIString.new("9")]
539
- ])
540
- end
541
- end
542
-
543
- describe "#replace" do
544
- it "replaces the contents of the current string with the new string" do
545
- ansi_string = ANSIString.new("abc")
546
- original_object_id = ansi_string.object_id
547
- expect(ansi_string.replace("def")).to eq ANSIString.new("def")
548
- expect(ansi_string.object_id).to eq(original_object_id)
549
- end
550
- end
551
-
552
- describe "#reverse" do
553
- it "reverses the string" do
554
- ansi_string = ANSIString.new("abc")
555
- expect(ansi_string.reverse).to eq ANSIString.new("cba")
556
- end
557
-
558
- it "reverses the string with ANSI sequences" do
559
- ansi_string = ANSIString.new("a#{blue('b')}#{yellow('c')}")
560
- expect(ansi_string.reverse).to eq ANSIString.new("#{yellow('c')}#{blue('b')}a")
561
- end
562
- end
563
-
564
- describe "#slice" do
565
- it "returns a substring of one character given a numeric index" do
566
- ansi_string = ANSIString.new("a#{blue('b')}c")
567
- expect(ansi_string.slice(0)).to eq ANSIString.new("a")
568
- expect(ansi_string.slice(1)).to eq ANSIString.new(blue("b"))
569
- expect(ansi_string.slice(2)).to eq ANSIString.new("c")
570
- end
571
-
572
- it "returns a substring of characters of N length given a start index and max length N" do
573
- ansi_string = ANSIString.new("a#{blue('b')}c")
574
- expect(ansi_string.slice(0, 0)).to eq ANSIString.new("")
575
- expect(ansi_string.slice(0, 2)).to eq ANSIString.new("a#{blue('b')}")
576
- expect(ansi_string.slice(1, 2)).to eq ANSIString.new("#{blue('b')}c")
577
-
578
- # length is over, doesn't blow up
579
- expect(ansi_string.slice(1, 3)).to eq ANSIString.new("#{blue('b')}c")
580
- end
581
-
582
- it "returns a substring of characters using a range as delimiters" do
583
- ansi_string = ANSIString.new("a#{blue('b')}c")
584
- expect(ansi_string.slice(0..1)).to eq ANSIString.new("a#{blue('b')}")
585
- expect(ansi_string.slice(0...2)).to eq ANSIString.new("a#{blue('b')}")
586
-
587
- # length is over, doesn't blow up
588
- expect(ansi_string.slice(1..3)).to eq ANSIString.new("#{blue('b')}c")
589
- end
590
-
591
- it "returns a substring of characters matching the given regex" do
592
- ansi_string = ANSIString.new("a#{blue('b')}c")
593
- expect(ansi_string.slice(/b/)).to eq ANSIString.new("#{blue('b')}")
594
- expect(ansi_string.slice(/(b)c/)).to eq ANSIString.new("#{blue('b')}c")
595
-
596
- # length is over, doesn't blow up
597
- expect(ansi_string.slice(/.*/)).to eq ANSIString.new("a#{blue('b')}c")
598
- end
599
-
600
- it "returns a substring for the capture group matching the given regex and capture group index" do
601
- ansi_string = ANSIString.new("a#{blue('b')}c")
602
- expect(ansi_string.slice(/((a)(b)(c))/, 1)).to eq ANSIString.new("a#{blue('b')}c")
603
- expect(ansi_string.slice(/((a)(b)(c))/, 2)).to eq ANSIString.new("a")
604
- expect(ansi_string.slice(/((a)(b)(c))/, 3)).to eq ANSIString.new("#{blue('b')}")
605
- expect(ansi_string.slice(/((a)(b)(c))/, 4)).to eq ANSIString.new("c")
606
- end
607
-
608
- it "returns the substring when a given string is found" do
609
- ansi_string = ANSIString.new("a#{blue('b')}c")
610
- expect(ansi_string.slice("bc")).to eq(ANSIString.new("#{blue('b')}c"))
611
- end
612
-
613
- it "returns nil when no matches are found" do
614
- ansi_string = ANSIString.new("a#{blue('b')}c")
615
- expect(ansi_string.slice("zzz")).to be nil
616
- expect(ansi_string.slice(/zzz/)).to be nil
617
- expect(ansi_string.slice(99)).to be nil
618
- expect(ansi_string.slice(99, 100)).to be nil
619
- expect(ansi_string.slice(99..100)).to be nil
620
- end
621
- end
622
-
623
- describe "#split" do
624
- it "splits on the given string pattern" do
625
- ansi_string = ANSIString.new("apples are #{red('red')}. bananas are #{blue('blue')}. cats are #{yellow('yellow')}.")
626
- expect(ansi_string.split(". ")).to eq([
627
- ANSIString.new("apples are #{red('red')}"),
628
- ANSIString.new("bananas are #{blue('blue')}"),
629
- ANSIString.new("cats are #{yellow('yellow')}.")
630
- ])
631
- end
632
-
633
- it "splits on the given regex pattern" do
634
- ansi_string = ANSIString.new("apples are #{red('red')}. bananas are #{blue('blue')}. cats are #{yellow('yellow')}.")
635
- expect(ansi_string.split(/\.\s?/)).to eq([
636
- ANSIString.new("apples are #{red('red')}"),
637
- ANSIString.new("bananas are #{blue('blue')}"),
638
- ANSIString.new("cats are #{yellow('yellow')}")
639
- ])
640
- end
641
-
642
- it "limits how many times it splits with a secondary limit argument" do
643
- ansi_string = ANSIString.new("apples are #{red('red')}. bananas are #{blue('blue')}. cats are #{yellow('yellow')}.")
644
- expect(ansi_string.split(/\.\s?/, 2)).to eq([
645
- ANSIString.new("apples are #{red('red')}"),
646
- ANSIString.new("bananas are #{blue('blue')}. cats are #{yellow('yellow')}.")
647
- ])
648
- end
649
- end
650
-
651
- describe "#strip" do
652
- it 'returns a copy of the string with leading and trailing whitespace removed' do
653
- ansi_string = ANSIString.new " this is his #{blue('pig')} "
654
- expect(ansi_string.strip).to eq ANSIString.new "this is his #{blue('pig')}"
655
- expect(ansi_string).to eq ANSIString.new " this is his #{blue('pig')} "
656
- end
657
- end
658
-
659
- describe "#sub" do
660
- subject(:ansi_string){ ANSIString.new blue(string) }
661
- let(:string){ "this is blue" }
662
-
663
- it "returns an ANSIString" do
664
- expect(ansi_string.sub(/ is /, "")).to eq ANSIString.new(blue("thisblue"))
665
- end
666
-
667
- it "works across ansi sequences" do
668
- blue_string = blue("this is blue")
669
- yellow_string = yellow("this is yellow")
670
- non_colored_string = "hi there\nbye there"
671
- str = ANSIString.new(blue_string + yellow_string + non_colored_string + " \n \n \n ")
672
- expect(str.sub(/\s*\Z/m, "")).to eq ANSIString.new(blue_string + yellow_string + non_colored_string)
673
- end
674
- end
675
-
676
- describe "#gsub" do
677
- it "needs to be implemented"
678
- end
679
-
680
- describe "#to_s" do
681
- subject(:ansi_string){ ANSIString.new blue(string) }
682
- let(:string){ "this is blue" }
683
-
684
- it "returns the ANSI capable string" do
685
- expect(ansi_string.to_s).to eq blue(string)
686
- end
687
- end
688
-
689
- describe "#inspect" do
690
- subject(:ansi_string){ ANSIString.new blue(string) }
691
- let(:string){ "this is blue" }
692
-
693
- it "returns a quoted version of the strong" do
694
- expect(ansi_string.inspect).to eq "\"\\e[34mthis is blue\\e[0m\""
695
- end
696
- end
697
-
698
- describe "#succ" do
699
- it "needs to be implemented"
700
- end
701
- end