strings 0.1.5 → 0.1.6

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
  SHA256:
3
- metadata.gz: 59762ac60270a0fa21a1989b6ec1bf8b98eac632fc9ff8c091ef7dd711a8baf8
4
- data.tar.gz: 43276937aaef5a75cb43e8e29a6ea9f5f09b4488d22a84a03fa128de07840afb
3
+ metadata.gz: 921f81ef625130227be2240cdd40e00a88db1f902abd30af157833b31fc9e57c
4
+ data.tar.gz: f26a6ca02ea2a3fb59cd81c4347ee2381f7b745c1b80975ce8e3262d33b91a9e
5
5
  SHA512:
6
- metadata.gz: 29244946de99fa208749f9ca7f88010b70b466f0d29583f76b9b982b9b299a69b14a1f76a43cd537f46e758f2f7e3e40f057d6506650b67fcd84d6e788f127c0
7
- data.tar.gz: 6d8b306a59eaafb78308e293739dadaaded471ef2a97daffc3cf31681a490df05067845600faed4b2fdcf390aa307016934f07751a7d3aa5c5fff49363162812
6
+ metadata.gz: 03de4bc3bc330b62ea283a86904edf9a67d2921c1c42c12e12168f5b4d1d306cabd2bee7bbfc519e70bdbfc5f2a93f356aa99a47b830e1b28135d1f07b42ec36
7
+ data.tar.gz: 547a0b7849b8fa00db911f2e066cb0157f64cb5b169f64d5f62068f0e34be387b86bf862edefee08c6d98598d7f37f37416ea480bd58e2cff0338b1fb8ab31cc
@@ -1,5 +1,15 @@
1
1
  # Change log
2
2
 
3
+ ## [v0.1.6] - 2019-08-28
4
+
5
+ ### Changed
6
+ * Change Wrap#wrap, Align#align & Pad#pad to handle different line endings
7
+ * Change Pad#pad to pad empty lines
8
+
9
+ ### Fixed
10
+ * Fix Wrap#wrap to handle adjacent ANSI codes
11
+ * Fix Wrap#insert_ansi to handle nested ANSI codes
12
+
3
13
  ## [v0.1.5] - 2019-03-29
4
14
 
5
15
  ### Changed
@@ -30,6 +40,7 @@
30
40
 
31
41
  * Initial implementation and release
32
42
 
43
+ [v0.1.6]: https://github.com/piotrmurach/strings/compare/v0.1.4...v0.1.5
33
44
  [v0.1.5]: https://github.com/piotrmurach/strings/compare/v0.1.4...v0.1.5
34
45
  [v0.1.4]: https://github.com/piotrmurach/strings/compare/v0.1.3...v0.1.4
35
46
  [v0.1.3]: https://github.com/piotrmurach/strings/compare/v0.1.2...v0.1.3
data/README.md CHANGED
@@ -386,7 +386,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
386
386
 
387
387
  Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/strings. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
388
388
 
