tty 0.0.9 → 0.0.10

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