strings 0.0.0 → 0.1.0
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 +4 -4
- data/.rspec +2 -0
- data/.travis.yml +20 -3
- data/CHANGELOG.md +7 -0
- data/Gemfile +10 -1
- data/README.md +340 -5
- data/Rakefile +5 -3
- data/appveyor.yml +21 -0
- data/assets/strings_logo.png +0 -0
- data/benchmarks/speed_profile.rb +30 -0
- data/lib/strings.rb +109 -3
- data/lib/strings/align.rb +141 -0
- data/lib/strings/ansi.rb +68 -0
- data/lib/strings/fold.rb +27 -0
- data/lib/strings/pad.rb +94 -0
- data/lib/strings/padder.rb +160 -0
- data/lib/strings/truncate.rb +107 -0
- data/lib/strings/version.rb +1 -1
- data/lib/strings/wrap.rb +165 -0
- data/strings.gemspec +5 -2
- data/tasks/console.rake +11 -0
- data/tasks/coverage.rake +11 -0
- data/tasks/spec.rake +29 -0
- metadata +47 -4
data/appveyor.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
install:
|
3
|
+
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
|
4
|
+
- ruby --version
|
5
|
+
- gem --version
|
6
|
+
- bundle install
|
7
|
+
build: off
|
8
|
+
test_script:
|
9
|
+
- bundle exec rake ci
|
10
|
+
environment:
|
11
|
+
matrix:
|
12
|
+
- ruby_version: "200"
|
13
|
+
- ruby_version: "200-x64"
|
14
|
+
- ruby_version: "21"
|
15
|
+
- ruby_version: "21-x64"
|
16
|
+
- ruby_version: "22"
|
17
|
+
- ruby_version: "22-x64"
|
18
|
+
- ruby_version: "23"
|
19
|
+
- ruby_version: "23-x64"
|
20
|
+
- ruby_version: "24"
|
21
|
+
- ruby_version: "24-x64"
|
Binary file
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'strings'
|
3
|
+
|
4
|
+
text = "Ignorance is the parent of fear."
|
5
|
+
|
6
|
+
Benchmark.ips do |x|
|
7
|
+
x.report('wrap') do
|
8
|
+
Strings::Wrap.wrap(text, 10)
|
9
|
+
end
|
10
|
+
|
11
|
+
x.report('truncate') do
|
12
|
+
Strings::Truncate.truncate(text, 10)
|
13
|
+
end
|
14
|
+
|
15
|
+
x.compare!
|
16
|
+
end
|
17
|
+
|
18
|
+
# (2017-12-09)
|
19
|
+
#
|
20
|
+
# Calculating -------------------------------------
|
21
|
+
# wrap 186 i/100ms
|
22
|
+
# truncate 295 i/100ms
|
23
|
+
# -------------------------------------------------
|
24
|
+
# wrap 1917.2 (±2.2%) i/s - 9672 in 5.047297s
|
25
|
+
# truncate 3020.6 (±3.0%) i/s - 15340 in 5.083516s
|
26
|
+
#
|
27
|
+
# Comparison:
|
28
|
+
# truncate: 3020.6 i/s
|
29
|
+
# wrap: 1917.2 i/s - 1.58x slower
|
30
|
+
#
|
data/lib/strings.rb
CHANGED
@@ -1,5 +1,111 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'strings/align'
|
4
|
+
require_relative 'strings/ansi'
|
5
|
+
require_relative 'strings/fold'
|
6
|
+
require_relative 'strings/pad'
|
7
|
+
require_relative 'strings/truncate'
|
8
|
+
require_relative 'strings/wrap'
|
9
|
+
require_relative 'strings/version'
|
2
10
|
|
3
11
|
module Strings
|
4
|
-
#
|
5
|
-
|
12
|
+
# Align text within the width.
|
13
|
+
#
|
14
|
+
# @see Strings::Align#align
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
def align(*args)
|
18
|
+
Align.align(*args)
|
19
|
+
end
|
20
|
+
module_function :align
|
21
|
+
|
22
|
+
# Align text left within the width.
|
23
|
+
#
|
24
|
+
# @see Strings::Align#align_left
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def align_left(*args)
|
28
|
+
Align.align_left(*args)
|
29
|
+
end
|
30
|
+
module_function :align_left
|
31
|
+
|
32
|
+
# Align text with the width.
|
33
|
+
#
|
34
|
+
# @see Strings::Align#align
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def align_center(*args)
|
38
|
+
Align.align_center(*args)
|
39
|
+
end
|
40
|
+
module_function :align_center
|
41
|
+
|
42
|
+
# Align text with the width.
|
43
|
+
#
|
44
|
+
# @see Strings::Align#align
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def align_right(*args)
|
48
|
+
Align.align_right(*args)
|
49
|
+
end
|
50
|
+
module_function :align_right
|
51
|
+
|
52
|
+
# Check if string contains ANSI codes
|
53
|
+
#
|
54
|
+
# @see Strings::ANSI#ansi?
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def ansi?(string)
|
58
|
+
ANSI.ansi?(string)
|
59
|
+
end
|
60
|
+
module_function :ansi?
|
61
|
+
|
62
|
+
# Remove any line break characters from the text
|
63
|
+
#
|
64
|
+
# @see Strings::Fold#fold
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def fold(*args)
|
68
|
+
Fold.fold(*args)
|
69
|
+
end
|
70
|
+
module_function :fold
|
71
|
+
|
72
|
+
# Apply padding to multiline text with ANSI codes
|
73
|
+
#
|
74
|
+
# @see Strings::Pad#pad
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def pad(*args)
|
78
|
+
Pad.pad(*args)
|
79
|
+
end
|
80
|
+
module_function :pad
|
81
|
+
|
82
|
+
# Remove ANSI codes from the string
|
83
|
+
#
|
84
|
+
# @see Strings::ANSI#sanitize
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def sanitize(text)
|
88
|
+
ANSI.sanitize(text)
|
89
|
+
end
|
90
|
+
module_function :sanitize
|
91
|
+
|
92
|
+
# Truncate a text at a given length
|
93
|
+
#
|
94
|
+
# @see Strings::Truncate#truncate
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
def truncate(text, truncate_at, options = {})
|
98
|
+
Truncate.truncate(text, truncate_at, options)
|
99
|
+
end
|
100
|
+
module_function :truncate
|
101
|
+
|
102
|
+
# Wrap a text into lines at wrap length
|
103
|
+
#
|
104
|
+
# @see Strings::Wrap#wrap
|
105
|
+
#
|
106
|
+
# @api public
|
107
|
+
def wrap(text, wrap_at)
|
108
|
+
Wrap.wrap(text, wrap_at)
|
109
|
+
end
|
110
|
+
module_function :wrap
|
111
|
+
end # Strings
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'unicode/display_width'
|
4
|
+
|
5
|
+
require_relative 'ansi'
|
6
|
+
|
7
|
+
module Strings
|
8
|
+
# Responsible for text alignment
|
9
|
+
module Align
|
10
|
+
NEWLINE = "\n".freeze
|
11
|
+
|
12
|
+
SPACE = ' '.freeze
|
13
|
+
|
14
|
+
# Aligns text within the width.
|
15
|
+
#
|
16
|
+
# If the text is greater than the width then unmodified
|
17
|
+
# string is returned.
|
18
|
+
#
|
19
|
+
# @param [String] text
|
20
|
+
# the text to align lines of
|
21
|
+
# @param [Integer] width
|
22
|
+
# the maximum width to align to
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# text = "the madness of men"
|
26
|
+
#
|
27
|
+
# Strings::Align.align(22, :left)
|
28
|
+
# # => "the madness of men "
|
29
|
+
#
|
30
|
+
# Strings::Align.align(22, :center)
|
31
|
+
# # => " the madness of men "
|
32
|
+
#
|
33
|
+
# Strings::Align(22, :right)
|
34
|
+
# # => " the madness of men"
|
35
|
+
#
|
36
|
+
# Strings::Align.align(22, :center, fill: '*)
|
37
|
+
# # => "***the madness of men***"
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def align(text, width, direction: :left, **options)
|
41
|
+
return text if width.nil?
|
42
|
+
method = to_alignment(direction)
|
43
|
+
send(method, text, width, options)
|
44
|
+
end
|
45
|
+
module_function :align
|
46
|
+
|
47
|
+
# Convert direction to method name
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
def to_alignment(direction)
|
51
|
+
case direction.to_sym
|
52
|
+
when :left then :align_left
|
53
|
+
when :right then :align_right
|
54
|
+
when :center then :align_center
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Unknown alignment `#{direction}`."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
module_function :to_alignment
|
60
|
+
|
61
|
+
# Aligns text to the left at given length
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def align_left(text, width, fill: SPACE, separator: NEWLINE)
|
67
|
+
return if width.nil?
|
68
|
+
each_line(text, separator) do |line|
|
69
|
+
width_diff = width - display_width(line)
|
70
|
+
if width_diff > 0
|
71
|
+
line + fill * width_diff
|
72
|
+
else
|
73
|
+
line
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
module_function :align_left
|
78
|
+
|
79
|
+
# Centers text within the width
|
80
|
+
#
|
81
|
+
# @return [String]
|
82
|
+
#
|
83
|
+
# @api public
|
84
|
+
def align_center(text, width, fill: SPACE, separator: NEWLINE)
|
85
|
+
return text if width.nil?
|
86
|
+
each_line(text, separator) do |line|
|
87
|
+
width_diff = width - display_width(line)
|
88
|
+
if width_diff > 0
|
89
|
+
right_count = (width_diff.to_f / 2).ceil
|
90
|
+
left_count = width_diff - right_count
|
91
|
+
[fill * left_count, line, fill * right_count].join
|
92
|
+
else
|
93
|
+
text
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
module_function :align_center
|
98
|
+
|
99
|
+
# Aligns text to the right at given length
|
100
|
+
#
|
101
|
+
# @return [String]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def align_right(text, width, fill: SPACE, separator: NEWLINE)
|
105
|
+
return text if width.nil?
|
106
|
+
each_line(text, separator) do |line|
|
107
|
+
width_diff = width - display_width(line)
|
108
|
+
if width_diff > 0
|
109
|
+
fill * width_diff + line
|
110
|
+
else
|
111
|
+
line
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
module_function :align_right
|
116
|
+
|
117
|
+
# Enumerate text line by line
|
118
|
+
#
|
119
|
+
# @param [String] text
|
120
|
+
#
|
121
|
+
# @return [String]
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
def each_line(text, separator)
|
125
|
+
lines = text.split(separator)
|
126
|
+
return yield(text) if text.empty?
|
127
|
+
lines.reduce([]) do |aligned, line|
|
128
|
+
aligned << yield(line)
|
129
|
+
end.join(separator)
|
130
|
+
end
|
131
|
+
module_function :each_line
|
132
|
+
|
133
|
+
# Visible width of a string
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
def display_width(string)
|
137
|
+
Unicode::DisplayWidth.of(Strings::ANSI.sanitize(string))
|
138
|
+
end
|
139
|
+
module_function :display_width
|
140
|
+
end # Align
|
141
|
+
end # Strings
|
data/lib/strings/ansi.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strings
|
4
|
+
# Helper functions for handling ANSI escape sequences
|
5
|
+
module ANSI
|
6
|
+
# The control sequence indicator
|
7
|
+
CSI = "\033".freeze
|
8
|
+
|
9
|
+
# The code for reseting styling
|
10
|
+
RESET = "\e[0m".freeze
|
11
|
+
|
12
|
+
# The regex to match ANSI codes
|
13
|
+
ANSI_MATCHER = '(\[)?\033(\[)?[;?\d]*[\dA-Za-z]([\];])?'.freeze
|
14
|
+
|
15
|
+
# Remove ANSI characters from the text
|
16
|
+
#
|
17
|
+
# @param [String] text
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# Strings::ANSI.sanitize("\e[33mfoo\[e0m")
|
21
|
+
# # => "foo"
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def sanitize(text)
|
27
|
+
text.gsub(/#{ANSI_MATCHER}/, '')
|
28
|
+
end
|
29
|
+
module_function :sanitize
|
30
|
+
|
31
|
+
# Check if string contains ANSI codes
|
32
|
+
#
|
33
|
+
# @param [String] string
|
34
|
+
# the string to check
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# Strings::ANSI.ansi?("\e[33mfoo\[e0m")
|
38
|
+
# # => true
|
39
|
+
#
|
40
|
+
# @return [Boolean]
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def ansi?(string)
|
44
|
+
!!(string =~ /#{ANSI_MATCHER}/)
|
45
|
+
end
|
46
|
+
module_function :ansi?
|
47
|
+
|
48
|
+
# Check if string contains only ANSI codes
|
49
|
+
#
|
50
|
+
# @param [String] string
|
51
|
+
# the string to check
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# Strings::ANSI.only_ansi?("\e[33mfoo\[e0m")
|
55
|
+
# # => false
|
56
|
+
#
|
57
|
+
# Strings::ANSI.only_ansi?("\e[33m")
|
58
|
+
# # => false
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def only_ansi?(string)
|
64
|
+
!!(string =~ /^#{ANSI_MATCHER}$/)
|
65
|
+
end
|
66
|
+
module_function :only_ansi?
|
67
|
+
end # Sanitizer
|
68
|
+
end # Strings
|
data/lib/strings/fold.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strings
|
4
|
+
module Fold
|
5
|
+
LINE_BREAK = "(\r\n+|\r+|\n+|\t+)".freeze
|
6
|
+
|
7
|
+
# Fold a multiline text into a single line string
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# fold("\tfoo \r\n\n bar") # => "foo bar"
|
11
|
+
#
|
12
|
+
# @param [String] text
|
13
|
+
#
|
14
|
+
# @param [String] separator
|
15
|
+
# the separators to be removed from the text, default: (\r\n+|\r+|\n+|\t+)
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def fold(text, separator = LINE_BREAK)
|
21
|
+
text.gsub(/([ ]+)#{separator}/, "\\1")
|
22
|
+
.gsub(/#{separator}(?<space>[ ]+)/, "\\k<space>")
|
23
|
+
.gsub(/#{separator}/, ' ')
|
24
|
+
end
|
25
|
+
module_function :fold
|
26
|
+
end # Fold
|
27
|
+
end # Strings
|
data/lib/strings/pad.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'unicode/display_width'
|
4
|
+
|
5
|
+
require_relative 'ansi'
|
6
|
+
require_relative 'padder'
|
7
|
+
|
8
|
+
module Strings
|
9
|
+
# Responsible for text padding
|
10
|
+
module Pad
|
11
|
+
NEWLINE = "\n".freeze
|
12
|
+
|
13
|
+
SPACE = ' '.freeze
|
14
|
+
|
15
|
+
# Apply padding to multiline text with ANSI codes
|
16
|
+
#
|
17
|
+
# @param [String] text
|
18
|
+
# the text to pad out
|
19
|
+
# @param [Integer, Array[Integer]] padding
|
20
|
+
# the padding to apply to text
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# text = "Ignorance is the parent of fear."
|
24
|
+
#
|
25
|
+
# Strings::Pad.pad(text, [1, 2], fill: "*")
|
26
|
+
# # =>
|
27
|
+
# # "************************************\n"
|
28
|
+
# # "**Ignorance is the parent of fear.**\n"
|
29
|
+
# # "************************************\n"
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def pad(text, padding, fill: SPACE, separator: NEWLINE)
|
35
|
+
padding = Strings::Padder.parse(padding)
|
36
|
+
text_copy = text.dup
|
37
|
+
line_width = max_line_length(text, separator)
|
38
|
+
output = []
|
39
|
+
|
40
|
+
filler_line = fill * line_width
|
41
|
+
|
42
|
+
padding.top.times do
|
43
|
+
output << pad_around(filler_line, padding, fill: fill)
|
44
|
+
end
|
45
|
+
|
46
|
+
text_copy.split(separator).each do |line|
|
47
|
+
output << pad_around(line, padding, fill: fill)
|
48
|
+
end
|
49
|
+
|
50
|
+
padding.bottom.times do
|
51
|
+
output << pad_around(filler_line, padding, fill: fill)
|
52
|
+
end
|
53
|
+
|
54
|
+
output.join(separator)
|
55
|
+
end
|
56
|
+
module_function :pad
|
57
|
+
|
58
|
+
# Apply padding to left and right side of string
|
59
|
+
#
|
60
|
+
# @param [String] text
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def pad_around(text, padding, fill: SPACE)
|
66
|
+
fill * padding.left + text + fill * padding.right
|
67
|
+
end
|
68
|
+
module_function :pad_around
|
69
|
+
|
70
|
+
# Determine maximum length for all multiline content
|
71
|
+
#
|
72
|
+
# @param [String] text
|
73
|
+
# @param [String] separator
|
74
|
+
#
|
75
|
+
# @return [Integer]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def max_line_length(text, separator)
|
79
|
+
lines = text.split(separator, -1)
|
80
|
+
display_width(lines.max_by { |line| display_width(line) } || '')
|
81
|
+
end
|
82
|
+
module_function :max_line_length
|
83
|
+
|
84
|
+
# Calculate visible string width
|
85
|
+
#
|
86
|
+
# @return [Integer]
|
87
|
+
#
|
88
|
+
# @api private
|
89
|
+
def display_width(string)
|
90
|
+
Unicode::DisplayWidth.of(Strings::ANSI.sanitize(string))
|
91
|
+
end
|
92
|
+
module_function :display_width
|
93
|
+
end # Padding
|
94
|
+
end # Strings
|