389
- 1. Fork it ( https://github.com/piotrmurach/verse/fork )
389
+ 1. Fork it ( https://github.com/piotrmurach/strings/fork )
390
390
  2. Create your feature branch (`git checkout -b my-new-feature`)
391
391
  3. Commit your changes (`git commit -am 'Add some feature'`)
392
392
  4. Push to the branch (`git push origin my-new-feature`)
@@ -7,8 +7,8 @@ module Strings
7
7
  # Responsible for text alignment
8
8
  module Align
9
9
  NEWLINE = "\n".freeze
10
-
11
- SPACE = ' '.freeze
10
+ SPACE = " ".freeze
11
+ LINE_BREAK = %r{\r\n|\r|\n}.freeze
12
12
 
13
13
  # Aligns text within the width.
14
14
  #
@@ -62,9 +62,11 @@ module Strings
62
62
  # @return [String]
63
63
  #
64
64
  # @api public
65
- def align_left(text, width, fill: SPACE, separator: NEWLINE)
65
+ def align_left(text, width, fill: SPACE, separator: nil)
66
66
  return if width.nil?
67
- each_line(text, separator) do |line|
67
+ sep = separator || text[LINE_BREAK] || NEWLINE
68
+
69
+ each_line(text, sep) do |line|
68
70
  width_diff = width - display_width(line)
69
71
  if width_diff > 0
70
72
  line + fill * width_diff
@@ -80,9 +82,11 @@ module Strings
80
82
  # @return [String]
81
83
  #
82
84
  # @api public
83
- def align_center(text, width, fill: SPACE, separator: NEWLINE)
85
+ def align_center(text, width, fill: SPACE, separator: nil)
84
86
  return text if width.nil?
85
- each_line(text, separator) do |line|
87
+ sep = separator || text[LINE_BREAK] || NEWLINE
88
+
89
+ each_line(text, sep) do |line|
86
90
  width_diff = width - display_width(line)
87
91
  if width_diff > 0
88
92
  right_count = (width_diff.to_f / 2).ceil
@@ -100,9 +104,11 @@ module Strings
100
104
  # @return [String]
101
105
  #
102
106
  # @api public
103
- def align_right(text, width, fill: SPACE, separator: NEWLINE)
107
+ def align_right(text, width, fill: SPACE, separator: nil)
104
108
  return text if width.nil?
105
- each_line(text, separator) do |line|
109
+ sep = separator || text[LINE_BREAK] || NEWLINE
110
+
111
+ each_line(text, sep) do |line|
106
112
  width_diff = width - display_width(line)
107
113
  if width_diff > 0
108
114
  fill * width_diff + line
@@ -9,8 +9,8 @@ module Strings
9
9
  # Responsible for text padding
10
10
  module Pad
11
11
  NEWLINE = "\n".freeze
12
-
13
- SPACE = ' '.freeze
12
+ SPACE = " ".freeze
13
+ LINE_BREAK = %r{\r\n|\r|\n}.freeze
14
14
 
15
15
  # Apply padding to multiline text with ANSI codes
16
16
  #
@@ -31,10 +31,11 @@ module Strings
31
31
  # @return [String]
32
32
  #
33
33
  # @api private
34
- def pad(text, padding, fill: SPACE, separator: NEWLINE)
34
+ def pad(text, padding, fill: SPACE, separator: nil)
35
35
  padding = Strings::Padder.parse(padding)
36
36
  text_copy = text.dup
37
- line_width = max_line_length(text, separator)
37
+ sep = separator || text[LINE_BREAK] || NEWLINE
38
+ line_width = max_line_length(text, sep)
38
39
  output = []
39
40
 
40
41
  filler_line = fill * line_width
@@ -43,7 +44,8 @@ module Strings
43
44
  output << pad_around(filler_line, padding, fill: fill)
44
45
  end
45
46
 
46
- text_copy.split(separator).each do |line|
47
+ text_copy.split(sep).each do |line|
48
+ line = line.empty? ? filler_line : line
47
49
  output << pad_around(line, padding, fill: fill)
48
50
  end
49
51
 
@@ -51,7 +53,7 @@ module Strings
51
53
  output << pad_around(filler_line, padding, fill: fill)
52
54
  end
53
55
 
54
- output.join(separator)
56
+ output.join(sep)
55
57
  end
56
58
  module_function :pad
57
59
 
@@ -72,7 +72,7 @@ module Strings
72
72
 
73
73
  # Set top padding
74
74
  #
75
- # @param [Integer] val
75
+ # @param [Integer] value
76
76
  #
77
77
  # @return [nil]
78
78
  #
@@ -92,7 +92,7 @@ module Strings
92
92
 
93
93
  # Set right padding
94
94
  #
95
- # @param [Integer] val
95
+ # @param [Integer] value
96
96
  #
97
97
  # @api public
98
98
  def right=(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Strings
4
- VERSION = '0.1.5'
4
+ VERSION = "0.1.6"
5
5
  end # Strings
@@ -9,12 +9,10 @@ require_relative 'fold'
9
9
  module Strings
10
10
  module Wrap
11
11
  DEFAULT_WIDTH = 80
12
-
13
12
  NEWLINE = "\n".freeze
14
-
15
- SPACE = ' '.freeze
16
-
17
- LINE_BREAK = "\r\n+|\r+|\n+".freeze
13
+ SPACE = " ".freeze
14
+ LINE_BREAK = %r{\r\n|\r|\n}.freeze
15
+ LINE_BREAKS = "\r\n+|\r+|\n+".freeze
18
16
 
19
17
  # Wrap a text into lines no longer than wrap_at length.
20
18
  # Preserves existing lines and existing word boundaries.
@@ -26,14 +24,15 @@ module Strings
26
24
  # >text
27
25
  #
28
26
  # @api public
29
- def wrap(text, wrap_at = DEFAULT_WIDTH)
30
- if text.length < wrap_at.to_i || wrap_at.to_i.zero?
27
+ def wrap(text, wrap_at = DEFAULT_WIDTH, separator: nil)
28
+ if text.scan(/[[:print:]]/).length < wrap_at.to_i || wrap_at.to_i.zero?
31
29
  return text
32
30
  end
33
31
  ansi_stack = []
34
- text.split(%r{#{LINE_BREAK}}, -1).map do |paragraph|
32
+ sep = separator || text[LINE_BREAK] || NEWLINE
33
+ text.split(%r{#{LINE_BREAKS}}, -1).map do |paragraph|
35
34
  format_paragraph(paragraph, wrap_at, ansi_stack)
36
- end * NEWLINE
35
+ end * sep
37
36
  end
38
37
  module_function :wrap
39
38
 
@@ -70,7 +69,12 @@ module Strings
70
69
  elsif ansi_matched
71
70
  ansi_stack << [ansi[0...-1].join, line_length + word_length]
72
71
  ansi_matched = false
73
- ansi = []
72
+
73
+ if ansi.last == Strings::ANSI::CSI
74
+ ansi = [ansi.last]
75
+ else
76
+ ansi = []
77
+ end
74
78
  end
75
79
  next if ansi.length > 0
76
80
  end
@@ -91,26 +95,26 @@ module Strings
91
95
  end
92
96
 
93
97
  if char == SPACE # ends with space
94
- lines << insert_ansi(ansi_stack, line.join)
98
+ lines << insert_ansi(line.join, ansi_stack)
95
99
  line = []
96
100
  line_length = 0
97
101
  word << char
98
102
  word_length += char_length
99
103
  elsif word_length + char_length <= wrap_at
100
- lines << insert_ansi(ansi_stack, line.join)
104
+ lines << insert_ansi(line.join, ansi_stack)
101
105
  line = [word.join + char]
102
106
  line_length = word_length + char_length
103
107
  word = []
104
108
  word_length = 0
105
109
  else # hyphenate word - too long to fit a line
106
- lines << insert_ansi(ansi_stack, word.join)
110
+ lines << insert_ansi(word.join, ansi_stack)
107
111
  line_length = 0
108
112
  word = [char]
109
113
  word_length = char_length
110
114
  end
111
115
  end
112
- lines << insert_ansi(ansi_stack, line.join) unless line.empty?
113
- lines << insert_ansi(ansi_stack, word.join) unless word.empty?
116
+ lines << insert_ansi(line.join, ansi_stack) unless line.empty?
117
+ lines << insert_ansi(word.join, ansi_stack) unless word.empty?
114
118
  lines
115
119
  end
116
120
  module_function :format_paragraph
@@ -120,36 +124,43 @@ module Strings
120
124
  # Check if there are any ANSI states, if present
121
125
  # insert ANSI codes at given positions unwinding the stack.
122
126
  #
123
- # @param [Array[Array[String, Integer]]] ansi_stack
124
- # the ANSI codes to apply
125
- #
126
127
  # @param [String] string
127
128
  # the string to insert ANSI codes into
128
129
  #
130
+ # @param [Array[Array[String, Integer]]] ansi_stack
131
+ # the ANSI codes to apply
132
+ #
129
133
  # @return [String]
130
134
  #
131
135
  # @api private
132
- def insert_ansi(ansi_stack, string)
136
+ def insert_ansi(string, ansi_stack = [])
133
137
  return string if ansi_stack.empty?
134
- to_remove = 0
135
- reset_index = -1
136
- output = string.dup
137
- resetting = false
138
- ansi_stack.reverse_each do |state|
139
- if state[0] =~ /#{Regexp.quote(Strings::ANSI::RESET)}/
140
- resetting = true
141
- reset_index = state[1]
142
- to_remove += 2
138
+ return string if string.empty?
139
+
140
+ new_stack = []
141
+ output = string.dup
142
+ length = string.size
143
+ matched_reset = false
144
+ ansi_reset = Strings::ANSI::RESET
145
+
146
+ # Reversed so that string index don't count ansi
147
+ ansi_stack.reverse_each do |ansi|
148
+ if ansi[0] =~ /#{Regexp.quote(ansi_reset)}/
149
+ matched_reset = true
150
+ output.insert(ansi[1], ansi_reset)
143
151
  next
144
- elsif !resetting
145
- reset_index = -1
146
- resetting = false
152
+ elsif !matched_reset # ansi without reset
153
+ matched_reset = false
154
+ new_stack << ansi # keep the ansi
155
+ next if ansi[1] == length
156
+ output.insert(-1, ansi_reset) # add reset at the end
147
157
  end
148
158
 
149
- color, color_index = *state
150
- output.insert(reset_index, Strings::ANSI::RESET).insert(color_index, color)
159
+ output.insert(ansi[1], ansi[0])
151
160
  end
152
- ansi_stack.pop(to_remove) # remove used states
161
+
162
+ ansi_stack.replace(new_stack)
163
+
153
164
  output
154
165
  end
155
166
  module_function :insert_ansi
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if ENV['COVERAGE'] || ENV['TRAVIS']
2
4
  require 'simplecov'
3
5
  require 'coveralls'
4
6
 
5
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
7
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
6
8
  SimpleCov::Formatter::HTMLFormatter,
7
9
  Coveralls::SimpleCov::Formatter
8
- ]
10
+ ])
9
11
 
10
12
  SimpleCov.start do
11
13
  command_name 'spec'
@@ -59,4 +59,13 @@ RSpec.describe Strings::Align, '#align_left' do
59
59
  "outdone by the madness of men***********"
60
60
  ].join)
61
61
  end
62
+
63
+ it "handles \r\n line separator" do
64
+ text = "Closes #360\r\n\r\nCloses !217"
65
+ expect(Strings::Align.align_left(text, 27)).to eq([
66
+ "Closes #360 ",
67
+ " ",
68
+ "Closes !217 "
69
+ ].join("\r\n"))
70
+ end
62
71
  end
@@ -59,4 +59,13 @@ RSpec.describe Strings::Align, '#align_right' do
59
59
  "***********outdone by the madness of men"
60
60
  ].join)
