tty 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.rspec +2 -1
  2. data/.travis.yml +3 -6
  3. data/README.md +232 -134
  4. data/lib/tty/plugins/plugin.rb +56 -0
  5. data/lib/tty/plugins.rb +75 -0
  6. data/lib/tty/shell/suggestion.rb +102 -0
  7. data/lib/tty/shell.rb +41 -14
  8. data/lib/tty/system/editor.rb +111 -0
  9. data/lib/tty/system/which.rb +13 -1
  10. data/lib/tty/system.rb +44 -28
  11. data/lib/tty/table/border/null.rb +0 -9
  12. data/lib/tty/table/border/row_line.rb +21 -0
  13. data/lib/tty/table/border.rb +63 -32
  14. data/lib/tty/table/border_dsl.rb +1 -1
  15. data/lib/tty/table/column_set.rb +16 -17
  16. data/lib/tty/table/field.rb +27 -7
  17. data/lib/tty/table/header.rb +18 -9
  18. data/lib/tty/table/operation/alignment_set.rb +20 -25
  19. data/lib/tty/table/operation/escape.rb +30 -0
  20. data/lib/tty/table/operation/filter.rb +36 -0
  21. data/lib/tty/table/operation/truncation.rb +22 -11
  22. data/lib/tty/table/operation/wrapped.rb +21 -10
  23. data/lib/tty/table/operations.rb +10 -8
  24. data/lib/tty/table/orientation/horizontal.rb +1 -1
  25. data/lib/tty/table/renderer/ascii.rb +3 -3
  26. data/lib/tty/table/renderer/basic.rb +135 -65
  27. data/lib/tty/table/renderer/color.rb +1 -4
  28. data/lib/tty/table/renderer/unicode.rb +3 -3
  29. data/lib/tty/table/renderer.rb +48 -61
  30. data/lib/tty/table/row.rb +30 -3
  31. data/lib/tty/table/transformation.rb +38 -0
  32. data/lib/tty/table/validatable.rb +7 -5
  33. data/lib/tty/table.rb +78 -99
  34. data/lib/tty/terminal/color.rb +2 -2
  35. data/lib/tty/terminal/echo.rb +1 -1
  36. data/lib/tty/terminal/pager/basic.rb +52 -0
  37. data/lib/tty/terminal/pager/system.rb +39 -0
  38. data/lib/tty/terminal/pager.rb +95 -0
  39. data/lib/tty/terminal.rb +30 -1
  40. data/lib/tty/version.rb +1 -1
  41. data/lib/tty.rb +41 -1
  42. data/spec/spec_helper.rb +20 -0
  43. data/spec/tty/plugins/find_spec.rb +28 -0
  44. data/spec/tty/plugins/load_spec.rb +20 -0
  45. data/spec/tty/plugins/plugin/load_spec.rb +30 -0
  46. data/spec/tty/plugins/plugin/new_spec.rb +18 -0
  47. data/spec/tty/shell/suggest_spec.rb +50 -0
  48. data/spec/tty/support/conversion_spec.rb +3 -3
  49. data/spec/tty/support/delegatable_spec.rb +1 -1
  50. data/spec/tty/support/equatable_spec.rb +6 -9
  51. data/spec/tty/system/editor/available_spec.rb +40 -0
  52. data/spec/tty/system/editor/build_spec.rb +40 -0
  53. data/spec/tty/system/editor/command_spec.rb +16 -0
  54. data/spec/tty/system/editor/executables_spec.rb +13 -0
  55. data/spec/tty/system/editor/invoke_spec.rb +38 -0
  56. data/spec/tty/system/editor/open_spec.rb +27 -0
  57. data/spec/tty/system/platform_spec.rb +4 -6
  58. data/spec/tty/system/which/which_spec.rb +48 -0
  59. data/spec/tty/system/which_spec.rb +8 -34
  60. data/spec/tty/table/border/ascii/rendering_spec.rb +19 -5
  61. data/spec/tty/table/border/new_spec.rb +1 -1
  62. data/spec/tty/table/border/null/rendering_spec.rb +24 -8
  63. data/spec/tty/table/border/unicode/rendering_spec.rb +19 -5
  64. data/spec/tty/table/column_set/extract_widths_spec.rb +4 -15
  65. data/spec/tty/table/column_set/total_width_spec.rb +15 -0
  66. data/spec/tty/table/data_spec.rb +14 -0
  67. data/spec/tty/table/each_spec.rb +17 -4
  68. data/spec/tty/table/each_with_index_spec.rb +34 -6
  69. data/spec/tty/table/field/length_spec.rb +21 -0
  70. data/spec/tty/table/field/lines_spec.rb +21 -0
  71. data/spec/tty/table/filter_spec.rb +23 -0
  72. data/spec/tty/table/header/call_spec.rb +1 -1
  73. data/spec/tty/table/header/height_spec.rb +27 -0
  74. data/spec/tty/table/initialize_spec.rb +6 -6
  75. data/spec/tty/table/operation/alignment_set/call_spec.rb +39 -0
  76. data/spec/tty/table/operation/escape/call_spec.rb +16 -0
  77. data/spec/tty/table/operation/filter/call_spec.rb +17 -0
  78. data/spec/tty/table/operation/truncation/call_spec.rb +15 -10
  79. data/spec/tty/table/operation/truncation/truncate_spec.rb +1 -1
  80. data/spec/tty/table/operation/wrapped/call_spec.rb +15 -10
  81. data/spec/tty/table/operation/wrapped/wrap_spec.rb +1 -1
  82. data/spec/tty/table/operations/new_spec.rb +4 -4
  83. data/spec/tty/table/options_spec.rb +0 -28
  84. data/spec/tty/table/orientation_spec.rb +5 -6
  85. data/spec/tty/table/properties_spec.rb +1 -4
  86. data/spec/tty/table/render_spec.rb +57 -0
  87. data/spec/tty/table/{renders_with_spec.rb → render_with_spec.rb} +29 -10
  88. data/spec/tty/table/renderer/ascii/render_spec.rb +68 -0
  89. data/spec/tty/table/renderer/ascii/separator_spec.rb +28 -0
  90. data/spec/tty/table/renderer/basic/alignment_spec.rb +18 -16
  91. data/spec/tty/table/renderer/basic/extract_column_widths_spec.rb +17 -12
  92. data/spec/tty/table/renderer/basic/filter_spec.rb +53 -0
  93. data/spec/tty/table/renderer/basic/multiline_content_spec.rb +135 -0
  94. data/spec/tty/table/renderer/basic/new_spec.rb +13 -2
  95. data/spec/tty/table/renderer/basic/options_spec.rb +48 -0
  96. data/spec/tty/table/renderer/basic/render_spec.rb +19 -121
  97. data/spec/tty/table/renderer/basic/separator_spec.rb +14 -48
  98. data/spec/tty/table/renderer/basic/truncation_spec.rb +35 -0
  99. data/spec/tty/table/renderer/basic/wrapping_spec.rb +40 -0
  100. data/spec/tty/table/{border_spec.rb → renderer/border_spec.rb} +17 -20
  101. data/spec/tty/table/renderer/select_spec.rb +22 -0
  102. data/spec/tty/table/{border → renderer}/style_spec.rb +13 -14
  103. data/spec/tty/table/renderer/unicode/render_spec.rb +68 -0
  104. data/spec/tty/table/renderer/unicode/separator_spec.rb +26 -0
  105. data/spec/tty/table/rotate_spec.rb +2 -3
  106. data/spec/tty/table/row/call_spec.rb +1 -1
  107. data/spec/tty/table/row/each_spec.rb +31 -0
  108. data/spec/tty/table/row/height_spec.rb +27 -0
  109. data/spec/tty/table/to_s_spec.rb +3 -3
  110. data/spec/tty/table/transformation/extract_tuples_spec.rb +35 -0
  111. data/spec/tty/table/validatable/validate_options_spec.rb +1 -2
  112. data/spec/tty/terminal/home_spec.rb +3 -3
  113. data/spec/tty/terminal/page_spec.rb +13 -0
  114. data/spec/tty/terminal/pager/available_spec.rb +40 -0
  115. data/spec/tty/terminal/pager/basic/page_spec.rb +54 -0
  116. data/spec/tty/terminal/pager/command_spec.rb +16 -0
  117. data/spec/tty/terminal/pager/executables_spec.rb +13 -0
  118. data/spec/tty/terminal/pager/page_spec.rb +47 -0
  119. data/spec/tty/terminal/pager/system/page_spec.rb +29 -0
  120. data/spec/tty/text/distance/distance_spec.rb +12 -0
  121. data/tty.gemspec +7 -3
  122. metadata +160 -27
  123. data/spec/tty/table/operation/alignment_set/align_rows_spec.rb +0 -53
  124. data/spec/tty/table/renderer/pick_renderer_spec.rb +0 -25
  125. data/spec/tty/table/renderer_spec.rb +0 -49
