tty-table 0.1.0

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