tty-table 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +22 -0
  6. data/Gemfile +19 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +389 -0
  9. data/Rakefile +8 -0
  10. data/benchmarks/speed.rb +36 -0
  11. data/lib/tty/table/border/ascii.rb +32 -0
  12. data/lib/tty/table/border/null.rb +37 -0
  13. data/lib/tty/table/border/row_line.rb +18 -0
  14. data/lib/tty/table/border/unicode.rb +32 -0
  15. data/lib/tty/table/border.rb +219 -0
  16. data/lib/tty/table/border_dsl.rb +251 -0
  17. data/lib/tty/table/border_options.rb +53 -0
  18. data/lib/tty/table/column_set.rb +121 -0
  19. data/lib/tty/table/columns.rb +170 -0
  20. data/lib/tty/table/empty.rb +26 -0
  21. data/lib/tty/table/error.rb +39 -0
  22. data/lib/tty/table/field.rb +139 -0
  23. data/lib/tty/table/header.rb +163 -0
  24. data/lib/tty/table/indentation.rb +52 -0
  25. data/lib/tty/table/operation/alignment_set.rb +103 -0
  26. data/lib/tty/table/operation/escape.rb +30 -0
  27. data/lib/tty/table/operation/filter.rb +34 -0
  28. data/lib/tty/table/operation/padding.rb +95 -0
  29. data/lib/tty/table/operation/truncation.rb +41 -0
  30. data/lib/tty/table/operation/wrapped.rb +43 -0
  31. data/lib/tty/table/operations.rb +69 -0
  32. data/lib/tty/table/options.rb +30 -0
  33. data/lib/tty/table/orientation/horizontal.rb +48 -0
  34. data/lib/tty/table/orientation/vertical.rb +38 -0
  35. data/lib/tty/table/orientation.rb +57 -0
  36. data/lib/tty/table/padder.rb +180 -0
  37. data/lib/tty/table/renderer/ascii.rb +16 -0
  38. data/lib/tty/table/renderer/basic.rb +294 -0
  39. data/lib/tty/table/renderer/color.rb +12 -0
  40. data/lib/tty/table/renderer/unicode.rb +21 -0
  41. data/lib/tty/table/renderer.rb +101 -0
  42. data/lib/tty/table/row.rb +248 -0
  43. data/lib/tty/table/transformation.rb +39 -0
  44. data/lib/tty/table/validatable.rb +64 -0
  45. data/lib/tty/table/version.rb +7 -0
  46. data/lib/tty/table.rb +469 -0
  47. data/lib/tty-table.rb +48 -0
  48. data/spec/spec_helper.rb +51 -0
  49. data/spec/unit/access_spec.rb +86 -0
  50. data/spec/unit/add_row_spec.rb +28 -0
  51. data/spec/unit/border/ascii/rendering_spec.rb +90 -0
  52. data/spec/unit/border/new_spec.rb +27 -0
  53. data/spec/unit/border/null/rendering_spec.rb +130 -0
  54. data/spec/unit/border/options/from_spec.rb +38 -0
  55. data/spec/unit/border/options/new_spec.rb +14 -0
  56. data/spec/unit/border/unicode/rendering_spec.rb +63 -0
  57. data/spec/unit/border_options/new_spec.rb +20 -0
  58. data/spec/unit/border_options/update_spec.rb +18 -0
  59. data/spec/unit/column_set/extract_widths_spec.rb +15 -0
  60. data/spec/unit/column_set/total_width_spec.rb +15 -0
  61. data/spec/unit/column_set/widths_from_spec.rb +51 -0
  62. data/spec/unit/columns/enforce_spec.rb +67 -0
  63. data/spec/unit/columns/widths_spec.rb +35 -0
  64. data/spec/unit/data_spec.rb +14 -0
  65. data/spec/unit/each_spec.rb +41 -0
  66. data/spec/unit/each_with_index_spec.rb +57 -0
  67. data/spec/unit/empty_spec.rb +23 -0
  68. data/spec/unit/eql_spec.rb +34 -0
  69. data/spec/unit/field/equality_spec.rb +51 -0
  70. data/spec/unit/field/length_spec.rb +21 -0
  71. data/spec/unit/field/lines_spec.rb +21 -0
  72. data/spec/unit/field/new_spec.rb +29 -0
  73. data/spec/unit/field/width_spec.rb +23 -0
  74. data/spec/unit/filter_spec.rb +23 -0
  75. data/spec/unit/header/call_spec.rb +30 -0
  76. data/spec/unit/header/color_spec.rb +19 -0
  77. data/spec/unit/header/equality_spec.rb +51 -0
  78. data/spec/unit/header/height_spec.rb +27 -0
  79. data/spec/unit/header/new_spec.rb +25 -0
  80. data/spec/unit/header/set_spec.rb +20 -0
  81. data/spec/unit/header/to_ary_spec.rb +14 -0
  82. data/spec/unit/header_spec.rb +13 -0
  83. data/spec/unit/indentation/insert_indent_spec.rb +27 -0
  84. data/spec/unit/initialize_spec.rb +88 -0
  85. data/spec/unit/operation/alignment_set/call_spec.rb +39 -0
  86. data/spec/unit/operation/alignment_set/each_spec.rb +17 -0
  87. data/spec/unit/operation/alignment_set/new_spec.rb +27 -0
  88. data/spec/unit/operation/alignment_set/to_ary_spec.rb +14 -0
  89. data/spec/unit/operation/escape/call_spec.rb +16 -0
  90. data/spec/unit/operation/filter/call_spec.rb +17 -0
  91. data/spec/unit/operation/truncation/call_spec.rb +32 -0
  92. data/spec/unit/operation/wrapped/call_spec.rb +33 -0
  93. data/spec/unit/operations/new_spec.rb +30 -0
  94. data/spec/unit/options/access_spec.rb +14 -0
  95. data/spec/unit/options_spec.rb +25 -0
  96. data/spec/unit/orientation_spec.rb +145 -0
  97. data/spec/unit/padder/parse_spec.rb +45 -0
  98. data/spec/unit/padder/to_s_spec.rb +14 -0
  99. data/spec/unit/padding_spec.rb +120 -0
  100. data/spec/unit/properties_spec.rb +25 -0
  101. data/spec/unit/render_spec.rb +63 -0
  102. data/spec/unit/render_with_spec.rb +106 -0
  103. data/spec/unit/renderer/ascii/indentation_spec.rb +41 -0
  104. data/spec/unit/renderer/ascii/padding_spec.rb +61 -0
  105. data/spec/unit/renderer/ascii/render_spec.rb +68 -0
  106. data/spec/unit/renderer/ascii/resizing_spec.rb +114 -0
  107. data/spec/unit/renderer/ascii/separator_spec.rb +28 -0
  108. data/spec/unit/renderer/basic/alignment_spec.rb +88 -0
  109. data/spec/unit/renderer/basic/coloring_spec.rb +46 -0
  110. data/spec/unit/renderer/basic/extract_column_widths_spec.rb +28 -0
  111. data/spec/unit/renderer/basic/filter_spec.rb +53 -0
  112. data/spec/unit/renderer/basic/indentation_spec.rb +48 -0
  113. data/spec/unit/renderer/basic/multiline_content_spec.rb +135 -0
  114. data/spec/unit/renderer/basic/new_spec.rb +26 -0
  115. data/spec/unit/renderer/basic/options_spec.rb +52 -0
  116. data/spec/unit/renderer/basic/padding_spec.rb +52 -0
  117. data/spec/unit/renderer/basic/render_spec.rb +57 -0
  118. data/spec/unit/renderer/basic/resizing_spec.rb +96 -0
  119. data/spec/unit/renderer/basic/separator_spec.rb +43 -0
  120. data/spec/unit/renderer/basic/truncation_spec.rb +35 -0
  121. data/spec/unit/renderer/basic/wrapping_spec.rb +40 -0
  122. data/spec/unit/renderer/border_spec.rb +104 -0
  123. data/spec/unit/renderer/render_spec.rb +36 -0
  124. data/spec/unit/renderer/select_spec.rb +22 -0
  125. data/spec/unit/renderer/style_spec.rb +72 -0
  126. data/spec/unit/renderer/unicode/indentation_spec.rb +41 -0
  127. data/spec/unit/renderer/unicode/padding_spec.rb +61 -0
  128. data/spec/unit/renderer/unicode/render_spec.rb +68 -0
  129. data/spec/unit/renderer/unicode/separator_spec.rb +26 -0
  130. data/spec/unit/renderer_spec.rb +19 -0
  131. data/spec/unit/rotate_spec.rb +86 -0
  132. data/spec/unit/row/access_spec.rb +25 -0
  133. data/spec/unit/row/call_spec.rb +45 -0
  134. data/spec/unit/row/data_spec.rb +26 -0
  135. data/spec/unit/row/each_spec.rb +31 -0
  136. data/spec/unit/row/equality_spec.rb +73 -0
  137. data/spec/unit/row/height_spec.rb +27 -0
  138. data/spec/unit/row/new_spec.rb +41 -0
  139. data/spec/unit/row/to_ary_spec.rb +14 -0
  140. data/spec/unit/to_s_spec.rb +63 -0
  141. data/spec/unit/transformation/extract_tuples_spec.rb +35 -0
  142. data/spec/unit/validatable/validate_options_spec.rb +33 -0
  143. data/spec/unit/validatable_spec.rb +32 -0
  144. data/tasks/console.rake +10 -0
  145. data/tasks/coverage.rake +11 -0
  146. data/tasks/spec.rake +29 -0
  147. data/tty-table.gemspec +28 -0
  148. metadata +371 -0