@@ -1,12 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
+ require 'tty/table/validatable'
4
+
3
5
  module TTY
4
6
  class Table
5
- module Renderer
7
+ class Renderer
6
8
 
7
9
  # Renders table without any border styles.
8
10
  class Basic
9
- extend TTY::Delegatable
11
+ include TTY::Table::Validatable
10
12
 
11
13
  # Table to be rendered
12
14
  #
@@ -14,40 +16,108 @@ module TTY
14
16
  #
15
17
  # @api public
16
18
  attr_reader :table
19
+ private :table
17
20
 
18
21
  # Table border to be rendered
19
22
  #
20
23
  # @return [TTY::Table::Border]
21
24
  #
22
25
  # @api private
23
- attr_reader :border_class
26
+ attr_accessor :border_class
24
27
 
25
- TABLE_DELEGATED_METHODS = [:column_widths, :column_aligns]
28
+ # The table enforced column widths
29
+ #
30
+ # @return [Array]
31
+ #
32
+ # @api public
33
+ attr_accessor :column_widths
26
34
 
27
- delegatable_method :table, *TABLE_DELEGATED_METHODS
35
+ # The table column alignments
36
+ #
37
+ # @return [Array]
38
+ #
39
+ # @api private
40
+ attr_accessor :column_aligns
28
41
 