61
61
  end
62
+
63
+ it "handles \r\n line separator" do
64
+ text = "Closes #360\r\n\r\nCloses !217"
65
+ expect(Strings::Align.align_right(text, 27)).to eq([
66
+ " Closes #360",
67
+ " ",
68
+ " Closes !217"
69
+ ].join("\r\n"))
70
+ end
62
71
  end
@@ -74,4 +74,22 @@ RSpec.describe Strings::Align, '#align' do
74
74
  "*****outdone by the madness of men******"
75
75
  ].join)
76
76
  end
77
+
78
+ it "handles \r\n line separator" do
79
+ text = "Closes #360\r\n\r\nCloses !217"
80
+ expect(Strings::Align.align(text, 27)).to eq([
81
+ "Closes #360 ",
82
+ " ",
83
+ "Closes !217 "
84
+ ].join("\r\n"))
85
+ end
86
+
87
+ it "handles \r\n line separator and centers" do
88
+ text = "Closes #360\r\n\r\nCloses !217"
89
+ expect(Strings::Align.align_center(text, 27)).to eq([
90
+ " Closes #360 ",
91
+ " ",
92
+ " Closes !217 "
93
+ ].join("\r\n"))
94
+ end
77
95
  end
@@ -60,4 +60,15 @@ RSpec.describe Strings::Pad, '#pad' do
60
60
  " ",
