strings 0.1.5 → 0.2.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.
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'strings-ansi'
4
- require 'unicode/display_width'
5
- require 'unicode_utils/each_grapheme'
3
+ require "strings-ansi"
4
+ require "unicode/display_width"
5
+ require "unicode_utils/each_grapheme"
6
6
 
7
7
  module Strings
8
8
  # A module responsible for text truncation
9
9
  module Truncate
10
- DEFAULT_TRAILING = ''.freeze
10
+ DEFAULT_TRAILING = "".freeze
11
11
 
12
12
  DEFAULT_LENGTH = 30
13
13
 
@@ -27,15 +27,15 @@ module Strings
27
27
  # text = "The sovereignest thing on earth is parmacetti for an inward bruise."
28
28
  #
29
29
  # Strings::Truncate.truncate(text)
30
- # # => "The sovereignest thing on ear…"
30
+ # # => "The sovereignest thing on ea…"
31
31
  #
32
32
  # Strings::Truncate.truncate(text, 20)
33
- # # => "The sovereignest th…"
33
+ # # => "The sovereignest t…"
34
34
  #
35
- # Strings::Truncate.truncate(text, 20, separator: ' ' )
35
+ # Strings::Truncate.truncate(text, 20, separator: " " )
36
36
  # # => "The sovereignest…"
37
37
  #
38
- # Strings::Truncate.truncate(40, trailing: '... (see more)' )
38
+ # Strings::Truncate.truncate(text, 40, trailing: "... (see more)" )
39
39
  # # => "The sovereignest thing on... (see more)"
40
40
  #
41
41
  # @api public
@@ -70,6 +70,7 @@ module Strings
70
70
  # @api private
71
71
  def shorten(original_chars, chars, length_without_trailing)
72
72
  truncated = []
73
+ return truncated if length_without_trailing.zero?
73
74
  char_width = display_width(chars[0])
74
75
  while length_without_trailing - char_width > 0
75
76
  orig_char = original_chars.shift
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Strings
4
- VERSION = '0.1.5'
4
+ VERSION = "0.2.1"
5
5
  end # Strings
data/lib/strings/wrap.rb CHANGED
@@ -1,55 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'strings-ansi'
4
- require 'unicode/display_width'
5
- require 'unicode_utils/each_grapheme'
6
-
7
- require_relative 'fold'
3
+ require "strings-ansi"
4
+ require "unicode/display_width"
5
+ require "unicode_utils/each_grapheme"
8
6
 
9
7
  module Strings
10
8
  module Wrap
11
9
  DEFAULT_WIDTH = 80
12
-
13
- NEWLINE = "\n".freeze
14
-
15
- SPACE = ' '.freeze
16
-
17
- LINE_BREAK = "\r\n+|\r+|\n+".freeze
10
+ NEWLINE = "\n"
11
+ SPACE = " "
12
+ LINE_BREAK = %r{\r\n|\r|\n}.freeze
13
+ LINE_BREAKS = "\r\n+|\r+|\n+"
18
14
 
19
15
  # Wrap a text into lines no longer than wrap_at length.
20
16
  # Preserves existing lines and existing word boundaries.
21
17
  #
22
18
  # @example
23
19
  # Strings::Wrap.wrap("Some longish text", 8)
24
- # # => >Some
25
- # >longish
26
- # >text
20
+ # # => "Some \nlongish \ntext"
27
21
  #
28
22
  # @api public
29
- def wrap(text, wrap_at = DEFAULT_WIDTH)
30
- if text.length < wrap_at.to_i || wrap_at.to_i.zero?
23
+ def wrap(text, wrap_at = DEFAULT_WIDTH, separator: NEWLINE)
24
+ if text.scan(/[[:print:]]/).length < wrap_at.to_i || wrap_at.to_i.zero?
31
25
  return text
32
26
  end
27
+
33
28
  ansi_stack = []