29
- # Initialize and setup a Renderer
42
+ # The table operations applied to rows
43
+ #
44
+ # @api public
45
+ attr_reader :operations
46
+
47
+ # A callable object used for formatting field content
48
+ #
49
+ # @api public
50
+ attr_accessor :filter
51
+
52
+ # The table column span behaviour. When true the column's line breaks
53
+ # cause the column to span multiple rows. By default set to false.
54
+ #
55
+ # @return [Boolean]
56
+ #
57
+ # @api public
58
+ attr_accessor :multiline
59
+
60
+ # Initialize a Renderer
30
61
  #
31
62
  # @param [Hash] options
63
+ # @option options [String] :column_aligns
64
+ # used to format table individual column alignment
65
+ # @option options [String] :column_widths
66
+ # used to format table individula column width
67
+ #
32
68
  # :indent - Indent the first column by indent value
33
69
  # :padding - Pad out the row cell by padding value
34
70
  #
35
- # @return [Table::Renderer::Basic]
36
- def initialize(options={})
37
- setup(options)
71
+ # @return [TTY::Table::Renderer::Basic]
72
+ #
73
+ # @api private
74
+ def initialize(table, options={})
75
+ validate_rendering_options!(options)
76
+ @table = table || (raise ArgumentRequired, "Expected TTY::Table instance, got #{table.inspect}")
77
+ @multiline = options.fetch(:multiline) { false }
78
+ @operations = TTY::Table::Operations.new(table)
79
+ unless multiline
80
+ @operations.add_operation(:escape, Operation::Escape.new)
81
+ @operations.run_operations(:escape)
82
+ end
83
+
84
+ @border = TTY::Table::BorderOptions.from(options.delete(:border))
85
+ @column_widths = Array(options.fetch(:column_widths) {
86
+ ColumnSet.new(table).extract_widths
87
+ }).map(&:to_i)
88
+ @column_aligns = Array(options.delete(:column_aligns)).map(&:to_sym)
89
+ @filter = options.fetch(:filter) { proc { |val, row, col| val } }
90
+ @width = options.fetch(:width) { TTY.terminal.width }
91
+ @border_class = options.fetch(:border_class) { Border::Null }
38
92
  end