61
61
  ].join("\n"))
62
62
  end
63
+
64
+ it "handles \r\n line separator" do
65
+ text = "Closes #360\r\n\r\nCloses !217"
66
+ expect(Strings::Pad.pad(text, [1,1,1,1])).to eq([
67
+ " ",
68
+ " Closes #360 ",
69
+ " ",
70
+ " Closes !217 ",
71
+ " "
72
+ ].join("\r\n"))
73
+ end
63
74
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "#insert_ansi" do
4
+ it "doesn't do anything when empty stack" do
5
+ text = "Ignorance is the parent of fear."
6
+
7
+ val = Strings::Wrap.insert_ansi(text, [])
8
+
9
+ expect(val).to eq(text)
10
+ end
11
+
12
+ it "inserts ANSI strings in a single line" do
13
+ text = "Ignorance is the parent of fear."
14
+ stack = [["\e[32;44m", 0], ["\e[0m", text.size]]
15
+
16
+ val = Strings::Wrap.insert_ansi(text, stack)
17
+
18
+ expect(val).to eq("\e[32;44mIgnorance is the parent of fear.\e[0m")
19
+ expect(stack).to eq([])
20
+ end
21
+
22
+ it "inserts ANSI strings with missing reset in a single line" do
23
+ text = "Ignorance is the parent of fear."
24
+ stack = [["\e[32;44m", 0]]
25
+
26
+ val = Strings::Wrap.insert_ansi(text, stack)
27
+
28
+ expect(val).to eq("\e[32;44mIgnorance is the parent of fear.\e[0m")
29
+ expect(stack).to eq([["\e[32;44m", 0]])
30
+ end
31
+
32
+ it "inserts 3 ansi strings in a single line" do
33
+ text = "Ignorance is the parent of fear."
34
+ stack = [
35
+ ["\e[32m", 0], ["\e[0m", 9],
36
+ ["\e[33m", 13], ["\e[0m", 16],
37
+ ["\e[34m", 27], ["\e[0m", 31]
38
+ ]
39
+
40
+ val = Strings::Wrap.insert_ansi(text, stack)
41
+
42
+ expect(val).to eq("\e[32mIgnorance\e[0m is \e[33mthe\e[0m parent of \e[34mfear\e[0m.")
43
+ expect(stack).to eq([])
44
+ end
45
+
46
+ it "inserts nested ANSI strings in a single line" do
47
+ text = "Ignorance is the parent of fear."
48
+ stack = [["\e[32m", 10], ["\e[33m", 17], ["\e[0m", 23], ["\e[0m", 26]]
49
+
50
+ val = Strings::Wrap.insert_ansi(text, stack)
51
+
52
+ expect(val).to eq("Ignorance \e[32mis the \e[33mparent\e[0m of\e[0m fear.")
53
+ expect(stack).to eq([])
54
+ end
55
+
56
+ it "removes matching pairs of ANSI codes only" do
57
+ text = "one"
58
+ stack = [["\e[32m", 0], ["\e[0m", 3], ["\e[33m", 3]]
59
+
60
+ val = Strings::Wrap.insert_ansi(text, stack)
61
+
62
+ expect(val).to eq("\e[32mone\e[0m")
63
+ expect(stack).to eq([["\e[33m", 3]])
64
+ end
65
+ end
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Strings::Wrap, '.wrap' do
4
4
  context 'when unicode' do
