strings 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ #
@@ -1,5 +1,111 @@
1
- require "strings/version"
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
- # Your code goes here...
5
- end
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
@@ -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
@@ -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
@@ -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