39
93
 
40
- # Setup attributes when Renderer is invoked
94
+ # Store border characters, style and separator for the table rendering
95
+ #
96
+ # @param [Hash, BorderOptions] options
41
97
  #
42
- # @return [self]
98
+ # @yield [] block representing border options
99
+ #
100
+ # @api public
101
+ def border(options=(not_set=true), &block)
102
+ @border = TTY::Table::BorderOptions.new unless @border
103
+ if block_given?
104
+ border_dsl = TTY::Table::BorderDSL.new(&block)
105
+ @border = border_dsl.options
106
+ elsif !not_set
107
+ @border = TTY::Table::BorderOptions.from(options)
108
+ end
109
+ @border
110
+ end
111
+
112
+ # Initialize and add operations
43
113
  #
44
114
  # @api private
45
- def setup(options = {})
46
- @padding = 0
47
- @indent = options.fetch :indent, 0
48
- self
115
+ def add_operations
116
+ operations.add_operation(:alignment, Operation::AlignmentSet.new(column_aligns, column_widths))
117
+ operations.add_operation(:filter, Operation::Filter.new(filter))
118
+ operations.add_operation(:truncation, Operation::Truncation.new(column_widths))
119
+ operations.add_operation(:wrapping, Operation::Wrapped.new(column_widths))
49
120
  end
50
- private :setup
51
121
 
52
122
  # Sets the output padding,
53
123
  #
@@ -59,74 +129,72 @@ module TTY
59
129
  @padding = [0, value].max
60
130
  end
61
131
 
62
- # @api public
63
- def self.render(table, options={})
64
- new(options).render(table)
65
- end
66
-
67
132
  # Renders table
68
133
  #
69
- # @param [TTY::Table] table
70
- # the table to be rendered
71
- #
72
134
  # @return [String] string representation of table
73
135
  #
74
136
  # @api public
75
- def render(table, border_class=Border::Null)
76
- @table = table
77
- @border_class = table.border_class || border_class
137
+ def render
78
138
  return if table.empty?
79
139
 
80
- body = []
81
- unless table.length.zero?
82
- ColumnSet.new(table).extract_widths!
83
- # TODO: throw an error if too many columns as compared to terminal width
84
- # and then change table.orientation from vertical to horizontal
85
- # TODO: Decide about table orientation
86
- body += render_header
87
- body += render_rows
88
- end
89
- body.compact.join("\n")
140
+ # TODO: throw an error if too many columns as compared to terminal width
141
+ # and then change table.orientation from vertical to horizontal
142
+ # TODO: Decide about table orientation
143
+ add_operations
144
+ ops = [:filter, :alignment]
145
+ multiline ? ops << :wrapping : ops << :truncation
146
+ operations.run_operations(*ops)
147
+ render_data.compact.join("\n")
90
148
  end
91
149
 
92
150
  private
93
151
 
94
- # Format the header
152
+ # Render table data
153
+ #
154
+ # @api private
155
+ def render_data
156
+ first_row = table.first
157
+ data_border = border_class.new(column_widths, border)
158
+ header = render_header(first_row, data_border)
159
+ rows_with_border = render_rows(data_border)
160
+
161
+ [header, rows_with_border, data_border.bottom_line].compact
162
+ end
163
+
164
+ # Format the header if present
165
+ #
166
+ # @param [TTY::Table::Row, TTY::Table::Header] row
167
+ # the first row in the table
168
+ #
169
+ # @param [TTY::Table::Border] data_boder
170
+ # the border for this table
95
171
  #
96
- # @return [Array[String]]
172
+ # @return [String]
97
173
  #
98
174
  # @api private
99
- def render_header
100
- header = table.header
101
- if header && !header.empty?
102
- operations = table.operations
103
- operations.run_operations(:alignment, header)
104
- border = border_class.new(header, table.border)
105
- [ border.top_line, border.row_line ].compact
175
+ def render_header(row, data_border)
176
+ top_line = data_border.top_line
177
+ if row.is_a?(TTY::Table::Header)
178
+ [top_line, data_border.row_line(row), data_border.separator].compact
106
179
  else
107
- []
180
+ top_line
108
181
  end
