monospace_text_formatter 0.0.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.
- data/Changelog +2 -0
- data/LICENSE +20 -0
- data/README.md +173 -0
- data/lib/monospace_text_formatter/atomic_chunk.rb +40 -0
- data/lib/monospace_text_formatter/atomic_chunk_factory.rb +31 -0
- data/lib/monospace_text_formatter/box.rb +158 -0
- data/lib/monospace_text_formatter/chunk.rb +174 -0
- data/lib/monospace_text_formatter/line.rb +100 -0
- data/lib/monospace_text_formatter/version.rb +3 -0
- data/lib/monospace_text_formatter.rb +10 -0
- metadata +66 -0
data/Changelog
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Jacek Mikrut
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
MonospaceTextFormatter
|
2
|
+
======================
|
3
|
+
|
4
|
+
MonospaceTextFormatter is a Ruby Gem that formats monospaced text into a **line or visual box** of defined **width** and **height** boundaries (expressed in number of characters), by wrapping and/or truncating the text, and filling remaining area with spaces (or custom characters) so that it forms a rectangular block. It **doesn't split words** unless they are longer than the width boundary. It also applies **horizontal** and **vertical alignment** to the text. All these attributes are customizable and optional.
|
5
|
+
|
6
|
+
* **MonospaceTextFormatter::Line** class formats a single-line text;
|
7
|
+
|
8
|
+
* **MonospaceTextFormatter::Box** class formats multiple-line text.
|
9
|
+
|
10
|
+
Usage examples
|
11
|
+
--------------
|
12
|
+
|
13
|
+
Note: All the following attributes can also be provided in Hash as the second argument for the constructor.
|
14
|
+
|
15
|
+
### MonospaceTextFormatter::Line
|
16
|
+
|
17
|
+
The `line` object for the following examples:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
line = MonospaceTextFormatter::Line.new("This is some text.")
|
21
|
+
```
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
line.to_s
|
25
|
+
=> "This is some text."
|
26
|
+
```
|
27
|
+
|
28
|
+
* `width` (a number of characters)
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
line.width = 20
|
32
|
+
line.to_s
|
33
|
+
=> "This is some text. "
|
34
|
+
|
35
|
+
line.width = 16
|
36
|
+
line.to_s
|
37
|
+
=> "This is some ..."
|
38
|
+
```
|
39
|
+
|
40
|
+
* `omission`
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
line.omission = " [...]"
|
44
|
+
line.to_s
|
45
|
+
=> "This is [...] "
|
46
|
+
```
|
47
|
+
|
48
|
+
* `align` (available options: `:left` _(default)_, `:center` and `:right`)
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
line.align = :right
|
52
|
+
line.to_s
|
53
|
+
=> " This is [...]"
|
54
|
+
```
|
55
|
+
|
56
|
+
* `fill`
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
line.fill = "-"
|
60
|
+
line.to_s
|
61
|
+
=> "---This is [...]"
|
62
|
+
```
|
63
|
+
|
64
|
+
### MonospaceTextFormatter::Box
|
65
|
+
|
66
|
+
The `box` object for the following examples:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
box = MonospaceTextFormatter::Box.new("First line.\nAnd second, a little bit longer line.")
|
70
|
+
```
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
box.to_s
|
74
|
+
=> "First line. \nAnd second, a little bit longer line."
|
75
|
+
|
76
|
+
box.lines
|
77
|
+
=> ["First line. ",
|
78
|
+
"And second, a little bit longer line."]
|
79
|
+
```
|
80
|
+
|
81
|
+
* `width` (a number of characters)
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
box.width = 20
|
85
|
+
box.lines
|
86
|
+
=> ["First line. ",
|
87
|
+
"And second, a little",
|
88
|
+
"bit longer line. "]
|
89
|
+
```
|
90
|
+
|
91
|
+
* `height` (a number of lines)
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
box.height = 5
|
95
|
+
box.lines
|
96
|
+
=> ["First line. ",
|
97
|
+
"And second, a little",
|
98
|
+
"bit longer line. ",
|
99
|
+
" ",
|
100
|
+
" "]
|
101
|
+
|
102
|
+
box.height = 2
|
103
|
+
box.lines
|
104
|
+
=> ["First line. ",
|
105
|
+
"And second, a ... "]
|
106
|
+
```
|
107
|
+
|
108
|
+
* `omission`
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
box.omission = " [...]"
|
112
|
+
box.lines
|
113
|
+
=> ["First line. ",
|
114
|
+
"And second, a [...] "]
|
115
|
+
```
|
116
|
+
|
117
|
+
* `align` (available options: `:left` _(default)_, `:center` and `:right`)
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
box.align = :center
|
121
|
+
box.lines
|
122
|
+
=> [" First line. ",
|
123
|
+
"And second, a [...] "]
|
124
|
+
```
|
125
|
+
|
126
|
+
* `valign` (available options: `:top` _(default)_, `:middle` and `:bottom`)
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
box.height = 5
|
130
|
+
box.valign = :bottom
|
131
|
+
box.lines
|
132
|
+
=> [" ",
|
133
|
+
" ",
|
134
|
+
" First line. ",
|
135
|
+
"And second, a little",
|
136
|
+
" bit longer line. "]
|
137
|
+
```
|
138
|
+
|
139
|
+
* `fill`
|
140
|
+
|
141
|
+
``` ruby
|
142
|
+
box.fill = "#"
|
143
|
+
box.lines
|
144
|
+
=> ["####################",
|
145
|
+
"####################",
|
146
|
+
"####First line.#####",
|
147
|
+
"And second, a little",
|
148
|
+
"##bit longer line.##"]
|
149
|
+
```
|
150
|
+
|
151
|
+
More can be found in **RSpec examples**.
|
152
|
+
|
153
|
+
Installation
|
154
|
+
------------
|
155
|
+
|
156
|
+
As a Ruby Gem, MonospaceTextFormatter can be installed either by running
|
157
|
+
|
158
|
+
```bash
|
159
|
+
gem install monospace_text_formatter
|
160
|
+
```
|
161
|
+
|
162
|
+
or adding
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
gem "monospace_text_formatter"
|
166
|
+
```
|
167
|
+
|
168
|
+
to the Gemfile and then invoking `bundle install`.
|
169
|
+
|
170
|
+
License
|
171
|
+
-------
|
172
|
+
|
173
|
+
License is included in the LICENSE file.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MonospaceTextFormatter
|
2
|
+
class AtomicChunk
|
3
|
+
|
4
|
+
def initialize(string)
|
5
|
+
@string = string
|
6
|
+
end
|
7
|
+
|
8
|
+
def display_string
|
9
|
+
@display_string ||= @string
|
10
|
+
end
|
11
|
+
|
12
|
+
def display_length
|
13
|
+
@display_length ||= display_string.length
|
14
|
+
end
|
15
|
+
|
16
|
+
def newline?
|
17
|
+
@string == "\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
def blank?
|
21
|
+
@string.strip.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def empty?
|
25
|
+
@string.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@string
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
%Q(#<#{self.class} #{to_s.inspect}>)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
to_s == other.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MonospaceTextFormatter
|
2
|
+
class AtomicChunkFactory
|
3
|
+
|
4
|
+
REGEXP = /^(?:\n|[ \t]+|[^ \t\n]+)/
|
5
|
+
|
6
|
+
def new(string)
|
7
|
+
AtomicChunk.new(string)
|
8
|
+
end
|
9
|
+
|
10
|
+
def slice_from!(string)
|
11
|
+
(slice = string.slice!(regexp)) ? new(slice) : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def slice_from(string)
|
15
|
+
slice_from!(string.dup)
|
16
|
+
end
|
17
|
+
|
18
|
+
def split_string(string, length)
|
19
|
+
duplicated_string = string.dup
|
20
|
+
head_chunk = new(duplicated_string.slice!(0, length))
|
21
|
+
tail_chunk = new(duplicated_string)
|
22
|
+
[ head_chunk, tail_chunk ]
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def regexp
|
28
|
+
REGEXP
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module MonospaceTextFormatter
|
2
|
+
class Box
|
3
|
+
|
4
|
+
def initialize(string_or_chunk, attrs={})
|
5
|
+
@omission = string_to_chunk(" ...")
|
6
|
+
@align = :left
|
7
|
+
@valign = :top
|
8
|
+
@fill = " "
|
9
|
+
|
10
|
+
raise ArgumentError, "No string given" unless string_or_chunk
|
11
|
+
@original_chunk = to_chunk_if_string(string_or_chunk)
|
12
|
+
|
13
|
+
attributes(attrs)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :omission, :align, :valign, :fill
|
17
|
+
|
18
|
+
def width
|
19
|
+
@fixed_width || content_lines.first && content_lines.first.width
|
20
|
+
end
|
21
|
+
|
22
|
+
def height
|
23
|
+
@fixed_height || content_lines.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def attributes(attrs={})
|
27
|
+
attrs.each { |name, value| send("#{name}=", value) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def width=(fixed_width)
|
31
|
+
return if fixed_width == @fixed_width
|
32
|
+
raise ArgumentError, "The :width must be equal or greater than 0, but is #{fixed_width}" unless fixed_width.nil? or fixed_width >= 0
|
33
|
+
|
34
|
+
@to_s = @lines = @aligned_all_lines = @all_lines = @empty_top_lines = @content_lines = @empty_bottom_lines = nil
|
35
|
+
@fixed_width = fixed_width
|
36
|
+
end
|
37
|
+
|
38
|
+
def height=(fixed_height)
|
39
|
+
return if fixed_height == @fixed_height
|
40
|
+
raise ArgumentError, "The :height must be equal or greater than 0, but is #{fixed_height}" unless fixed_height.nil? or fixed_height >= 0
|
41
|
+
|
42
|
+
@to_s = @lines = @aligned_all_lines = @all_lines = @empty_top_lines = @content_lines = @empty_bottom_lines = nil
|
43
|
+
@fixed_height = fixed_height
|
44
|
+
end
|
45
|
+
|
46
|
+
def omission=(omission)
|
47
|
+
return if omission == @omission
|
48
|
+
|
49
|
+
@to_s = @lines = @aligned_all_lines = @all_lines = @content_lines = nil if @content_lines && truncated?
|
50
|
+
@omission = to_chunk_if_string(omission)
|
51
|
+
end
|
52
|
+
|
53
|
+
def align=(align)
|
54
|
+
return if align == @align
|
55
|
+
raise ArgumentError, "The :align must be a Symbol or String with value 'left', 'center' or 'right', but is #{align.inspect}" unless [:left, :center, :right].include?(align.to_sym)
|
56
|
+
|
57
|
+
@to_s = @lines = @aligned_all_lines = nil
|
58
|
+
@align = align.to_sym
|
59
|
+
end
|
60
|
+
|
61
|
+
def valign=(valign)
|
62
|
+
return if valign == @valign
|
63
|
+
raise ArgumentError, "The :valign must be a Symbol or String with value 'top', 'middle' or 'bottom', but is #{valign.inspect}" unless [:top, :middle, :bottom].include?(valign.to_sym)
|
64
|
+
|
65
|
+
@to_s = @lines = @aligned_all_lines = @all_lines = @empty_top_lines = @empty_bottom_lines = nil
|
66
|
+
@valign = valign.to_sym
|
67
|
+
end
|
68
|
+
|
69
|
+
def fill=(fill)
|
70
|
+
return if fill == @fill
|
71
|
+
|
72
|
+
@to_s = @lines = @aligned_all_lines = @all_lines = @empty_top_lines = @empty_bottom_lines = nil
|
73
|
+
@fill = fill
|
74
|
+
end
|
75
|
+
|
76
|
+
def truncated?
|
77
|
+
content_lines.last && content_lines.last.truncated?
|
78
|
+
end
|
79
|
+
|
80
|
+
def lines
|
81
|
+
@lines ||= aligned_all_lines.map { |aligned_line| aligned_line.to_s }
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
@to_s ||= lines.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def to_chunk_if_string(string)
|
91
|
+
string.is_a?(String) ? string_to_chunk(string) : string
|
92
|
+
end
|
93
|
+
|
94
|
+
def string_to_chunk(string)
|
95
|
+
Chunk.new(string)
|
96
|
+
end
|
97
|
+
|
98
|
+
def aligned_all_lines
|
99
|
+
@aligned_all_lines ||= all_lines.each do |line|
|
100
|
+
line.attributes(:align => align, :fill => fill, :width => width)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def all_lines
|
105
|
+
@all_lines ||= empty_top_lines + content_lines + empty_bottom_lines
|
106
|
+
end
|
107
|
+
|
108
|
+
def empty_top_lines
|
109
|
+
@empty_top_lines ||= if @fixed_height && fill && !fill.empty?
|
110
|
+
case valign
|
111
|
+
when :top
|
112
|
+
[]
|
113
|
+
when :middle
|
114
|
+
Array.new(((@fixed_height - content_lines.size) / 2.0).floor, Line.new(""))
|
115
|
+
when :bottom
|
116
|
+
Array.new(@fixed_height - content_lines.size, Line.new(""))
|
117
|
+
end
|
118
|
+
else
|
119
|
+
[]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def empty_bottom_lines
|
124
|
+
@empty_bottom_lines ||= if @fixed_height && fill && !fill.empty?
|
125
|
+
case valign
|
126
|
+
when :top
|
127
|
+
Array.new(@fixed_height - content_lines.size, Line.new(""))
|
128
|
+
when :middle
|
129
|
+
Array.new(((@fixed_height - content_lines.size) / 2.0).ceil, Line.new(""))
|
130
|
+
when :bottom
|
131
|
+
[]
|
132
|
+
end
|
133
|
+
else
|
134
|
+
[]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def content_lines
|
139
|
+
return @content_lines unless @content_lines.nil?
|
140
|
+
return @content_lines = [] if @fixed_width == 0 && @fixed_height.nil?
|
141
|
+
return @content_lines = [Line.new("")] if @original_chunk.empty?
|
142
|
+
|
143
|
+
@remaining_chunk = @original_chunk.duplicate
|
144
|
+
@line_chunks = []
|
145
|
+
|
146
|
+
until (@fixed_height && @line_chunks.size == @fixed_height) || @remaining_chunk.empty?
|
147
|
+
@line_chunks << if @line_chunks.size + 1 == @fixed_height
|
148
|
+
@remaining_chunk
|
149
|
+
else
|
150
|
+
@remaining_chunk.slice!(@fixed_width ? @fixed_width : nil)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
common_width = @fixed_width || @line_chunks.map { |chunk| chunk.display_length }.max
|
155
|
+
@content_lines = @line_chunks.map { |chunk| Line.new(chunk, :width => common_width, :omission => omission) }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module MonospaceTextFormatter
|
2
|
+
class Chunk
|
3
|
+
|
4
|
+
def initialize(string=nil)
|
5
|
+
@remaining_string = ""
|
6
|
+
@atomic_chunks = []
|
7
|
+
@display_length = 0
|
8
|
+
|
9
|
+
concat(string) unless string.nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
def duplicate
|
13
|
+
duplicate = self.clone
|
14
|
+
duplicate.instance_variable_set("@remaining_string", @remaining_string.clone)
|
15
|
+
duplicate.instance_variable_set( "@atomic_chunks", @atomic_chunks.clone)
|
16
|
+
duplicate
|
17
|
+
end
|
18
|
+
|
19
|
+
def multiline?
|
20
|
+
atomic_chunks.any? { |atomic_chunk| atomic_chunk.newline? } or remaining_string.include?("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
def blank?
|
24
|
+
to_s.strip.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
to_s.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def display_length
|
32
|
+
slice_whole_remaining_string unless remaining_string.empty?
|
33
|
+
@display_length
|
34
|
+
end
|
35
|
+
|
36
|
+
def concat(string_or_chunk)
|
37
|
+
|
38
|
+
if string_or_chunk.kind_of?(Chunk)
|
39
|
+
slice_whole_remaining_string if string_or_chunk.atomic_chunks.any?
|
40
|
+
|
41
|
+
string_or_chunk.atomic_chunks.each { |atomic_chunk| push_atomic_chunk(atomic_chunk) }
|
42
|
+
remaining_string.concat(string_or_chunk.remaining_string)
|
43
|
+
|
44
|
+
else
|
45
|
+
remaining_string.concat(string_or_chunk)
|
46
|
+
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def +(string_or_chunk)
|
52
|
+
duplicate.concat(string_or_chunk)
|
53
|
+
end
|
54
|
+
|
55
|
+
def slice!(max_display_length=nil, smartly_split_too_long_words=true)
|
56
|
+
sliced_chunk = self.class.new
|
57
|
+
return sliced_chunk if max_display_length == 0
|
58
|
+
|
59
|
+
while retrieve_first_atomic_chunk
|
60
|
+
|
61
|
+
if atomic_chunks.first.blank? and sliced_chunk.blank?
|
62
|
+
shift_atomic_chunk
|
63
|
+
next
|
64
|
+
|
65
|
+
elsif max_display_length && (atomic_chunks.first.display_length + sliced_chunk.display_length > max_display_length)
|
66
|
+
break
|
67
|
+
|
68
|
+
elsif atomic_chunks.first.newline?
|
69
|
+
shift_atomic_chunk
|
70
|
+
break
|
71
|
+
|
72
|
+
else
|
73
|
+
sliced_chunk.push_atomic_chunk(shift_atomic_chunk)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
sliced_chunk.pop_atomic_chunk while sliced_chunk.atomic_chunks.last && sliced_chunk.atomic_chunks.last.blank?
|
78
|
+
|
79
|
+
if smartly_split_too_long_words &&
|
80
|
+
max_display_length &&
|
81
|
+
retrieve_first_atomic_chunk &&
|
82
|
+
atomic_chunks.first.display_length > max_display_length
|
83
|
+
|
84
|
+
smartly_split_too_long_word(max_display_length, sliced_chunk)
|
85
|
+
end
|
86
|
+
|
87
|
+
sliced_chunk
|
88
|
+
end
|
89
|
+
|
90
|
+
def slice(max_display_length)
|
91
|
+
duplicate.slice!(max_display_length)
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_s
|
95
|
+
atomic_chunks.map { |atomic_chunk| atomic_chunk.to_s }.join + remaining_string
|
96
|
+
end
|
97
|
+
|
98
|
+
def inspect
|
99
|
+
%Q(#<#{self.class} #{to_s.inspect}>)
|
100
|
+
end
|
101
|
+
|
102
|
+
def ==(other)
|
103
|
+
to_s == other.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
def atomic_chunk_factory
|
109
|
+
@atomic_chunk_factory ||= AtomicChunkFactory.new
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_accessor :atomic_chunks, :remaining_string
|
113
|
+
|
114
|
+
def push_atomic_chunk(atomic_chunk)
|
115
|
+
atomic_chunks.push(atomic_chunk)
|
116
|
+
@display_length += atomic_chunk.display_length
|
117
|
+
end
|
118
|
+
|
119
|
+
def pop_atomic_chunk
|
120
|
+
chunk = atomic_chunks.pop
|
121
|
+
@display_length -= chunk.display_length
|
122
|
+
chunk
|
123
|
+
end
|
124
|
+
|
125
|
+
def unshift_atomic_chunk(atomic_chunk)
|
126
|
+
atomic_chunks.unshift(atomic_chunk)
|
127
|
+
@display_length += atomic_chunk.display_length
|
128
|
+
end
|
129
|
+
|
130
|
+
def shift_atomic_chunk
|
131
|
+
chunk = atomic_chunks.shift
|
132
|
+
@display_length -= chunk.display_length
|
133
|
+
chunk
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete_atomic_chunk_at(index)
|
137
|
+
chunk = atomic_chunks.delete_at(index)
|
138
|
+
@display_length -= chunk.display_length
|
139
|
+
chunk
|
140
|
+
end
|
141
|
+
|
142
|
+
def retrieve_first_atomic_chunk
|
143
|
+
!!(atomic_chunks.first or slice_atomic_chunk_from_remaining_string!)
|
144
|
+
end
|
145
|
+
|
146
|
+
def slice_whole_remaining_string
|
147
|
+
slice_atomic_chunk_from_remaining_string! until remaining_string.empty?
|
148
|
+
end
|
149
|
+
|
150
|
+
def slice_atomic_chunk_from_remaining_string!
|
151
|
+
if atomic_chunk = slice_from_remaining_string!
|
152
|
+
push_atomic_chunk(atomic_chunk)
|
153
|
+
atomic_chunk
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def slice_from_remaining_string!
|
158
|
+
atomic_chunk_factory.slice_from!(remaining_string)
|
159
|
+
end
|
160
|
+
|
161
|
+
def smartly_split_too_long_word(max_display_length, sliced_chunk)
|
162
|
+
length_needed = max_display_length - sliced_chunk.display_length - (sliced_chunk.blank? ? 0 : 1)
|
163
|
+
return unless atomic_chunks.first.display_length % max_display_length <= length_needed
|
164
|
+
|
165
|
+
atomic_chunk = shift_atomic_chunk
|
166
|
+
atomic_chunk_1, atomic_chunk_2 = atomic_chunk_factory.split_string(atomic_chunk.to_s, length_needed)
|
167
|
+
|
168
|
+
sliced_chunk.concat(" ") unless sliced_chunk.blank?
|
169
|
+
sliced_chunk.concat(atomic_chunk_1.to_s)
|
170
|
+
|
171
|
+
unshift_atomic_chunk(atomic_chunk_2)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module MonospaceTextFormatter
|
2
|
+
class Line
|
3
|
+
|
4
|
+
def initialize(string_or_chunk, attrs={})
|
5
|
+
@omission = string_to_chunk(" ...")
|
6
|
+
@align = :left
|
7
|
+
@fill = " "
|
8
|
+
@truncated = nil
|
9
|
+
|
10
|
+
raise ArgumentError, "No string given" unless string_or_chunk
|
11
|
+
@original_chunk = to_chunk_if_string(string_or_chunk)
|
12
|
+
|
13
|
+
attributes(attrs)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :omission, :align, :fill
|
17
|
+
|
18
|
+
def width
|
19
|
+
@fixed_width || visible_chunk.display_length
|
20
|
+
end
|
21
|
+
|
22
|
+
def attributes(attrs={})
|
23
|
+
attrs.each { |name, value| send("#{name}=", value) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def width=(fixed_width)
|
27
|
+
return if fixed_width == @fixed_width
|
28
|
+
raise ArgumentError, "The :width must be equal or greater than 0, but is #{fixed_width}" unless fixed_width.nil? or fixed_width >= 0
|
29
|
+
|
30
|
+
@aligned_visible_text = @visible_chunk = nil
|
31
|
+
@fixed_width = fixed_width
|
32
|
+
end
|
33
|
+
|
34
|
+
def omission=(omission)
|
35
|
+
return if omission == @omission
|
36
|
+
|
37
|
+
@aligned_visible_text = @visible_chunk = nil if @truncated
|
38
|
+
@omission = to_chunk_if_string(omission)
|
39
|
+
end
|
40
|
+
|
41
|
+
def align=(align)
|
42
|
+
return if align == @align
|
43
|
+
raise ArgumentError, "The :align must be a Symbol or String with value 'left', 'center' or 'right', but is #{align.inspect}" unless [:left, :center, :right].include?(align.to_sym)
|
44
|
+
|
45
|
+
@aligned_visible_text = nil
|
46
|
+
@align = align.to_sym
|
47
|
+
end
|
48
|
+
|
49
|
+
def fill=(fill)
|
50
|
+
return if fill == @fill
|
51
|
+
|
52
|
+
@aligned_visible_text = nil
|
53
|
+
@fill = fill
|
54
|
+
end
|
55
|
+
|
56
|
+
def truncated?
|
57
|
+
visible_chunk
|
58
|
+
@truncated
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
aligned_visible_text
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def to_chunk_if_string(string)
|
68
|
+
string.is_a?(String) ? string_to_chunk(string) : string
|
69
|
+
end
|
70
|
+
|
71
|
+
def string_to_chunk(string)
|
72
|
+
Chunk.new(string)
|
73
|
+
end
|
74
|
+
|
75
|
+
def aligned_visible_text
|
76
|
+
@aligned_visible_text ||= if @fixed_width.nil? or fill.nil? or fill.empty?
|
77
|
+
visible_chunk.to_s
|
78
|
+
else
|
79
|
+
case align
|
80
|
+
when :left
|
81
|
+
visible_chunk.to_s.ljust(@fixed_width, fill)
|
82
|
+
when :center
|
83
|
+
visible_chunk.to_s.center(@fixed_width, fill)
|
84
|
+
when :right
|
85
|
+
visible_chunk.to_s.rjust(@fixed_width, fill)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def visible_chunk
|
91
|
+
@visible_chunk ||= if @original_chunk.multiline? || @fixed_width && @original_chunk.display_length > @fixed_width
|
92
|
+
@truncated = true
|
93
|
+
@original_chunk.slice(@fixed_width ? @fixed_width - omission.display_length : nil).concat(omission).slice!(@fixed_width)
|
94
|
+
else
|
95
|
+
@truncated = false
|
96
|
+
@original_chunk.slice(@fixed_width)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require "monospace_text_formatter/version"
|
2
|
+
|
3
|
+
require "monospace_text_formatter/atomic_chunk"
|
4
|
+
require "monospace_text_formatter/atomic_chunk_factory"
|
5
|
+
require "monospace_text_formatter/chunk"
|
6
|
+
require "monospace_text_formatter/line"
|
7
|
+
require "monospace_text_formatter/box"
|
8
|
+
|
9
|
+
module MonospaceTextFormatter
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: monospace_text_formatter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jacek Mikrut
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-16 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70478590 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70478590
|
25
|
+
description: Formats monospaced text into a line or visual box of defined 'width'
|
26
|
+
and 'height' boundaries (expressed in number of characters).
|
27
|
+
email: jacekmikrut.software@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- lib/monospace_text_formatter/atomic_chunk_factory.rb
|
33
|
+
- lib/monospace_text_formatter/box.rb
|
34
|
+
- lib/monospace_text_formatter/version.rb
|
35
|
+
- lib/monospace_text_formatter/atomic_chunk.rb
|
36
|
+
- lib/monospace_text_formatter/line.rb
|
37
|
+
- lib/monospace_text_formatter/chunk.rb
|
38
|
+
- lib/monospace_text_formatter.rb
|
39
|
+
- README.md
|
40
|
+
- LICENSE
|
41
|
+
- Changelog
|
42
|
+
homepage: http://github.com/jacekmikrut/monospace_text_formatter
|
43
|
+
licenses: []
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.12
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Formats monospaced text into a line or visual box.
|
66
|
+
test_files: []
|