terminal-table 1.4.3 → 1.4.4
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/History.rdoc +5 -0
- data/README.rdoc +212 -130
- data/Todo.rdoc +0 -1
- data/examples/examples.rb +20 -7
- data/lib/terminal-table.rb +3 -7
- data/lib/terminal-table/cell.rb +49 -14
- data/lib/terminal-table/core_ext.rb +1 -21
- data/lib/terminal-table/row.rb +48 -0
- data/lib/terminal-table/separator.rb +14 -0
- data/lib/terminal-table/style.rb +61 -0
- data/lib/terminal-table/table.rb +138 -188
- data/lib/terminal-table/version.rb +1 -1
- data/spec/cell_spec.rb +38 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/table_spec.rb +139 -65
- data/terminal-table.gemspec +4 -4
- metadata +8 -4
- data/lib/terminal-table/heading.rb +0 -10
@@ -1,28 +1,8 @@
|
|
1
1
|
|
2
2
|
class String
|
3
3
|
def align position, length
|
4
|
-
|
4
|
+
self.__send__ position, length
|
5
5
|
end
|
6
6
|
alias_method :left, :ljust
|
7
7
|
alias_method :right, :rjust
|
8
|
-
end
|
9
|
-
|
10
|
-
module Enumerable
|
11
|
-
def map_with_index &block
|
12
|
-
vals = []
|
13
|
-
each_with_index { |v, i| vals << yield(v, i) }
|
14
|
-
vals
|
15
|
-
end
|
16
|
-
alias :collect_with_index :map_with_index
|
17
|
-
end
|
18
|
-
|
19
|
-
class Object
|
20
|
-
def yield_or_eval &block
|
21
|
-
return unless block
|
22
|
-
if block.arity > 0
|
23
|
-
yield self
|
24
|
-
else
|
25
|
-
self.instance_eval &block
|
26
|
-
end
|
27
|
-
end
|
28
8
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Terminal
|
2
|
+
class Table
|
3
|
+
class Row
|
4
|
+
|
5
|
+
##
|
6
|
+
# Row cells
|
7
|
+
|
8
|
+
attr_reader :cells
|
9
|
+
|
10
|
+
attr_reader :table
|
11
|
+
|
12
|
+
##
|
13
|
+
# Initialize with _width_ and _options_.
|
14
|
+
|
15
|
+
def initialize table, array = []
|
16
|
+
@cell_index = 0
|
17
|
+
@table = table
|
18
|
+
@cells = []
|
19
|
+
array.each { |item| self << item }
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_cell item
|
23
|
+
options = item.is_a?(Hash) ? item : {:value => item}
|
24
|
+
cell = Cell.new(options.merge(:index => @cell_index, :table => @table))
|
25
|
+
@cell_index += cell.colspan
|
26
|
+
@cells << cell
|
27
|
+
end
|
28
|
+
alias << add_cell
|
29
|
+
|
30
|
+
def [] index
|
31
|
+
cells[index]
|
32
|
+
end
|
33
|
+
|
34
|
+
def height
|
35
|
+
cells.map { |c| c.lines.count }.max
|
36
|
+
end
|
37
|
+
|
38
|
+
def render
|
39
|
+
y = @table.style.border_y
|
40
|
+
(0...height).to_a.map do |line|
|
41
|
+
y + cells.map do |cell|
|
42
|
+
cell.render(line)
|
43
|
+
end.join(y) + y
|
44
|
+
end.join("\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Terminal
|
2
|
+
class Table
|
3
|
+
class Separator < Row
|
4
|
+
|
5
|
+
def render
|
6
|
+
arr_x = (0...@table.number_of_columns).to_a.map do |i|
|
7
|
+
@table.style.border_x * (@table.column_width(i) + @table.cell_padding)
|
8
|
+
end
|
9
|
+
border_i = @table.style.border_i
|
10
|
+
border_i + arr_x.join(border_i) + border_i
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
module Terminal
|
3
|
+
class Table
|
4
|
+
# A Style object holds all the formatting information for a Table object
|
5
|
+
#
|
6
|
+
# To create a table with a certain style, use either the constructor
|
7
|
+
# option <tt>:style</tt>, the Table#style object or the Table#style= method
|
8
|
+
#
|
9
|
+
# All these examples have the same effect:
|
10
|
+
#
|
11
|
+
# # by constructor
|
12
|
+
# @table = Table.new(:style => {:padding_left => 2, :width => 40})
|
13
|
+
#
|
14
|
+
# # by object
|
15
|
+
# @table.style.padding_left = 2
|
16
|
+
# @table.style.width = 40
|
17
|
+
#
|
18
|
+
# # by method
|
19
|
+
# @table.style = {:padding_left => 2, :width => 40}
|
20
|
+
#
|
21
|
+
# To set a default style for all tables created afterwards use Style.defaults=
|
22
|
+
#
|
23
|
+
# Terminal::Table::Style.defaults = {:width => 80}
|
24
|
+
#
|
25
|
+
class Style
|
26
|
+
@@defaults = {
|
27
|
+
:border_x => "-", :border_y => "|", :border_i => "+",
|
28
|
+
:padding_left => 1, :padding_right => 1,
|
29
|
+
:width => nil
|
30
|
+
}
|
31
|
+
|
32
|
+
attr_accessor :border_x
|
33
|
+
attr_accessor :border_y
|
34
|
+
attr_accessor :border_i
|
35
|
+
|
36
|
+
attr_accessor :padding_left
|
37
|
+
attr_accessor :padding_right
|
38
|
+
|
39
|
+
attr_accessor :width
|
40
|
+
|
41
|
+
|
42
|
+
def initialize options = {}
|
43
|
+
apply self.class.defaults.merge(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply options
|
47
|
+
options.each { |m, v| __send__ "#{m}=", v }
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def defaults
|
52
|
+
@@defaults
|
53
|
+
end
|
54
|
+
|
55
|
+
def defaults= options
|
56
|
+
@@defaults = defaults.merge(options)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/terminal-table/table.rb
CHANGED
@@ -2,143 +2,39 @@
|
|
2
2
|
module Terminal
|
3
3
|
class Table
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
#++
|
8
|
-
|
9
|
-
Error = Class.new StandardError
|
10
|
-
|
11
|
-
##
|
12
|
-
# Table characters, x axis, y axis, and intersection.
|
13
|
-
|
14
|
-
X, Y, I = '-', '|', '+'
|
15
|
-
|
16
|
-
##
|
17
|
-
# Rows array.
|
18
|
-
|
19
|
-
attr_accessor :rows
|
5
|
+
attr_reader :title
|
6
|
+
attr_reader :headings
|
20
7
|
|
21
8
|
##
|
22
9
|
# Generates a ASCII table with the given _options_.
|
23
10
|
|
24
11
|
def initialize options = {}, &block
|
25
|
-
@
|
12
|
+
@column_widths = []
|
13
|
+
self.style = options.fetch :style, {}
|
14
|
+
self.title = options.fetch :title, nil
|
26
15
|
self.headings = options.fetch :headings, []
|
27
|
-
|
28
|
-
|
29
|
-
yield_or_eval &block if block
|
30
|
-
end
|
31
|
-
|
32
|
-
def headings= h
|
33
|
-
@headings = h
|
34
|
-
recalc_column_lengths @headings
|
35
|
-
end
|
36
|
-
|
37
|
-
##
|
38
|
-
# Render the table.
|
39
|
-
|
40
|
-
def render
|
41
|
-
buffer = [separator, "\n"]
|
42
|
-
if has_headings?
|
43
|
-
buffer << render_headings
|
44
|
-
buffer << "\n" << separator << "\n"
|
45
|
-
end
|
46
|
-
buffer << @rows.map do |row|
|
47
|
-
render_row(row)
|
48
|
-
end.join("\n")
|
49
|
-
buffer << "\n" << separator << "\n"
|
50
|
-
buffer.join
|
51
|
-
end
|
52
|
-
alias :to_s :render
|
53
|
-
|
54
|
-
##
|
55
|
-
# Render headings.
|
56
|
-
|
57
|
-
def render_headings
|
58
|
-
Y + @headings.map_with_index do |heading, i|
|
59
|
-
width = 0
|
60
|
-
if heading.is_a?(Hash) and !heading[:colspan].nil?
|
61
|
-
i.upto(i + heading[:colspan] - 1) do |col|
|
62
|
-
width += length_of_column(col)
|
63
|
-
end
|
64
|
-
width += (heading[:colspan] - 1) * (Y.length + 2)
|
65
|
-
else
|
66
|
-
width = length_of_column(i)
|
67
|
-
end
|
68
|
-
Heading.new( width, heading).render
|
69
|
-
end.join(Y) + Y
|
16
|
+
self.rows = options.fetch :rows, []
|
17
|
+
yield_or_eval(&block) if block
|
70
18
|
end
|
71
19
|
|
72
20
|
##
|
73
|
-
#
|
74
|
-
|
75
|
-
def render_row row
|
76
|
-
if row == :separator
|
77
|
-
separator
|
78
|
-
else
|
79
|
-
Y + row.map_with_index do |cell, i|
|
80
|
-
render_cell(cell, row_to_index(row, i))
|
81
|
-
end.join(Y) + Y
|
82
|
-
end
|
83
|
-
end
|
21
|
+
# Align column _n_ to the given _alignment_ of :center, :left, or :right.
|
84
22
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
if cell.is_a?(Hash) and !cell[:colspan].nil?
|
91
|
-
i.upto(i + cell[:colspan] - 1) do |col|
|
92
|
-
width += length_of_column(col)
|
93
|
-
end
|
94
|
-
width += (cell[:colspan] - 1) * (Y.length + 2)
|
95
|
-
else
|
96
|
-
width = length_of_column(i)
|
23
|
+
def align_column n, alignment
|
24
|
+
r = rows
|
25
|
+
column(n).each_with_index do |col, i|
|
26
|
+
cell = r[i][n]
|
27
|
+
cell.alignment = alignment unless cell.alignment?
|
97
28
|
end
|
98
|
-
Cell.new(width, cell).render
|
99
|
-
end
|
100
|
-
|
101
|
-
##
|
102
|
-
# Create a separator based on colum lengths.
|
103
|
-
|
104
|
-
def separator
|
105
|
-
I + columns.collect_with_index do |col, i|
|
106
|
-
X * (length_of_column(i) + 2)
|
107
|
-
end.join(I) + I
|
108
29
|
end
|
109
30
|
|
110
31
|
##
|
111
32
|
# Add a row.
|
112
33
|
|
113
|
-
def add_row
|
34
|
+
def add_row array
|
35
|
+
row = array == :separator ? Separator.new(self) : Row.new(self, array)
|
114
36
|
@rows << row
|
115
|
-
|
116
|
-
end
|
117
|
-
|
118
|
-
def recalc_column_lengths row
|
119
|
-
if row.is_a?(Symbol) then return end
|
120
|
-
i = 0
|
121
|
-
row.each do |cell|
|
122
|
-
if cell.is_a?(Hash)
|
123
|
-
colspan = cell[:colspan] || 1
|
124
|
-
cell_value = cell[:value]
|
125
|
-
else
|
126
|
-
colspan = 1
|
127
|
-
cell_value = cell
|
128
|
-
end
|
129
|
-
colspan.downto(1) do |j|
|
130
|
-
cell_length = cell_value.to_s.length
|
131
|
-
if colspan > 1
|
132
|
-
spacing_length = (3 * (colspan - 1))
|
133
|
-
length_in_columns = (cell_length - spacing_length)
|
134
|
-
cell_length = (length_in_columns.to_f / colspan).ceil
|
135
|
-
end
|
136
|
-
if (@column_lengths[i] || 0) < cell_length
|
137
|
-
@column_lengths[i] = cell_length
|
138
|
-
end
|
139
|
-
i = i + 1
|
140
|
-
end
|
141
|
-
end
|
37
|
+
recalc_column_widths row
|
142
38
|
end
|
143
39
|
alias :<< :add_row
|
144
40
|
|
@@ -146,121 +42,175 @@ module Terminal
|
|
146
42
|
# Add a separator.
|
147
43
|
|
148
44
|
def add_separator
|
149
|
-
|
45
|
+
self << :separator
|
46
|
+
end
|
47
|
+
|
48
|
+
def cell_spacing
|
49
|
+
cell_padding + style.border_y.length
|
150
50
|
end
|
151
|
-
|
152
|
-
##
|
153
|
-
# Weither or not any headings are present, since they are optional.
|
154
51
|
|
155
|
-
def
|
156
|
-
|
52
|
+
def cell_padding
|
53
|
+
style.padding_left + style.padding_right
|
157
54
|
end
|
158
55
|
|
159
56
|
##
|
160
57
|
# Return column _n_.
|
161
58
|
|
162
|
-
def column n
|
163
|
-
|
59
|
+
def column n, method = :value, array = rows
|
60
|
+
array.map { |row|
|
61
|
+
cell = row[n]
|
62
|
+
cell && method ? cell.__send__(method) : cell
|
63
|
+
}.compact
|
164
64
|
end
|
165
65
|
|
166
66
|
##
|
167
67
|
# Return _n_ column including headings.
|
168
68
|
|
169
|
-
def column_with_headings n
|
170
|
-
|
171
|
-
end
|
172
|
-
|
173
|
-
def row_with_hash row
|
174
|
-
# this method duplicates the multi-column columns in each column they are in
|
175
|
-
index = 0
|
176
|
-
row.inject [] do |columns, column|
|
177
|
-
if column.is_a?(Hash) && column[:colspan] && column[:colspan] > 1
|
178
|
-
column[:start_index] = index
|
179
|
-
column[:colspan].times do
|
180
|
-
columns << column
|
181
|
-
index += 1
|
182
|
-
end
|
183
|
-
else
|
184
|
-
columns << column
|
185
|
-
index += 1
|
186
|
-
end
|
187
|
-
columns
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def row_to_index row, index
|
192
|
-
new_index = -1
|
193
|
-
0.upto(index) do |i|
|
194
|
-
column = row[i]
|
195
|
-
if column.is_a?(Hash) && column[:colspan] && column[:colspan] > 1 && index != i
|
196
|
-
new_index = new_index + column[:colspan]
|
197
|
-
else
|
198
|
-
new_index += 1
|
199
|
-
end
|
200
|
-
end
|
201
|
-
return new_index
|
69
|
+
def column_with_headings n, method = :value
|
70
|
+
column n, method, headings_with_rows
|
202
71
|
end
|
203
72
|
|
204
73
|
##
|
205
74
|
# Return columns.
|
206
75
|
|
207
|
-
def columns
|
76
|
+
def columns
|
208
77
|
(0...number_of_columns).map { |n| column n }
|
209
78
|
end
|
210
79
|
|
211
80
|
##
|
212
81
|
# Return length of column _n_.
|
213
82
|
|
214
|
-
def
|
215
|
-
@
|
83
|
+
def column_width n
|
84
|
+
width = @column_widths[n] || 0
|
85
|
+
width + additional_column_widths[n].to_i
|
216
86
|
end
|
87
|
+
alias length_of_column column_width # for legacy support
|
217
88
|
|
218
89
|
##
|
219
90
|
# Return total number of columns available.
|
220
91
|
|
221
92
|
def number_of_columns
|
222
|
-
|
223
|
-
raise Error, 'your table needs some rows'
|
93
|
+
headings_with_rows.map { |r| r.cells.size }.max
|
224
94
|
end
|
225
|
-
|
95
|
+
|
226
96
|
##
|
227
|
-
#
|
97
|
+
# Set the headings
|
228
98
|
|
229
|
-
def
|
230
|
-
|
231
|
-
|
232
|
-
r[i][n] = { :value => col, :alignment => alignment } unless Hash === col
|
233
|
-
end
|
99
|
+
def headings= array
|
100
|
+
@headings = Row.new(self, array)
|
101
|
+
recalc_column_widths @headings
|
234
102
|
end
|
235
|
-
|
103
|
+
|
236
104
|
##
|
237
|
-
#
|
238
|
-
|
239
|
-
def
|
240
|
-
|
105
|
+
# Render the table.
|
106
|
+
|
107
|
+
def render
|
108
|
+
separator = Separator.new(self)
|
109
|
+
buffer = [separator]
|
110
|
+
unless @title.nil?
|
111
|
+
opts = {:value => @title, :alignment => :center, :colspan => number_of_columns}
|
112
|
+
buffer << Row.new(self, [opts])
|
113
|
+
buffer << separator
|
114
|
+
end
|
115
|
+
unless @headings.cells.empty?
|
116
|
+
buffer << @headings
|
117
|
+
buffer << separator
|
118
|
+
end
|
119
|
+
buffer += @rows
|
120
|
+
buffer << separator
|
121
|
+
buffer.map { |r| r.render }.join("\n")
|
241
122
|
end
|
242
|
-
|
123
|
+
alias :to_s :render
|
124
|
+
|
243
125
|
##
|
244
126
|
# Return rows without separator rows.
|
245
127
|
|
246
128
|
def rows
|
247
|
-
@rows.reject { |row| row
|
129
|
+
@rows.reject { |row| row.is_a? Separator }
|
248
130
|
end
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
def all_rows
|
254
|
-
@rows
|
131
|
+
|
132
|
+
def rows= array
|
133
|
+
@rows = []
|
134
|
+
array.each { |arr| self << arr }
|
255
135
|
end
|
256
136
|
|
137
|
+
def style=(options)
|
138
|
+
style.apply options
|
139
|
+
end
|
140
|
+
|
141
|
+
def style
|
142
|
+
@style ||= Style.new
|
143
|
+
end
|
144
|
+
|
145
|
+
def title=(title)
|
146
|
+
@title = title
|
147
|
+
recalc_column_widths Row.new(self, [title])
|
148
|
+
end
|
149
|
+
|
257
150
|
##
|
258
151
|
# Check if _other_ is equal to self. _other_ is considered equal
|
259
152
|
# if it contains the same headings and rows.
|
260
153
|
|
261
154
|
def == other
|
262
|
-
if other.respond_to? :
|
263
|
-
|
155
|
+
if other.respond_to? :render and other.respond_to? :rows
|
156
|
+
self.headings == other.headings and self.rows == other.rows
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def columns_width
|
163
|
+
@column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y.length
|
164
|
+
end
|
165
|
+
|
166
|
+
def additional_column_widths
|
167
|
+
return [] if style.width.nil?
|
168
|
+
spacing = style.width - columns_width
|
169
|
+
if spacing < 0
|
170
|
+
raise "Table width exceeds wanted width of #{wanted} characters."
|
171
|
+
else
|
172
|
+
per_col = spacing / number_of_columns
|
173
|
+
arr = (1...number_of_columns).to_a.map { |i| per_col }
|
174
|
+
other_cols = arr.inject(0) { |s, i| s + i }
|
175
|
+
arr << spacing - other_cols
|
176
|
+
arr
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def recalc_column_widths row
|
181
|
+
return if row.is_a? Separator
|
182
|
+
i = 0
|
183
|
+
row.cells.each do |cell|
|
184
|
+
colspan = cell.colspan
|
185
|
+
cell_value = cell.value_for_column_width_recalc
|
186
|
+
colspan.downto(1) do |j|
|
187
|
+
cell_length = cell_value.to_s.length
|
188
|
+
if colspan > 1
|
189
|
+
spacing_length = cell_spacing * (colspan - 1)
|
190
|
+
length_in_columns = (cell_length - spacing_length)
|
191
|
+
cell_length = (length_in_columns.to_f / colspan).ceil
|
192
|
+
end
|
193
|
+
if @column_widths[i].to_i < cell_length
|
194
|
+
@column_widths[i] = cell_length
|
195
|
+
end
|
196
|
+
i = i + 1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Return headings combined with rows.
|
203
|
+
|
204
|
+
def headings_with_rows
|
205
|
+
[@headings] + rows
|
206
|
+
end
|
207
|
+
|
208
|
+
def yield_or_eval &block
|
209
|
+
return unless block
|
210
|
+
if block.arity > 0
|
211
|
+
yield self
|
212
|
+
else
|
213
|
+
self.instance_eval(&block)
|
264
214
|
end
|
265
215
|
end
|
266
216
|
end
|