109
182
  end
110
183
 
111
184
  # Format the rows
112
185
  #
186
+ # @param [TTY::Table::Border] data_boder
187
+ # the border for this table
188
+ #
113
189
  # @return [Arrays[String]]
114
190
  #
115
191
  # @api private
116
- def render_rows
117
- operations = table.operations
118
- table.each do |row|
119
- operations.run_operations(:alignment, row)
192
+ def render_rows(data_border)
193
+ rows = table.rows
194
+ size = rows.size
195
+ rows.each_with_index.map do |row, index|
196
+ render_row(row, data_border, size != (index += 1))
120
197
  end
121
- aligned = table.to_a
122
- first_row_border = border_class.new(aligned.first, table.border)
123
- aligned_border = aligned.each_with_index.map { |row, index|
124
- render_row(row, aligned.size != (index += 1))
125
- }
126
-
127
- [ table.header ? first_row_border.separator : first_row_border.top_line,
128
- aligned_border,
129
- first_row_border.bottom_line ].compact
130
198
  end
131
199
 
132
200
  # Format a single row with border
@@ -134,15 +202,17 @@ module TTY
134
202
  # @param [Array] row
135
203
  # a row to decorate
136
204
  #
205
+ # @param [TTY::Table::Border] data_boder
206
+ # the border for this table
207
+ #
137
208
  # @param [Boolean] is_last_row
138
209
  #
139
210
  # @api private
140
- def render_row(row, is_last_row)
141
- border = border_class.new(row, table.border)
142
- separator = border.separator
143
- row_line = border.row_line
211
+ def render_row(row, data_border, is_last_row)
212
+ separator = data_border.separator
213
+ row_line = data_border.row_line(row)
144
214
 
145
- if (table.border.separator == TTY::Table::Border::EACH_ROW) && is_last_row
215
+ if (border.separator == TTY::Table::Border::EACH_ROW) && is_last_row
146
216
  [row_line, separator]
147
217
  else
148
218
  row_line
@@ -2,12 +2,9 @@
2
2
 
3
3
  module TTY
4
4
  class Table
5
- module Renderer
5
+ class Renderer
6
6
  class Color < Basic
7
7
 
8
- def initialize
9
-
10
- end
11
8
 
12
9
  end # Color
13
10
  end # Renderer
@@ -2,11 +2,11 @@
2
2
 
3
3
  module TTY
4
4
  class Table
5
- module Renderer
5
+ class Renderer
6
6
  class Unicode < Basic
7
7
 
8
- def render(table)
9
- super table, TTY::Table::Border::Unicode
8
+ def initialize(table, options={})
9
+ super(table, options.merge(:border_class => TTY::Table::Border::Unicode))
10
10
  end
11
11
 
12
12
  end # Unicode
@@ -3,39 +3,13 @@
3
3
  module TTY
4
4
  class Table
5
5
 
6
- # Determine renderer based on terminal capabilities
7
- #
8
- # @return [TTY::Table::Renderer]
9
- #
10
- # @api public
11
- def self.renderer
12
- @renderer ||= if TTY.terminal.color?
13
- TTY::Table::Renderer::Color
14
- else
15
- TTY::Table::Renderer::Basic
16
- end
17
- end
18
-
19
- # @api public
20
- def self.renderer=(klass)
21
- @renderer = klass
22
- end
23
-
24
- # A mixin to allow common rendering methods
25
- #
26
- # @return [self]
27
- #
28
- # @api public
29
- module Renderer
30
- extend TTY::Delegatable
31
-
6
+ # A class responsible for rendering tabular data
7
+ class Renderer
32
8
  autoload :ASCII, 'tty/table/renderer/ascii'
33
9
  autoload :Basic, 'tty/table/renderer/basic'
34
10
  autoload :Color, 'tty/table/renderer/color'
35
11
  autoload :Unicode, 'tty/table/renderer/unicode'
36
12
 
37
- RENDERER_DELEGATED_METHODS = [ :render, :total_width]
38
-
39
13
  RENDERER_MAPPER = {
40
14
  :ascii => TTY::Table::Renderer::ASCII,
41
15
  :basic => TTY::Table::Renderer::Basic,
@@ -43,44 +17,45 @@ module TTY
43
17
  :unicode => TTY::Table::Renderer::Unicode
44
18
  }
