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.
@@ -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