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