45
19
 
46
- # Initialize a Renderer
47
- #
48
- # @api private
49
- def initialize(options={})
50
- super()
51
- end
52
-
53
- # Determine renderer class based on string name
20
+ # Select renderer class based on string name
54
21
  #
55
22
  # @param [Symbol] renderer
56
- # the renderer used for displaying table out of [:basic, :color, :unicode]
23
+ # the renderer used for displaying table out of [:basic, :ascii, :unicode, :color]
57
24
  #
58
25
  # @return [TTY::Table::Renderer]
59
26
  #
60
27
  # @api private
61
- def pick_renderer(type=nil)
62
- self.renderer= (type ? RENDERER_MAPPER[type].new : self.renderer)
28
+ def self.select(type)
29
+ RENDERER_MAPPER[type || :basic]
63
30
  end
64
31
 
65
- # Return the default renderer
32
+ # Raises an error if provided border class is of wrong type or has invalid
33
+ # implementation
66
34
  #
67
- # @return [TTY::Table::Renderer]
35
+ # @raise [TypeError]
36
+ # raised when providing wrong class for border
37
+ #
38
+ # @raise [NoImplementationError]
39
+ # raised when border class does not implement core methods
68
40
  #
69
41
  # @api public
70
- def renderer
71
- @renderer ||= TTY::Table.renderer.new
42
+ def self.assert_border_class(border_class)
43
+ return unless border_class
44
+ unless border_class <= TTY::Table::Border
45
+ raise TypeError, "#{border_class} should inherit from TTY::Table::Border"
46
+ end
47
+ unless border_class.characters
48
+ raise NoImplementationError, "#{border_class} should implement def_border"
49
+ end
72
50
  end
73
51
 
74
- # Set the renderer
52
+ # Add custom border for the renderer
75
53
  #
76
- # @return [TTY::Table::Renderer]
54
+ # @param [TTY::Table::Border] border_class
77
55
  #
78
- # @api private
79
- def renderer=(renderer)
80
- @renderer = renderer
81
- end
82
-
83
- # Add custom border for the renderer
56
+ # @param [TTY::Table] table
57
+ #
58
+ # @param [Hash] options
84
59
  #
85
60
  # @raise [TypeError]
86
61
  # raised if the klass does not inherit from Table::Border
@@ -89,18 +64,30 @@ module TTY
89
64
  # raise if the klass does not implement def_border
90
65
  #
91
66
  # @api public
92
- def renders_with(klass)
93
- unless klass <= TTY::Table::Border
94
- raise TypeError, "#{klass} should inherit from TTY::Table::Border"
95
- end
96
- unless klass.characters
97
- raise NoImplementationError, "#{klass} should implement def_border"
98
- end
99
- @border_class = klass
67
+ def self.render_with(border_class, table, options={}, &block)
68
+ assert_border_class(border_class)
69
+ options[:border_class] = border_class if border_class
70
+ render(table, options, &block)
100
71
  end
101
72
 
102
- delegatable_method :renderer, *RENDERER_DELEGATED_METHODS
103
-
73
+ # Render a given table and return the string representation.
74
+ #
75
+ # @param [TTY::Table] table
76
+ # the table to be rendered
77
+ #
78
+ # @param [Hash] options
79
+ # the options to render the table with
80
+ # @option options [String] :renderer
81
+ # used to format table output
82
+ #
83
+ # @return [String]
84
+ #
85
+ # @api public
86
+ def self.render(table, options={}, &block)
87
+ renderer = select(options[:renderer]).new(table, options)
88
+ yield renderer if block_given?
89
+ renderer.render
90
+ end
104
91
  end # Renderer
105
92
 
106
93
  end # Table
data/lib/tty/table/row.rb CHANGED
@@ -35,6 +35,8 @@ module TTY
35
35
  # @api private
36
36
  attr_reader :data
37
37
 
38
+ attr_reader :fields
39
+
38
40
  # Initialize a Row
39
41
  #
40
42
  # @example
@@ -59,11 +61,11 @@ module TTY
59
61
  case data