@@ -16,6 +16,18 @@ RSpec.describe Strings::Wrap, '.wrap' do
16
16
  expect(Strings::Wrap.wrap(text, 100)).to eq(text)
17
17
  end
18
18
 
19
+ it "wraps minimal width" do
20
+ str = "#?%"
21
+
22
+ val = Strings::Wrap.wrap(str, 1)
23
+
24
+ expect(val).to eq([
25
+ "#",
26
+ "?",
27
+ "%"
28
+ ].join("\n"))
29
+ end
30
+
19
31
  it "wraps correctly unbreakable words" do
20
32
  expect(Strings::Wrap.wrap('foobar1', 3)).to eq([
21
33
  "foo",
@@ -34,7 +46,7 @@ RSpec.describe Strings::Wrap, '.wrap' do
34
46
  " ",
35
47
  "conte",
36
48
  "nt "
37
- ].join("\n"))
49
+ ].join("\r\n"))
38
50
  end
39
51
 
40
52
  it "preserves newlines" do
@@ -113,6 +125,11 @@ RSpec.describe Strings::Wrap, '.wrap' do
113
125
  "も含み\n"
114
126
  ].join("\n"))
115
127
  end
128
+
129
+ it "handles \r\n line separator" do
130
+ text = "Closes #360\r\n\r\nCloses !217"
131
+ expect(Strings::Wrap.wrap(text, 15)).to eq(text)
132
+ end
116
133
  end
