columnist 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +150 -0
- data/lib/columnist/column.rb +99 -0
- data/lib/columnist/formatter/nested.rb +70 -0
- data/lib/columnist/formatter/progress.rb +37 -0
- data/lib/columnist/options_validator.rb +5 -0
- data/lib/columnist/row.rb +83 -0
- data/lib/columnist/table.rb +133 -0
- data/lib/columnist.rb +173 -0
- data/spec/column_spec.rb +416 -0
- data/spec/columnist.rb +693 -0
- data/spec/nested_formatter_spec.rb +301 -0
- data/spec/options_validator_spec.rb +24 -0
- data/spec/progress_formatter_spec.rb +89 -0
- data/spec/row_spec.rb +129 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/helpers/stdout.rb +8 -0
- data/spec/support/matchers/argument.rb +17 -0
- data/spec/table_spec.rb +154 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35ce2268e5eb282ea238c82f068afe57dc8140e4
|
4
|
+
data.tar.gz: cf74dcb33851da6ae27429675ce80d06288f0dc0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a76d6cb4ee88847ed5dd3ddc465d56cb3fc6f7f886a1e4c16dc80ce787f3ec49d52e6a81ec258250d9c39bce28cab12f31073d3d328e38f478984eecd90082b5
|
7
|
+
data.tar.gz: e5b603a886e092c61b43472a984027e275b81195db5eff1894b533c01a32a9b17c1ba60bde9f20b2f40079c03eac9e0136c3b9b2c56946f2819a70609db89742
|
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
## Columnist
|
2
|
+
|
3
|
+
This gem provides a DSL that makes it easy to write reports of various types in ruby. It eliminates
|
4
|
+
the need to litter your source with *puts* statements, instead providing a more readable, expressive
|
5
|
+
interface to your application. Some of the best features include:
|
6
|
+
|
7
|
+
* Formatters that automatically indicate progress
|
8
|
+
* Table syntax similar to HTML that makes it trivial to format your data in rows and columns
|
9
|
+
* Easily created headers and footers for your report
|
10
|
+
* Output suppression that makes it easy for your script to support a _quiet_ flag
|
11
|
+
* Capture report output as a string
|
12
|
+
|
13
|
+
The latest release, thanks to a contribution from [Josh Brown](https://github.com/tobijb), allows you
|
14
|
+
to choose between UTF8 or ASCII for drawing tables. By default it will use UTF8 if your system
|
15
|
+
supports it. Here is an example of output you can generate easily with "the reporter":
|
16
|
+
|
17
|
+
![Screenshot](http://i.imgur.com/5izCf.png)
|
18
|
+
|
19
|
+
### Installation
|
20
|
+
|
21
|
+
It is up on rubygems.org so add it to your bundle in the Gemfile
|
22
|
+
|
23
|
+
```bash
|
24
|
+
gem 'columnist', '>=1.0'
|
25
|
+
```
|
26
|
+
|
27
|
+
or do it the old fashioned way:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
gem install columnist
|
31
|
+
```
|
32
|
+
|
33
|
+
### Usage
|
34
|
+
|
35
|
+
The gem provides a mixin that can be included in your scripts.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'columnist'
|
39
|
+
|
40
|
+
class MyReport
|
41
|
+
include Columnist
|
42
|
+
...
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
### [Wiki](https://github.com/alb3rtuk/columnist)
|
47
|
+
|
48
|
+
The [Wiki](https://github.com/alb3rtuk/columnist) has all of the documentation
|
49
|
+
necessary for getting you started.
|
50
|
+
|
51
|
+
### API Reference
|
52
|
+
|
53
|
+
There are several methods the mixin provides that do not depend on the formatter used:
|
54
|
+
|
55
|
+
* _header(hash)_ and _footer(hash)_
|
56
|
+
* _:title_ - The title text for the section. _Default: 'Report'_
|
57
|
+
* _:width_ - The width in characters for the section. _Default: 100_
|
58
|
+
* _:align_ - 'left'|'right'|'center' align the title text. _Default: 'left'_
|
59
|
+
* _:spacing_ - Number of vertical lines to leave as spacing after|before the header|footer.
|
60
|
+
_Default: 1_
|
61
|
+
* _:timestamp_ - Include a line indicating the timestamp below|above the header|footer text.
|
62
|
+
Either true|false. _Default: false_
|
63
|
+
* _:rule_ - true|false indicates whether to include a horizontal rule below|above the
|
64
|
+
header|footer. _Default: false_
|
65
|
+
* _:color_ - The color to use for the terminal output i.e. 'red' or 'blue' or 'green'
|
66
|
+
* _:bold_ - true|false to boldface the font
|
67
|
+
* _report(hash) {block}_
|
68
|
+
* The first argument is a hash that defines the options for the method. See the details in the
|
69
|
+
formatter section for allowed values.
|
70
|
+
* The second argument is a block of ruby code that you want executed within the context of the
|
71
|
+
reporter. Any ruby code is allowed. See the examples that follow in the formatter sections for
|
72
|
+
details.
|
73
|
+
* _formatter=(string)_
|
74
|
+
* Factory method indicating the formatter you want your application to use. At present the 2
|
75
|
+
formatters are (_Default: 'nested'_):
|
76
|
+
* 'progress' - Use the progress formatter
|
77
|
+
* 'nested' - Use the nested (or documentation) formatter
|
78
|
+
* _horizontal_rule(hash)_
|
79
|
+
* _:char_ - The character used to build the rule. _Default: '-'_
|
80
|
+
* _:width_ - The width in characters of the rule. _Default: 100_
|
81
|
+
* _:color_ - The color to use for the terminal output i.e. 'red' or 'blue' or 'green'
|
82
|
+
* _:bold_ - true|false to boldface the font
|
83
|
+
* _vertical_spacing(int)_
|
84
|
+
* Number of blank lines to output. _Default: 1_
|
85
|
+
* _datetime(hash)_
|
86
|
+
* _:align_ - 'left'|'center'|'right' alignment of the timestamp. _Default: 'left'_
|
87
|
+
* _:width_ - The width of the string in characters. _Default: 100_
|
88
|
+
* _:format_ - Any allowed format from #strftime#. _Default: %Y-%m-%d %H:%I:%S%p_
|
89
|
+
* _:color_ - The color to use for the terminal output i.e. 'red' or 'blue' or 'green'
|
90
|
+
* _:bold_ - true|false to boldface the font
|
91
|
+
* _aligned(string, hash)_
|
92
|
+
* _text_ - String to display
|
93
|
+
* _:align_ - 'left'|'right'|'center' align the string text. _Default: 'left'_
|
94
|
+
* _:width_ - The width in characters of the string text. _Default: 100_
|
95
|
+
* _:color_ - The color to use for the terminal output i.e. 'red' or 'blue' or 'green'
|
96
|
+
* _:bold_ - true|false to boldface the font
|
97
|
+
* _table(hash) {block}_
|
98
|
+
* The first argument is a hash that defines properties of the table.
|
99
|
+
* _:border_ - true|false indicates whether to include borders around the table cells
|
100
|
+
* _:encoding_ - :ascii or :unicode (default unicode)
|
101
|
+
* The second argument is a block which includes calls the to the _row_ method
|
102
|
+
* _row {block}_
|
103
|
+
* _:header_ - Set to true to indicate if this is a header row in the table.
|
104
|
+
* _:color_ - The color to use for the terminal output i.e. 'red' or 'blue' or 'green'
|
105
|
+
* _:bold_ - true|false to boldface the font
|
106
|
+
* _column(string, hash)_
|
107
|
+
* _text_ - String to display in the table cell
|
108
|
+
* _options_ - The options to define the column
|
109
|
+
* :width - defines the width of the column
|
110
|
+
* :padding - The number of spaces to put on both the left and right of the text.
|
111
|
+
* :align - Allowed values are left|right|center
|
112
|
+
* :color - The color to use for the terminal output i.e. 'red' or 'blue' or 'green'
|
113
|
+
* :bold - true|false to boldface the font
|
114
|
+
* _suppress_output_ - Suppresses output stream that goes to STDOUT
|
115
|
+
* _capture_output_ - Captures all of the output stream to a string and restores output to STDOUT
|
116
|
+
* _restore_output_ - Restores the output stream to STDOUT
|
117
|
+
|
118
|
+
### To Do
|
119
|
+
|
120
|
+
* Add a formatter that supports html output
|
121
|
+
* Add the ability for a column to span across others in a table
|
122
|
+
|
123
|
+
### Contributors
|
124
|
+
|
125
|
+
* [Josh Brown](https://github.com/tobijb) added the ability to encode tables in either ascii or utf8
|
126
|
+
* [Stefan Frank](https://github.com/mugwump) for raising the issue that he could not capture report
|
127
|
+
output in a variable as a string
|
128
|
+
* [Mike Gunderloy](https://github.com/ffmike) for suggesting the need for suppressing output and
|
129
|
+
putting together a fantastic pull request and discussion
|
130
|
+
* [Jason Rogers](https://github.com/jacaetevha) and [Peter Suschlik](https://github.com/splattael)
|
131
|
+
for their contributions as well on items I missed
|
132
|
+
|
133
|
+
### License
|
134
|
+
|
135
|
+
Copyright (c) 2011-2014 Albert Rannetsperger
|
136
|
+
|
137
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
138
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
139
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
140
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
141
|
+
furnished to do so, subject to the following conditions:
|
142
|
+
|
143
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
144
|
+
portions of the Software.
|
145
|
+
|
146
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
147
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
148
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
149
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
150
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'colored'
|
2
|
+
|
3
|
+
module Columnist
|
4
|
+
class Column
|
5
|
+
include OptionsValidator
|
6
|
+
|
7
|
+
VALID_OPTIONS = [:width, :padding, :align, :color, :bold, :underline, :reversed]
|
8
|
+
attr_accessor :text, :size, *VALID_OPTIONS
|
9
|
+
|
10
|
+
def initialize(text = nil, options = {})
|
11
|
+
self.validate_options(options, *VALID_OPTIONS)
|
12
|
+
self.text = text.to_s
|
13
|
+
self.width = options[:width] || 10
|
14
|
+
self.align = options[:align] || 'left'
|
15
|
+
self.padding = options[:padding] || 0
|
16
|
+
self.color = options[:color] || nil
|
17
|
+
self.bold = options[:bold] || false
|
18
|
+
self.underline = options[:underline] || false
|
19
|
+
self.reversed = options[:reversed] || false
|
20
|
+
|
21
|
+
raise ArgumentError unless self.width > 0
|
22
|
+
raise ArgumentError unless self.padding.to_s.match(/^\d+$/)
|
23
|
+
end
|
24
|
+
|
25
|
+
def size
|
26
|
+
self.width - 2 * self.padding
|
27
|
+
end
|
28
|
+
|
29
|
+
def required_width
|
30
|
+
self.text.to_s.size + 2 * self.padding
|
31
|
+
end
|
32
|
+
|
33
|
+
def screen_rows
|
34
|
+
if self.text.nil? || self.text.empty?
|
35
|
+
[' ' * self.width]
|
36
|
+
else
|
37
|
+
self.text.scan(/.{1,#{self.size}}/m).map { |s| to_cell(s) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def to_cell(str)
|
44
|
+
# NOTE: For making underline and reversed work Change so that based on the
|
45
|
+
# unformatted text it determines how much spacing to add left and right
|
46
|
+
# then colorize the cell text
|
47
|
+
cell = str.empty? ? blank_cell : aligned_cell(str)
|
48
|
+
padding_str = ' ' * self.padding
|
49
|
+
padding_str + colorize(cell) + padding_str
|
50
|
+
end
|
51
|
+
|
52
|
+
def blank_cell
|
53
|
+
' ' * self.size
|
54
|
+
end
|
55
|
+
|
56
|
+
def aligned_cell(str)
|
57
|
+
case self.align
|
58
|
+
when 'left'
|
59
|
+
str.ljust(self.size)
|
60
|
+
when 'right'
|
61
|
+
str.rjust(self.size)
|
62
|
+
when 'center'
|
63
|
+
str.ljust((self.size - str.size)/2.0 + str.size).rjust(self.size)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def colorize(str)
|
68
|
+
str = str.send('bold') if self.bold
|
69
|
+
case self.color
|
70
|
+
when 'red'
|
71
|
+
return "\x1B[38;5;9m#{str}\x1B[38;5;256m"
|
72
|
+
when 'green'
|
73
|
+
return "\x1B[38;5;10m#{str}\x1B[38;5;256m"
|
74
|
+
when 'yellow'
|
75
|
+
return "\x1B[38;5;11m#{str}\x1B[38;5;256m"
|
76
|
+
when 'blue'
|
77
|
+
return "\x1B[38;5;33m#{str}\x1B[38;5;256m"
|
78
|
+
when 'magenta'
|
79
|
+
return "\x1B[38;5;13m#{str}\x1B[38;5;256m"
|
80
|
+
when 'cyan'
|
81
|
+
return "\x1B[38;5;14m#{str}\x1B[38;5;256m"
|
82
|
+
when 'gray'
|
83
|
+
return "\x1B[38;5;240m#{str}\x1B[38;5;256m"
|
84
|
+
when 'white'
|
85
|
+
return "\x1B[38;5;255m#{str}\x1B[38;5;256m"
|
86
|
+
when 'black'
|
87
|
+
return "\x1B[38;5;0m#{str}\x1B[38;5;256m"
|
88
|
+
end
|
89
|
+
if is_number?(self.color)
|
90
|
+
str = "\x1B[38;5;#{self.color}m#{str}\x1B[38;5;256m"
|
91
|
+
end
|
92
|
+
str
|
93
|
+
end
|
94
|
+
|
95
|
+
def is_number?(str)
|
96
|
+
true if Integer(str) rescue false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'colored'
|
3
|
+
|
4
|
+
module Columnist
|
5
|
+
class NestedFormatter
|
6
|
+
include Singleton
|
7
|
+
include OptionsValidator
|
8
|
+
|
9
|
+
VALID_OPTIONS = [:message, :type, :complete, :indent_size, :color, :bold]
|
10
|
+
attr_accessor :indent_size, :complete_string, :message_string, :color, :bold
|
11
|
+
|
12
|
+
def format(options, block)
|
13
|
+
self.validate_options(options, *VALID_OPTIONS)
|
14
|
+
|
15
|
+
indent_level :incr
|
16
|
+
|
17
|
+
padding = ' ' * @indent_level * (options[:indent_size] || self.indent_size)
|
18
|
+
|
19
|
+
message_str = padding + (options[:message] || self.message_string)
|
20
|
+
complete_str = options[:complete] || self.complete_string
|
21
|
+
|
22
|
+
if options[:type] == 'inline'
|
23
|
+
colorize("#{message_str}...", true, options)
|
24
|
+
else
|
25
|
+
colorize(message_str, false, options)
|
26
|
+
complete_str = padding + complete_str
|
27
|
+
end
|
28
|
+
|
29
|
+
block.call
|
30
|
+
|
31
|
+
colorize(complete_str, false, options)
|
32
|
+
|
33
|
+
indent_level :decr
|
34
|
+
end
|
35
|
+
|
36
|
+
def message_string
|
37
|
+
@message_string ||= 'working'
|
38
|
+
end
|
39
|
+
|
40
|
+
def complete_string
|
41
|
+
@complete_string ||= 'complete'
|
42
|
+
end
|
43
|
+
|
44
|
+
def indent_size
|
45
|
+
@indent_size ||= 2
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def colorize(str, inline, options)
|
51
|
+
str = str.send(options[:color]) if options[:color]
|
52
|
+
str = str.bold if options[:bold]
|
53
|
+
|
54
|
+
if inline
|
55
|
+
print str
|
56
|
+
else
|
57
|
+
puts str
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def indent_level(value)
|
62
|
+
case value
|
63
|
+
when :incr
|
64
|
+
@indent_level = (@indent_level) ? @indent_level + 1 : 0
|
65
|
+
when :decr
|
66
|
+
@indent_level -= 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'colored'
|
3
|
+
|
4
|
+
module Columnist
|
5
|
+
class ProgressFormatter
|
6
|
+
include Singleton
|
7
|
+
include OptionsValidator
|
8
|
+
|
9
|
+
VALID_OPTIONS = [:indicator, :color, :bold]
|
10
|
+
attr_accessor *VALID_OPTIONS
|
11
|
+
|
12
|
+
def format(options, block)
|
13
|
+
self.validate_options(options, *VALID_OPTIONS)
|
14
|
+
|
15
|
+
self.indicator = options[:indicator] if options[:indicator]
|
16
|
+
self.color = options[:color]
|
17
|
+
self.bold = options[:bold] || false
|
18
|
+
|
19
|
+
block.call
|
20
|
+
|
21
|
+
puts
|
22
|
+
end
|
23
|
+
|
24
|
+
def progress(override = nil)
|
25
|
+
str = override || self.indicator
|
26
|
+
|
27
|
+
str = str.send(self.color) if self.color
|
28
|
+
str = str.send('bold') if self.bold
|
29
|
+
|
30
|
+
print str
|
31
|
+
end
|
32
|
+
|
33
|
+
def indicator
|
34
|
+
@indicator ||= '.'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Columnist
|
2
|
+
class Row
|
3
|
+
include OptionsValidator
|
4
|
+
|
5
|
+
VALID_OPTIONS = [:header, :color, :border_color, :bold, :encoding]
|
6
|
+
attr_accessor :columns, :border, *VALID_OPTIONS
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
self.validate_options(options, *VALID_OPTIONS)
|
10
|
+
|
11
|
+
self.columns = []
|
12
|
+
self.border = false
|
13
|
+
self.header = options[:header] || false
|
14
|
+
self.color = options[:color]
|
15
|
+
self.border_color = options[:border_color]
|
16
|
+
self.bold = options[:bold] || false
|
17
|
+
self.encoding = options[:encoding] || :unicode
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(column)
|
21
|
+
if column.color.nil? && self.color
|
22
|
+
column.color = self.color
|
23
|
+
end
|
24
|
+
|
25
|
+
if self.bold || self.header
|
26
|
+
column.bold = true
|
27
|
+
end
|
28
|
+
|
29
|
+
self.columns << column
|
30
|
+
end
|
31
|
+
|
32
|
+
def output
|
33
|
+
screen_count.times do |sr|
|
34
|
+
border_char = use_utf8? ? "\u2503" : '|'
|
35
|
+
border_char = border_char.send(self.border_color) if self.border_color
|
36
|
+
|
37
|
+
line = (self.border) ? "#{border_char} " : ''
|
38
|
+
|
39
|
+
self.columns.size.times do |mc|
|
40
|
+
col = self.columns[mc]
|
41
|
+
# Account for the fact that some columns will have more screen rows than their
|
42
|
+
# counterparts in the row. An example being:
|
43
|
+
# c1 = Column.new('x' * 50, :width => 10)
|
44
|
+
# c2 = Column.new('x' * 20, :width => 10)
|
45
|
+
#
|
46
|
+
# c1.screen_rows.size == 5
|
47
|
+
# c2.screen_rows.size == 2
|
48
|
+
#
|
49
|
+
# So when we don't have a screen row for c2 we need to fill the screen with the
|
50
|
+
# proper number of blanks so the layout looks like (parenthesis on the right just
|
51
|
+
# indicate screen row index)
|
52
|
+
#
|
53
|
+
# +-------------+------------+
|
54
|
+
# | xxxxxxxxxxx | xxxxxxxxxx | (0)
|
55
|
+
# | xxxxxxxxxxx | xxxxxxxxxx | (1)
|
56
|
+
# | xxxxxxxxxxx | | (2)
|
57
|
+
# | xxxxxxxxxxx | | (3)
|
58
|
+
# | xxxxxxxxxxx | | (4)
|
59
|
+
# +-------------+------------+
|
60
|
+
if col.screen_rows[sr].nil?
|
61
|
+
line << ' ' * col.width
|
62
|
+
else
|
63
|
+
line << self.columns[mc].screen_rows[sr]
|
64
|
+
end
|
65
|
+
|
66
|
+
line << ' ' + ((self.border) ? "#{border_char} " : '')
|
67
|
+
end
|
68
|
+
|
69
|
+
puts line
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def screen_count
|
76
|
+
@sc ||= self.columns.inject(0) { |max, column| column.screen_rows.size > max ? column.screen_rows.size : max }
|
77
|
+
end
|
78
|
+
|
79
|
+
def use_utf8?
|
80
|
+
self.encoding == :unicode && "\u2501" != "u2501"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Columnist
|
2
|
+
class Table
|
3
|
+
include OptionsValidator
|
4
|
+
|
5
|
+
VALID_OPTIONS = [:border, :border_color, :width, :encoding]
|
6
|
+
attr_accessor :rows, *VALID_OPTIONS
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
self.validate_options(options, *VALID_OPTIONS)
|
10
|
+
|
11
|
+
self.border = options[:border] || false
|
12
|
+
self.border_color = options[:border_color] || false
|
13
|
+
self.width = options[:width] || false
|
14
|
+
self.encoding = options[:encoding] || Columnist::DEFAULTS[:encoding]
|
15
|
+
|
16
|
+
@rows = []
|
17
|
+
|
18
|
+
raise ArgumentError, "Invalid encoding" unless [:ascii, :unicode].include? self.encoding
|
19
|
+
end
|
20
|
+
|
21
|
+
def add(row)
|
22
|
+
# Inheritance from the table
|
23
|
+
row.border = self.border
|
24
|
+
row.border_color = self.border_color
|
25
|
+
|
26
|
+
# Inherit properties from the appropriate row
|
27
|
+
inherit_column_attrs(row) if self.rows[0]
|
28
|
+
|
29
|
+
self.rows << row
|
30
|
+
end
|
31
|
+
|
32
|
+
def output
|
33
|
+
return if self.rows.size == 0 # we got here with nothing to print to the screen
|
34
|
+
auto_adjust_widths if self.width == :auto
|
35
|
+
|
36
|
+
puts separator('first') if self.border
|
37
|
+
self.rows.each_with_index do |row, index|
|
38
|
+
row.output
|
39
|
+
puts separator('middle') if self.border && (index != self.rows.size - 1)
|
40
|
+
end
|
41
|
+
puts separator('last') if self.border
|
42
|
+
end
|
43
|
+
|
44
|
+
def auto_adjust_widths
|
45
|
+
column_widths = []
|
46
|
+
|
47
|
+
self.rows.each do |row|
|
48
|
+
row.columns.each_with_index do |col, i|
|
49
|
+
column_widths[i] = [col.required_width, (column_widths[i] || 0)].max
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
self.rows.each do |row|
|
54
|
+
row.columns.each_with_index do |col, i|
|
55
|
+
col.width = column_widths[i]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def separator(type = 'middle')
|
63
|
+
left, center, right, bar = use_utf8? ? utf8_separator(type) : ascii_separator
|
64
|
+
|
65
|
+
separator_str = left + self.rows[0].columns.map { |c| bar * (c.width + 2) }.join(center) + right
|
66
|
+
separator_str.send(self.border_color) if self.border_color
|
67
|
+
end
|
68
|
+
|
69
|
+
def use_utf8?
|
70
|
+
self.encoding == :unicode && "\u2501" != "u2501"
|
71
|
+
end
|
72
|
+
|
73
|
+
def ascii_separator
|
74
|
+
left = right = center = '+'
|
75
|
+
bar = '-'
|
76
|
+
[left, right, center, bar]
|
77
|
+
end
|
78
|
+
|
79
|
+
def utf8_separator(type)
|
80
|
+
bar = "\u2501"
|
81
|
+
|
82
|
+
left, center, right = case type
|
83
|
+
when 'first'
|
84
|
+
["\u250F", "\u2533", "\u2513"]
|
85
|
+
when 'middle'
|
86
|
+
["\u2523", "\u254A", "\u252B"]
|
87
|
+
when 'last'
|
88
|
+
["\u2517", "\u253B", "\u251B"]
|
89
|
+
end
|
90
|
+
|
91
|
+
[left, center, right, bar]
|
92
|
+
end
|
93
|
+
|
94
|
+
def inherit_column_attrs(row)
|
95
|
+
row.columns.each_with_index do |c, i|
|
96
|
+
use_positional_attrs(c, i)
|
97
|
+
use_color(row, c, i)
|
98
|
+
use_bold(row, c, i)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def use_positional_attrs(c, i)
|
103
|
+
# The positional attributes are always required to inheret to make sure the table
|
104
|
+
# displays properly
|
105
|
+
%w{align padding width}.each do |attr|
|
106
|
+
val = self.rows[0].columns[i].send(attr)
|
107
|
+
c.send(attr + "=", val)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def inherit_from
|
112
|
+
self.rows[0].header ? 1 : 0
|
113
|
+
end
|
114
|
+
|
115
|
+
def use_color(row, c, i)
|
116
|
+
if c.color
|
117
|
+
# keep default
|
118
|
+
elsif row.color
|
119
|
+
c.color = row.color
|
120
|
+
elsif inherit_from != 1
|
121
|
+
c.color = self.rows[inherit_from].columns[i].color
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def use_bold(row, c, i)
|
126
|
+
if row.bold
|
127
|
+
c.bold = row.bold
|
128
|
+
elsif inherit_from != 1
|
129
|
+
c.bold = self.rows[inherit_from].columns[i].bold
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|