terminal-table 1.4.3 → 1.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,28 +1,8 @@
1
1
 
2
2
  class String
3
3
  def align position, length
4
- send position, length
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
@@ -2,143 +2,39 @@
2
2
  module Terminal
3
3
  class Table
4
4
 
5
- #--
6
- # Exceptions
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
- @column_lengths = []
12
+ @column_widths = []
13
+ self.style = options.fetch :style, {}
14
+ self.title = options.fetch :title, nil
26
15
  self.headings = options.fetch :headings, []
27
- @rows = []
28
- options.fetch(:rows, []).each { |row| add_row row }
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
- # Render the given _row_.
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
- # Render the given _cell_ at index _i_.
87
-
88
- def render_cell cell, i
89
- width = 0
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 row
34
+ def add_row array
35
+ row = array == :separator ? Separator.new(self) : Row.new(self, array)
114
36
  @rows << row
115
- recalc_column_lengths row
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
- @rows << :separator
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 has_headings?
156
- not @headings.empty?
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
- rows.map { |row| row[n] }.compact
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
- headings_with_rows.map { |row| row_with_hash(row)[n] }.compact
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 length_of_column n
215
- @column_lengths[n] || 0
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
- return rows.first.length unless rows.empty?
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
- # Align column _n_ to the given _alignment_ of :center, :left, or :right.
97
+ # Set the headings
228
98
 
229
- def align_column n, alignment
230
- r = rows
231
- column(n).each_with_index do |col, i|
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
- # Return headings combined with rows.
238
-
239
- def headings_with_rows
240
- [@headings] + rows
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 == :separator }
129
+ @rows.reject { |row| row.is_a? Separator }
248
130
  end
249
-
250
- ##
251
- # Return rows including separator rows.
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? :headings and other.respond_to? :rows
263
- @headings == other.headings and rows == other.rows
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