117
134
 
118
135
  context 'with ANSI codes' do
@@ -151,5 +168,40 @@ RSpec.describe Strings::Wrap, '.wrap' do
151
168
  "insulted me."
152
169
  ].join("\n"))
153
170
  end
171
+
172
+ it "applies ANSI codes when below wrap width" do
173
+ str = "\e[32mone\e[0m\e[33mtwo\e[0m"
174
+
175
+ val = Strings::Wrap.wrap(str, 6)
176
+
177
+ expect(val).to eq("\e[32mone\e[0m\e[33mtwo\e[0m")
178
+ end
179
+
180
+ xit "splits ANSI codes matching wrap width with space between codes" do
181
+ str = "\e[32mone\e[0m \e[33mtwo\e[0m"
182
+
183
+ val = Strings::Wrap.wrap(str, 3)
184
+
185
+ expect(val).to eq("\e[32mone\e[0m\n\e[33mtwo\e[0m")
186
+ end
187
+
188
+ xit "splits ANSI codes matching wrap width" do
189
+ str = "\e[32mone\e[0m\e[33mtwo\e[0m"
190
+
191
+ val = Strings::Wrap.wrap(str, 3)
192
+
193
+ expect(val).to eq("\e[32mone\e[0m\n\e[33mtwo\e[0m")
194
+ end
195
+
196
+ xit "wraps deeply nested ANSI codes correctly" do
197
+ str = "\e[32mone\e[33mtwo\e[0m\e[0m"
198
+
199
+ val = Strings::Wrap.wrap(str, 3)
200
+
201
+ expect(val).to eq([
202
+ "\e[32mone\e[0m",
203
+ "\e[33mtwo\e[0m",
204
+ ].join("\n"))
205
+ end
154
206
  end
155
207
  end
@@ -1,10 +1,8 @@
1
- # encoding: utf-8
2
-
3
- desc 'Load gem inside irb console'
1
+ desc "Load gem inside irb console"
4
2
  task :console do
5
- require 'irb'
6
- require 'irb/completion'
7
- require File.join(__FILE__, '../../lib/tty-reader')
3
+ require "irb"
4
+ require "irb/completion"
5
+ require File.join(__FILE__, "../../lib/strings")
8
6
  ARGV.clear
9
7
  IRB.start
10
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-29 00:00:00.000000000 Z
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strings-ansi
@@ -132,6 +132,7 @@ files:
132
132
  - spec/unit/sanitize_spec.rb
133
133
  - spec/unit/truncate/truncate_spec.rb
134
134
  - spec/unit/truncate_spec.rb
135
+ - spec/unit/wrap/insert_ansi_spec.rb
135
136
  - spec/unit/wrap/wrap_spec.rb
136
137
  - spec/unit/wrap_spec.rb
137
138
  - strings.gemspec