60
62
  when Array
61
63
  @attributes = (header || (0...data.length)).to_a
62
- fields = data.inject([]) { |arr, datum| arr << to_field(datum) }
64
+ @fields = data.inject([]) { |arr, datum| arr << to_field(datum) }
63
65
  @data = Hash[@attributes.zip(fields)]
64
66
  when Hash
65
67
  @data = data.dup
66
- fields = @data.values.inject([]){|arr, datum| arr << to_field(datum) }
68
+ @fields = @data.values.inject([]){|arr, datum| arr << to_field(datum) }
67
69
  @attributes = (header || data.keys).to_a
68
70
  @data = Hash[@attributes.zip(fields)]
69
71
  end
@@ -95,7 +97,13 @@ module TTY
95
97
  end.value
96
98
  end
97
99
  end
98
- alias :call :[]
100
+
101
+ # Lookup attribute without evaluation
102
+ #
103
+ # @api public
104
+ def call(attribute)
105
+ data[attributes[attribute]]
106
+ end
99
107
 
100
108
  # Set value at index
101
109
  #
@@ -123,6 +131,15 @@ module TTY
123
131
  end
124
132
  alias :length :size
125
133
 
134
+ # Find maximum row height
135
+ #
136
+ # @return [Integer]
137
+ #
138
+ # @api public
139
+ def height
140
+ fields.map { |field| field.height }.max
141
+ end
142
+
126
143
  # Convert the Row into Array
127
144
  #
128
145
  # @example
@@ -163,11 +180,21 @@ module TTY
163
180
  to_a.hash
164
181
  end
165
182
 
183
+ # Map field values
184
+ #
185
+ # @api public
166
186
  def map!(&block)
167
187
  data.values_at(*attributes).each do |field|
168
188
  field.value = block.call(field)
169
189
  end
170
190
  end
191
+
192
+ # String representation of a row with its fields
193
+ #
194
+ # @api public
195
+ def inspect
196
+ "#<#{self.class.name} fields=#{to_a}>"
197
+ end
171
198
  end # Row
172
199
 
173
200
  end # Table
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module TTY
4
+ class Table
5
+
6
+ # A class for transforming table values
7
+ class Transformation
8
+
9
+ # Extract the header and row tuples from the value
10
+ #
11
+ # @param [Array] args
12
+ #
13
+ # @return [Object]
14
+ #
15
+ # @api public
16
+ def self.extract_tuples(args)
17
+ rows = args.pop
18
+ header = args.size.zero? ? nil : args.first
19
+ if rows.first.is_a?(Hash)
20
+ header, rows = group_header_and_rows(rows)
21
+ end
22
+ { header: header, rows: rows }
23
+ end
24
+
25
+ # Group hash keys into header and values into rows
26
+ #
27
+ # @params [Hash] value
28
+ #
29
+ # @api public
30
+ def self.group_header_and_rows(value)
31
+ header = value.map(&:keys).flatten.uniq
32
+ rows = value.inject([]) { |arr, el| arr + el.values }
33
+ [header, rows]
34
+ end
35
+
36
+ end # Transformation
37
+ end # Table
38
+ end # TTY
@@ -29,6 +29,13 @@ module TTY
29
29
  def assert_string_values(rows)
30
30
  end
31
31
 
32
+ def validate_rendering_options!(options)
33
+ if (column_widths = options[:column_widths]) &&
34
+ (!column_widths.kind_of?(Array) || column_widths.empty?)
35
+ raise InvalidArgument, ":column_widths must be a non-empty array"
36
+ end
37
+ end
38
+
32
39
  # Check if options are of required type
33
40
  #
34
41
  # @api private
@@ -42,11 +49,6 @@ module TTY
42
49
  !(rows.kind_of?(Array) || rows.kind_of?(Hash))
43
50
  raise InvalidArgument, ":rows must be a non-empty array or hash"
44
51
  end
45
-
46
- if (column_widths = options[:column_widths]) &&
47
- (!column_widths.kind_of?(Array) || column_widths.empty?)
48
- raise InvalidArgument, ":column_widths must be a non-empty array"
49
- end
50
52
  end
51
53
 
52
54
  end # Validatable