terminal-layout 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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