@@ -0,0 +1,294 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty/table/validatable'
4
+
5
+ module TTY
6
+ class Table
7
+ class Renderer
8
+ # Renders table without any border styles.
9
+ #
10
+ # @api private
11
+ class Basic
12
+ include TTY::Table::Validatable
13
+
14
+ # Table to be rendered
15
+ #
16
+ # @return [TTY::Table]
17
+ #
18
+ # @api public
19
+ attr_reader :table
20
+ # private :table
21
+
22
+ # Table border to be rendered
23
+ #
24
+ # @return [TTY::Table::Border]
25
+ #
26
+ # @api private
27
+ attr_accessor :border_class
28
+
29
+ # The table enforced column widths
30
+ #
31
+ # @return [Array]
32
+ #
33
+ # @api public
34
+ attr_writer :column_widths
35
+
36
+ # The table column alignments
37
+ #
38
+ # @return [Array]
39
+ #
40
+ # @api private
41
+ attr_accessor :column_aligns
42
+
43
+ # The table operations applied to rows
44
+ #
45
+ # @api public
46
+ attr_reader :operations
47
+
48
+ # A callable object used for formatting field content
49
+ #
50
+ # @api public
51
+ attr_accessor :filter
52
+
53
+ # The table column span behaviour. When true the column's line breaks
54
+ # cause the column to span multiple rows. By default set to false.
55
+ #
56
+ # @return [Boolean]
57
+ #
58
+ # @api public
59
+ attr_accessor :multiline
60
+
61
+ # The table indentation value
62
+ #
63
+ # @return [Integer]
64
+ #
65
+ # @api public
66
+ attr_accessor :indent
67
+
68
+ # The table totabl width
69
+ #
70
+ # @return [Integer]
71
+ #
72
+ # @api public
73
+ attr_accessor :width
74
+
75
+ # The table resizing behaviour. If true the algorithm will
76
+ # automatically expand or shrink table to fit the terminal
77
+ # width or specified width. By default its false.
78
+ #
79
+ # @return [Integer]
80
+ #
81
+ # @api public
82
+ attr_accessor :resize
83
+
84
+ # The table padding settings
85
+ #
86
+ # @return [TTY::Table::Padder]
87
+ #
88
+ # @api public
89
+ attr_reader :padding
90
+
91
+ # Initialize a Renderer
92
+ #
93
+ # @param [Hash] options
94
+ # @option options [String] :column_aligns
95
+ # used to format table individual column alignment
96
+ # @option options [String] :column_widths
97
+ # used to format table individula column width
98
+ # @option options [Integer] :indent
99
+ # indent the first column by indent value
100
+ # @option options [Integer,Array] :padding
101
+ # add padding to table fields
102
+ #
103
+ # @return [TTY::Table::Renderer::Basic]
104
+ #
105
+ # @api private
106
+ def initialize(table, options = {})
107
+ @table = assert_table_type(table)
108
+ @multiline = options.fetch(:multiline) { false }
109
+ @operations = TTY::Table::Operations.new(table)
110
+ @operations.add(:escape, Operation::Escape.new)
111
+ @border = TTY::Table::BorderOptions.from(options.delete(:border))
112
+ @column_widths = options.fetch(:column_widths, nil)
113
+ @column_aligns = Array(options.delete(:column_aligns)).map(&:to_sym)
114
+ @filter = options.fetch(:filter) { proc { |val, _| val } }
115
+ @width = options.fetch(:width) { TTY::Screen.width }
116
+ @border_class = options.fetch(:border_class) { Border::Null }
117
+ @indent = options.fetch(:indent) { 0 }
118
+ @resize = options.fetch(:resize) { false }
119
+ @padding = TTY::Table::Padder.parse(options[:padding])
120
+ end
121
+
122
+ # Parses supplied column widths, if not present
123
+ # calculates natural widths.
124
+ #
125
+ # @return [Array[Integer]]
126
+ #
127
+ # @api public
128
+ def column_widths
129
+ @column_widths = ColumnSet.widths_from(table, @column_widths)
130
+ end
131
+
132
+ # Store border characters, style and separator for the table rendering
133
+ #
134
+ # @param [Hash, Table::BorderOptions] options
135
+ #
136
+ # @yield [Table::BorderOptions]
137
+ # block representing border options
138
+ #
139
+ # @api public
140
+ def border(options=(not_set=true), &block)
141
+ @border = TTY::Table::BorderOptions.new unless @border
142
+ if block_given?
143
+ border_dsl = TTY::Table::BorderDSL.new(&block)
144
+ @border = border_dsl.options
145
+ elsif !not_set
146
+ @border = TTY::Table::BorderOptions.from(options)
147
+ end
148
+ @border
149
+ end
150
+
151
+ # Initialize and add operations
152
+ #
153
+ # @api private
154
+ def add_operations
155
+ operations.add(:alignment, Operation::AlignmentSet.new(column_aligns,
156
+ column_widths))
157
+ operations.add(:filter, Operation::Filter.new(filter))
158
+ operations.add(:truncation, Operation::Truncation.new(column_widths))
159
+ operations.add(:wrapping, Operation::Wrapped.new(column_widths, padding))
160
+ operations.add(:padding, Operation::Padding.new(padding, multiline))
161
+ end
162
+
163
+ # Initializes indentation
164
+ #
165
+ # @return [TTY::Table::Indentation]
166
+ #
167
+ # @api private
168
+ def indentation
169
+ @indentation ||= TTY::Table::Indentation.new(self)
170
+ end
171
+
172
+ # Delegate indentation insertion
173
+ #
174
+ # @api public
175
+ def insert_indent(line)
176
+ indentation.insert_indent(line)
177
+ end
178
+
179
+ # Return column contraints
180
+ #
181
+ # @api private
182
+ def columns_constraints
183
+ TTY::Table::Columns.new(self)
184
+ end
185
+
186
+ # Sets the output padding,
187
+ #
188
+ # @param [Integer] value
189
+ # the amount of padding, not allowed to be zero
190
+ #
191
+ # @api public
192
+ def padding=(value)
193
+ @padding = TTY::Table::Padder.parse(value)
194
+ end
195
+
196
+ # Renders table
197
+ #
198
+ # @return [String] string representation of table
199
+ #
200
+ # @api public
201
+ def render
202
+ return if table.empty?
203
+
204
+ operations.run_operations(:escape) unless multiline
205
+ columns_constraints.enforce
206
+ add_operations
207
+ ops = [:alignment]
208
+ ops << :padding unless padding.empty?
209
+ multiline ? ops << :wrapping : ops << :truncation
210
+ ops << :filter
211
+ operations.run_operations(*ops)
212
+
213
+ render_data.compact.join("\n")
214
+ end
215
+
216
+ private
217
+
218
+ # Render table data
219
+ #
220
+ # @api private
221
+ def render_data
222
+ first_row = table.first
223
+ data_border = border_class.new(column_widths, border)
224
+ header = render_header(first_row, data_border)
225
+ rows_with_border = render_rows(data_border)
226
+ bottom_line = data_border.bottom_line
227
+
228
+ insert_indent(bottom_line) if bottom_line
229
+
230
+ [header, rows_with_border, bottom_line].compact
231
+ end
232
+
233
+ # Format the header if present
234
+ #
235
+ # @param [TTY::Table::Row, TTY::Table::Header] row
236
+ # the first row in the table
237
+ #
238
+ # @param [TTY::Table::Border] data_boder
239
+ # the border for this table
240
+ #
241
+ # @return [String]
242
+ #
243
+ # @api private
244
+ def render_header(row, data_border)
245
+ top_line = data_border.top_line
246
+ if row.is_a?(TTY::Table::Header)
247
+ header = [top_line, data_border.row_line(row), data_border.separator]
248
+ insert_indent(header.compact)
249
+ else
250
+ top_line
251
+ end
252
+ end
253
+
254
+ # Format the rows
255
+ #
256
+ # @param [TTY::Table::Border] data_boder
257
+ # the border for this table
258
+ #
259
+ # @return [Arrays[String]]
260
+ #
261
+ # @api private
262
+ def render_rows(data_border)
263
+ rows = table.rows
264
+ size = rows.size
265
+ rows.each_with_index.map do |row, index|
266
+ render_row(row, data_border, size != (index += 1))
267
+ end
268
+ end
269
+
270
+ # Format a single row with border
271
+ #
272
+ # @param [Array] row
273
+ # a row to decorate
274
+ #
275
+ # @param [TTY::Table::Border] data_boder
276
+ # the border for this table
277
+ #
278
+ # @param [Boolean] is_last_row
279
+ #
280
+ # @api private
281
+ def render_row(row, data_border, is_last_row)
282
+ separator = data_border.separator
283
+ row_line = data_border.row_line(row)
284
+
285
+ if (border.separator == TTY::Table::Border::EACH_ROW) && is_last_row
286
+ insert_indent([row_line, separator])
287
+ else
288
+ insert_indent(row_line)
289
+ end
290
+ end
291
+ end # Basic
292
+ end # Renderer
293
+ end # Table
294
+ end # TTY
@@ -0,0 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Table
5
+ class Renderer
6
+ class Color < Basic
7
+
8
+
9
+ end # Color
10
+ end # Renderer
11
+ end # Table
12
+ end # TTY
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Table
5
+ class Renderer
6
+ # Unicode representation of table renderer
7
+ #
8
+ # @api private
9
+ class Unicode < Basic
10
+ # Create Unicode renderer
11
+ #
12
+ # @param [Table] table
13
+ #
14
+ # @api private
15
+ def initialize(table, options = {})
16
+ super(table, options.merge(border_class: TTY::Table::Border::Unicode))
17
+ end
18
+ end # Unicode
19
+ end # Renderer
20
+ end # Table
21
+ end # TTY
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Table
5
+ # A class responsible for rendering tabular data
6
+ #
7
+ # Used internally by {Table} to render table content out.
8
+ #
9
+ # @api private
10
+ class Renderer
11
+ autoload :ASCII, 'tty/table/renderer/ascii'
12
+ autoload :Basic, 'tty/table/renderer/basic'
13
+ autoload :Color, 'tty/table/renderer/color'
14
+ autoload :Unicode, 'tty/table/renderer/unicode'
15
+
16
+ RENDERER_MAPPER = {
17
+ ascii: TTY::Table::Renderer::ASCII,
18
+ basic: TTY::Table::Renderer::Basic,
19
+ color: TTY::Table::Renderer::Color,
20
+ unicode: TTY::Table::Renderer::Unicode
21
+ }
22
+
23
+ # Select renderer class based on string name.
24
+ #
25
+ # The possible values for renderer are
26
+ # [:basic, :ascii, :unicode, :color]
27
+ #
28
+ # @param [Symbol] renderer
29
+ # the renderer used for displaying table
30
+ #
31
+ # @return [TTY::Table::Renderer]
32
+ #
33
+ # @api private
34
+ def self.select(type)
35
+ RENDERER_MAPPER[type || :basic]
36
+ end
37
+
38
+ # Raises an error if provided border class is of wrong type or has invalid
39
+ # implementation
40
+ #
41
+ # @raise [TypeError]
42
+ # raised when providing wrong class for border
43
+ #
44
+ # @raise [NoImplementationError]
45
+ # raised when border class does not implement core methods
46
+ #
47
+ # @api public
48
+ def self.assert_border_class(border_class)
49
+ return unless border_class
50
+ unless border_class <= TTY::Table::Border
51
+ fail TypeError,
52
+ "#{border_class} should inherit from TTY::Table::Border"
53
+ end
54
+ unless border_class.characters
55
+ fail NoImplementationError,
56
+ "#{border_class} should implement def_border"
57
+ end
58
+ end
59
+
60
+ # Add custom border for the renderer
61
+ #
62
+ # @param [TTY::Table::Border] border_class
63
+ #
64
+ # @param [TTY::Table] table
65
+ #
66
+ # @param [Hash] options
67
+ #
68
+ # @raise [TypeError]
69
+ # raised if the klass does not inherit from Table::Border
70
+ #
71
+ # @raise [NoImplemntationError]
72
+ # raise if the klass does not implement def_border
73
+ #
74
+ # @api public
75
+ def self.render_with(border_class, table, options = {}, &block)
76
+ assert_border_class(border_class)
77
+ options[:border_class] = border_class if border_class
78
+ render(table, options, &block)
79
+ end
80
+
81
+ # Render a given table and return the string representation.
82
+ #
83
+ # @param [TTY::Table] table
84
+ # the table to be rendered
85
+ #
86
+ # @param [Hash] options
87
+ # the options to render the table with
88
+ # @option options [String] :renderer
89
+ # used to format table output
90
+ #
91
+ # @return [String]
92
+ #
93
+ # @api public
94
+ def self.render(table, options = {}, &block)
95
+ renderer = select(options[:renderer]).new(table, options)
96
+ yield renderer if block_given?
97
+ renderer.render
98
+ end
99
+ end # Renderer
100
+ end # Table
101
+ end # TTY
@@ -0,0 +1,248 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Table
5
+ # Convert an Array row into Row
6
+ #
7
+ # @return [TTY::Table::Row]
8
+ #
9
+ # @api private
10
+ def to_row(row, header = nil)
11
+ Row.new(row, header)
12
+ end
13
+
14
+ # A class that represents a row in a table.
15
+ #
16
+ # Used internally by {Table} to store row represenation by converting
17
+ # {Array} into {Row} instance.
18
+ #
19
+ # @api private
20
+ class Row
21
+ include Enumerable, Equatable
22
+ extend Forwardable
23
+
24
+ # The row attributes that describe each element
25
+ #
26
+ # @return [Array]
27
+ #
28
+ # @api private
29
+ attr_reader :attributes
30
+
31
+ # The row data
32
+ #
33
+ # @return [Hash]
34
+ #
35
+ # @api private
36
+ attr_reader :data
37
+
38
+ # The row fields
39
+ #
40
+ # @api public
41
+ attr_reader :fields
42
+
43
+ def_delegators :to_ary, :join
44
+
45
+ # Initialize a Row
46
+ #
47
+ # @example
48
+ # row = new TTY::Table::Row.new [1,2,3]
49
+ # row[1] # => 2
50
+ #
51
+ # row = new TTY::Table::Row.new [1,2,3], %w[a b c]
52
+ # row[0] # => 1
53
+ # row['a'] # => 1
54
+ #
55
+ # row = new TTY::Table::Row.new {"a": 1, "b": 2, "c": 3}
56
+ # row[0] # => 1
57
+ # row['a'] # => 1
58
+ #
59
+ # @param [#to_ary] data
60
+ # the row data
61
+ #
62
+ # @return [undefined]
63
+ #
64
+ # @api public
65
+ def initialize(data, header = nil)
66
+ case data
67
+ when Array
68
+ @attributes = (header || (0...data.length)).to_a
69
+ @fields = coerce_to_fields(data)
70
+ when Hash
71
+ @data = data.dup
72
+ @fields = coerce_to_fields(@data.values)
73
+ @attributes = (header || data.keys).to_a
74
+ end
75
+ @data = Hash[@attributes.zip(fields)]
76
+ end
77
+
78
+ # Coerces values to field instances
79
+ #
80
+ # @param [Array[Object]] values
81
+ #
82
+ # @return [Array[TTY::Table::Field]]
83
+ #
84
+ # @api public
85
+ def coerce_to_fields(values)
86
+ values.reduce([]) { |acc, el| acc << to_field(el) }
87
+ end
88
+
89
+ # Instantiates a new field
90
+ #
91
+ # @api public
92
+ def to_field(options = nil)
93
+ Field.new(options)
94
+ end
95
+
96
+ # Lookup a value in the row given an attribute allowing for Array or
97
+ # Hash like indexing
98
+ #
99
+ # @exmaple
100
+ # row[1]
101
+ # row[:id]
102
+ # row.call(:id)
103
+ #
104
+ # @api public
105
+ def [](attribute)
106
+ case attribute
107
+ when Integer
108
+ data[attributes[attribute]].value
109
+ else
110
+ data.fetch(attribute) do |name|
111
+ fail UnknownAttributeError, "the attribute #{name} is unkown"
112
+ end.value
113
+ end
114
+ end
115
+
116
+ # Lookup attribute without evaluation
117
+ #
118
+ # @api public
119
+ def call(attribute)
120
+ data[attributes[attribute]]
121
+ end
122
+
123
+ # Set value at index
124
+ #
125
+ # @example
126
+ # row[attribute] = value
127
+ #
128
+ # @api public
129
+ def []=(attribute, value)
130
+ case attribute
131
+ when Integer
132
+ data[attributes[attribute]] = to_field(value)
133
+ else
134
+ data[attribute] = to_field(value)
135
+ attributes << attribute unless attributes.include?(attribute)
136
+ end
137
+ end
138
+
139
+ # Iterate over each element in the Row
140
+ #
141
+ # @example
142
+ # vec = Row.new [1,2,3], ['a','b','c']
143
+ # vec.each { |element| ... }
144
+ #
145
+ # @return [self]
146
+ #
147
+ # @api public
148
+ def each
149
+ return to_enum unless block_given?
150
+ to_ary.each { |element| yield element }
151
+ self
152
+ end
153
+
154
+ # Number of data items in a row
155
+ #
156
+ # @return [Integer]
157
+ #
158
+ # @api public
159
+ def size
160
+ data.size
161
+ end
162
+ alias :length :size
163
+
164
+ # Check if there are no elements
165
+ #
166
+ # @return [Boolean]
167
+ #
168
+ # @api public
169
+ def empty?
170
+ to_ary.empty?
171
+ end
172
+
173
+ # Find maximum row height
174
+ #
175
+ # @return [Integer]
176
+ #
177
+ # @api public
178
+ def height
179
+ fields.map(&:height).max
180
+ end
181
+
182
+ # Convert the Row into Array
183
+ #
184
+ # @example
185
+ # array = row.to_ary
186
+ #
187
+ # @return [Array]
188
+ #
189
+ # @api public
190
+ def to_ary
191
+ to_hash.values_at(*attributes)
192
+ end
193
+
194
+ # Return the Row elements in an array.
195
+ #
196
+ # @return [Array]
197
+ #
198
+ # @api public
199
+ def to_a
200
+ to_ary.dup
201
+ end
202
+
203
+ # Convert the Row into hash
204
+ #
205
+ # @return [Hash]
206
+ #
207
+ # @api public
208
+ def to_hash
209
+ hash = data.dup
210
+ hash.update(hash) { |_, val| val.value if val }
211
+ end
212
+
213
+ # Check if this row is equivalent to another row
214
+ #
215
+ # @return [Boolean]
216
+ #
217
+ # @api public
218
+ def ==(other)
219
+ to_a == other.to_a
220
+ end
221
+ alias :eql? :==
222
+
223
+ # Provide a unique hash value. If a row contains the same data as another
224
+ # row, they will hash to the same value.
225
+ #
226
+ # @api public
227
+ def hash
228
+ to_a.hash
229
+ end
230
+
231
+ # Map field values
232
+ #
233
+ # @api public
234
+ def map!(&block)
235
+ data.values_at(*attributes).each do |field|
236
+ field.value = block.call(field)
237
+ end
238
+ end
239
+
240
+ # String representation of a row with its fields
241
+ #
242
+ # @api public
243
+ def inspect
244
+ "#<#{self.class.name} fields=#{to_a}>"
245
+ end
246
+ end # Row
247
+ end # Table
248
+ end # TTY
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Table
5
+ # A class for transforming table values
6
+ #
7
+ # Used internally by {Table}
8
+ #
9
+ # @api private
10
+ class Transformation
11
+ # Extract the header and row tuples from the value
12
+ #
13
+ # @param [Array] args
14
+ #
15
+ # @return [Object]
16
+ #
17
+ # @api public
18
+ def self.extract_tuples(args)
19
+ rows = args.pop
20
+ header = args.size.zero? ? nil : args.first
21
+ if rows.first.is_a?(Hash)
22
+ header, rows = group_header_and_rows(rows)
23
+ end
24
+ { header: header, rows: rows }
25
+ end
26
+
27
+ # Group hash keys into header and values into rows
28
+ #
29
+ # @params [Hash] value
30
+ #
31
+ # @api public
32
+ def self.group_header_and_rows(value)
33
+ header = value.map(&:keys).flatten.uniq
34
+ rows = value.reduce([]) { |arr, el| arr + el.values }
35
+ [header, rows]
36
+ end
37
+ end # Transformation
38
+ end # Table
39
+ end # TTY