34
- text.split(%r{#{LINE_BREAK}}, -1).map do |paragraph|
35
- format_paragraph(paragraph, wrap_at, ansi_stack)
36
- end * NEWLINE
29
+ text.lines.map do |line|
30
+ format_line(line, wrap_at, ansi_stack).join(separator)
31
+ end.join
37
32
  end
38
33
  module_function :wrap
39
34
 
40
- # Format paragraph to be maximum of wrap_at length
35
+ # Format line to be maximum of wrap_at length
41
36
  #
42
- # @param [String] paragraph
43
- # the paragraph to format
37
+ # @param [String] text_line
38
+ # the line to format
44
39
  # @param [Integer] wrap_at
45
- # the maximum length to wrap the paragraph
40
+ # the maximum length to wrap the line
46
41
  #
47
42
  # @return [Array[String]]
48
43
  # the wrapped lines
49
44
  #
50
45
  # @api private
51
- def format_paragraph(paragraph, wrap_at, ansi_stack)
52
- cleared_para = Fold.fold(paragraph)
46
+ def format_line(text_line, wrap_at, ansi_stack)
53
47
  lines = []
54
48
  line = []
55
49
  word = []
@@ -58,10 +52,10 @@ module Strings
58
52
  word_length = 0
59
53
  line_length = 0
60
54
  char_length = 0 # visible char length
61
- text_length = display_width(cleared_para)
55
+ text_length = display_width(text_line)
62
56
  total_length = 0
63
57
 
64
- UnicodeUtils.each_grapheme(cleared_para) do |char|
58
+ UnicodeUtils.each_grapheme(text_line) do |char|
65
59
  # we found ansi let's consume
66
60
  if char == Strings::ANSI::CSI || ansi.length > 0
67
61
  ansi << char
@@ -70,7 +64,12 @@ module Strings
70
64
  elsif ansi_matched
71
65
  ansi_stack << [ansi[0...-1].join, line_length + word_length]
72
66
  ansi_matched = false
73
- ansi = []
67
+
68
+ if ansi.last == Strings::ANSI::CSI
69
+ ansi = [ansi.last]
70
+ else
71
+ ansi = []
72
+ end
74
73
  end
75
74
  next if ansi.length > 0
76
75
  end
@@ -91,65 +90,76 @@ module Strings
91
90
  end
92
91
 
93
92
  if char == SPACE # ends with space
94
- lines << insert_ansi(ansi_stack, line.join)
93
+ lines << insert_ansi(line.join, ansi_stack)
95
94
  line = []
96
95
  line_length = 0
97
96
  word << char
98
97
  word_length += char_length
99
98
  elsif word_length + char_length <= wrap_at
100
- lines << insert_ansi(ansi_stack, line.join)
99
+ lines << insert_ansi(line.join, ansi_stack)
101
100
  line = [word.join + char]
102
101
  line_length = word_length + char_length
103
102
  word = []
104
103
  word_length = 0
105
104
  else # hyphenate word - too long to fit a line
106
- lines << insert_ansi(ansi_stack, word.join)
105
+ lines << insert_ansi(word.join, ansi_stack)
107
106
  line_length = 0
108
107
  word = [char]
109
108
  word_length = char_length
110
109
  end
111
110
  end
112
- lines << insert_ansi(ansi_stack, line.join) unless line.empty?
113
- lines << insert_ansi(ansi_stack, word.join) unless word.empty?
111
+ lines << insert_ansi(line.join, ansi_stack) unless line.empty?
112
+ lines << insert_ansi(word.join, ansi_stack) unless word.empty?
114
113
  lines
115
114
  end
116
- module_function :format_paragraph
115
+ module_function :format_line
117
116
 
118
117
  # Insert ANSI code into string
119
118
  #
120
119
  # Check if there are any ANSI states, if present
121
120
  # insert ANSI codes at given positions unwinding the stack.
122
121
  #
123
- # @param [Array[Array[String, Integer]]] ansi_stack
124
- # the ANSI codes to apply
125
- #
126
122
  # @param [String] string
127
123
  # the string to insert ANSI codes into
128
124
  #
125
+ # @param [Array[Array[String, Integer]]] ansi_stack
126
+ # the ANSI codes to apply
127
+ #
129
128
  # @return [String]
130
129
  #
131
130
  # @api private
132
- def insert_ansi(ansi_stack, string)
131
+ def insert_ansi(string, ansi_stack = [])
133
132
  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
133
+ return string if string.empty?
134
+
135
+ new_stack = []
136
+ output = string.dup
137
+ length = string.size
138
+ matched_reset = false
139
+ ansi_reset = Strings::ANSI::RESET
140
+
141
+ # Reversed so that string index don't count ansi
142
+ ansi_stack.reverse_each do |ansi|
143
+ if ansi[0] =~ /#{Regexp.quote(ansi_reset)}/
144
+ matched_reset = true
145
+ output.insert(ansi[1], ansi_reset)
143
146
  next
144
- elsif !resetting
145
- reset_index = -1
146
- resetting = false
147
+ elsif !matched_reset # ansi without reset
148
+ matched_reset = false
149
+ new_stack << ansi # keep the ansi
150
+ next if ansi[1] == length
151
+ if output.end_with?(NEWLINE)
152
+ output.insert(-2, ansi_reset)
153
+ else
154
+ output.insert(-1, ansi_reset) # add reset at the end
155
+ end
147
156
  end
148
157
 
149
- color, color_index = *state
150
- output.insert(reset_index, Strings::ANSI::RESET).insert(color_index, color)
158
+ output.insert(ansi[1], ansi[0])
151
159
  end
152
- ansi_stack.pop(to_remove) # remove used states
160
+
161
+ ansi_stack.replace(new_stack)
162
+
153
163
  output
154
164
  end
155
165
  module_function :insert_ansi
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.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-29 00:00:00.000000000 Z
11
+ date: 2021-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: strings-ansi
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.1'
19
+ version: '0.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.1'
26
+ version: '0.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: unicode_utils
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -42,30 +42,22 @@ dependencies:
42
42
  name: unicode-display_width
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '1.5'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '3.0'
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.5'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
54
  requirements:
59
55
  - - ">="
60
56
  - !ruby/object:Gem::Version
61
57
  version: '1.5'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
58
+ - - "<"
67
59
  - !ruby/object:Gem::Version
68
- version: '1.5'
60
+ version: '3.0'
69
61
  - !ruby/object:Gem::Dependency
70
62
  name: rake
71
63
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +86,20 @@ dependencies:
94
86
  - - ">="
95
87
  - !ruby/object:Gem::Version
96
88
  version: '3.0'
97
- description: A set of useful functions such as fold, truncate, wrap and more for transoforming
98
- strings.
89
+ description: A set of methods for working with strings such as align, truncate, wrap
90
+ and many more.
99
91
  email:
100
- - me@piotrmurach.com
92
+ - piotr@piotrmurach.com
101
93
  executables: []
102
94
  extensions: []
103
- extra_rdoc_files: []
95
+ extra_rdoc_files:
96
+ - README.md
97
+ - CHANGELOG.md
98
+ - LICENSE.txt
104
99
  files:
105
100
  - CHANGELOG.md
106
101
  - LICENSE.txt
107
102
  - README.md
108
- - Rakefile
109
- - bin/console
110
- - bin/setup
111
103
  - lib/strings.rb
112
104
  - lib/strings/align.rb
113
105
  - lib/strings/extensions.rb
@@ -117,31 +109,15 @@ files:
117
109
  - lib/strings/truncate.rb
118
110
  - lib/strings/version.rb
119
111
  - lib/strings/wrap.rb
120
- - spec/spec_helper.rb
121
- - spec/unit/align/align_left_spec.rb
122
- - spec/unit/align/align_right_spec.rb
123
- - spec/unit/align/align_spec.rb
124
- - spec/unit/align_spec.rb
125
- - spec/unit/ansi_spec.rb
126
- - spec/unit/extensions_spec.rb
127
- - spec/unit/fold/fold_spec.rb
128
- - spec/unit/fold_spec.rb
129
- - spec/unit/pad/pad_spec.rb
130
- - spec/unit/pad_spec.rb
131
- - spec/unit/padder/parse_spec.rb
132
- - spec/unit/sanitize_spec.rb
133
- - spec/unit/truncate/truncate_spec.rb
134
- - spec/unit/truncate_spec.rb
135
- - spec/unit/wrap/wrap_spec.rb
136
- - spec/unit/wrap_spec.rb
137
- - strings.gemspec
138
- - tasks/console.rake
139
- - tasks/coverage.rake
140
- - tasks/spec.rake
141
- homepage: ''
112
+ homepage: https://github.com/piotrmurach/strings
142
113
  licenses:
143
114
  - MIT
144
- metadata: {}
115
+ metadata:
116
+ allowed_push_host: https://rubygems.org
117
+ changelog_uri: https://github.com/piotrmurach/strings/blob/master/CHANGELOG.md
118
+ documentation_uri: https://www.rubydoc.info/gems/strings
119
+ homepage_uri: https://github.com/piotrmurach/strings
120
+ source_code_uri: https://github.com/piotrmurach/strings
145
121
  post_install_message:
146
122
  rdoc_options: []
147
123
  require_paths:
@@ -150,15 +126,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
126
  requirements:
151
127
  - - ">="
152
128
  - !ruby/object:Gem::Version
153
- version: '0'
129
+ version: 2.0.0
154
130
  required_rubygems_version: !ruby/object:Gem::Requirement
155
131
  requirements:
156
132
  - - ">="
157
133
  - !ruby/object:Gem::Version
158
134
  version: '0'
159
135
  requirements: []
160
- rubygems_version: 3.0.3
136
+ rubygems_version: 3.1.2
161
137
  signing_key:
162
138
  specification_version: 4
163
- summary: A set of useful functions for transforming strings.
139
+ summary: A set of methods for working with strings.
164
140
  test_files: []
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
3
- FileList['tasks/**/*.rake'].each(&method(:import))
4
-
5
- desc 'Run all specs'
6
- task ci: %w[ spec ]
7
-
8
- task default: :spec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "strings"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/spec/spec_helper.rb DELETED
@@ -1,37 +0,0 @@
1
- if ENV['COVERAGE'] || ENV['TRAVIS']
2
- require 'simplecov'
3
- require 'coveralls'
4
-
5
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
- SimpleCov::Formatter::HTMLFormatter,
7
- Coveralls::SimpleCov::Formatter
8
- ]
9
-
10
- SimpleCov.start do
11
- command_name 'spec'
12
- add_filter 'spec'
13
- end
14
- end
15
-
16
- require "bundler/setup"
17
- require "strings"
18
-
19
- module Helpers
20
- def unindent(text)
21
- text.gsub(/^[ \t]*/, '').chomp
22
- end
23
- end
24
-
25
- RSpec.configure do |config|
26
- config.include(Helpers)
27
-
28
- # Enable flags like --only-failures and --next-failure
29
- config.example_status_persistence_file_path = ".rspec_status"
30
-
31
- # Disable RSpec exposing methods globally on `Module` and `main`
32
- config.disable_monkey_patching!
33
-
34
- config.expect_with :rspec do |c|
35
- c.syntax = :expect
36
- end
37
- end