table_structure 0.3.20 → 0.3.21

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6070401fcd9287249b329ea8f2af80cd6a4f7bd3b758d983fa5aa7582344ece9
4
- data.tar.gz: 717a02a0ed5146dae1054d8b606528c0673e1c5704c7825d0de6bf6385289791
3
+ metadata.gz: c8d52660a94da1c1fbcc8a14a8332214c32ba1627a50239ee72f9916e12c8502
4
+ data.tar.gz: 2156f6ebaa0ee6fd88dfb282a3c339e20101da8bf33cd250355eff7557915921
5
5
  SHA512:
6
- metadata.gz: 3b07692b723c4c70363e93f91a680f8c0e1fd0d0d53e4925ffc758f11afe19a2134fcadae500fab54d6019ef0f92fb35c8a7f1b3c60923037628221c5816a2d2
7
- data.tar.gz: b2b577a2052191f607c9f87c16606650fc565176207a13adf639a6b1b456d8d9acb053ec2788f4fcf523c82e360b28e9d00c2c30e529fa9bfafaefe5e367f5bc
6
+ metadata.gz: 9e5091c8a9c362a09b563e608cf78b74fddb53526ebd53ef30700f4c975429689c5ab8e27f72918d7c4fbdcd798b7911f20dd70593a313fe59bdb88d5deb8e35
7
+ data.tar.gz: 6ae2d481a839d63271009435173250f929009bd2273a59f3aaa974cc14e7612d7cb55258c82c2fe189f304277249d298e0eaaa7d15d1d20b6acc53cee3bb02f9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.3.21
2
+ Changes:
3
+ - Add `TableStructure::Table`
4
+ - This provides methods for converting data with the schema.
5
+ - Use `TableStructure::Table.new` instead of `TableStructure::Schema#create_table`.
6
+ - `TableStructure::Schema`
7
+ - `TableStructure::Schema#create_table` has been deprecated. Use `TableStructure::Table.new` instead.
8
+
1
9
  # 0.3.20
2
10
  Changes:
3
11
  - `TableStructure::Schema`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- table_structure (0.3.20)
4
+ table_structure (0.3.21)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
  - Converts data with the schema, and outputs table structured data.
9
9
  - `TableStructure::Iterator`
10
10
  - Converts data with the schema, and enumerates table structured data.
11
+ - `TableStructure::Table`
12
+ - Provides methods for converting data with the schema.
11
13
 
12
14
  ## Installation
13
15
 
@@ -127,7 +129,6 @@ response_body = Enumerator.new do |y|
127
129
  writer.write(items, to: CSV.new(y))
128
130
  end
129
131
  ```
130
- [Sample with docker](https://github.com/jsmmr/ruby_table_structure_sample)
131
132
 
132
133
  You can also convert CSV character code:
133
134
  ```ruby
@@ -147,27 +148,31 @@ end
147
148
  ```
148
149
 
149
150
  #### TableStructure::Iterator
150
- Specifying `row_type: :hash` option works well.
151
- To use this option, define `column(s)` with `:key`.
151
+ If you want to convert the item to row as Hash instead of Array, specify `row_type: :hash`.
152
+ To use this option, define `:key` on `column(s)`.
152
153
 
153
154
  Define a schema:
154
155
  ```ruby
155
156
  class SampleTableSchema
156
157
  include TableStructure::Schema
157
158
 
158
- # If header is required, :name must also be defined.
159
- column key: :id,
159
+ # If `:header` is set to `false`, `:name` is optional.
160
+ column name: 'ID',
161
+ key: :id,
160
162
  value: ->(row, *) { row[:id] }
161
163
 
162
- column key: :name,
164
+ column name: 'Name',
165
+ key: :name,
163
166
  value: ->(row, *) { row[:name] }
164
167
 
165
- columns key: %i[pet1 pet2 pet3],
168
+ columns name: ['Pet 1', 'Pet 2', 'Pet 3'],
169
+ key: %i[pet1 pet2 pet3],
166
170
  value: ->(row, *) { row[:pets] }
167
171
 
168
172
  columns ->(table) {
169
173
  table[:questions].map do |question|
170
174
  {
175
+ name: question[:id],
171
176
  key: question[:id].downcase.to_sym,
172
177
  value: ->(row, *) { row[:answers][question[:id]] }
173
178
  }
@@ -175,7 +180,7 @@ class SampleTableSchema
175
180
  }
176
181
 
177
182
  ## If the schemas are nested, :key must be unique in parent and child schemas.
178
- ## This can also be avoided by specifying :key_prefix or :key_suffix option.
183
+ ## This can also be avoided by using :key_prefix or :key_suffix option.
179
184
  # columns ->(table) { NestedTableSchema.new(context: table, key_prefix: 'foo_', key_suffix: '_bar') }
180
185
  end
181
186
  ```
@@ -191,7 +196,7 @@ context = {
191
196
  }
192
197
 
193
198
  schema = SampleTableSchema.new(context: context)
194
- iterator = TableStructure::Iterator.new(schema, row_type: :hash, header: false)
199
+ iterator = TableStructure::Iterator.new(schema, header: false, row_type: :hash)
195
200
  ```
196
201
 
197
202
  Enumerate the items converted by the schema:
@@ -225,6 +230,33 @@ enum.lazy.select { |item| item[:q1] == '⭕️' }.take(1).force
225
230
  # => [{:id=>1, :name=>"Taro", :pet1=>"🐱", :pet2=>"🐶", :pet3=>nil, :q1=>"⭕️", :q2=>"❌", :q3=>"⭕️"}]
226
231
  ```
227
232
 
233
+ #### TableStructure::Table
234
+
235
+ Initialize a table with the schema and render the table:
236
+ ```erb
237
+ <% TableStructure::Table.new(schema, row_type: :hash) do |table| %>
238
+ <table>
239
+ <thead>
240
+ <tr>
241
+ <% table.header.each do |key, value| %>
242
+ <th class="<%= key %>"><%= value %></th>
243
+ <% end %>
244
+ </tr>
245
+ </thead>
246
+
247
+ <tbody>
248
+ <% table.body(@items).each do |row| %>
249
+ <tr>
250
+ <% row.each do |key, value| %>
251
+ <td class="<%= key %>"><%= value %></td>
252
+ <% end %>
253
+ </tr>
254
+ <% end %>
255
+ </tbody>
256
+ </table>
257
+ <% end %>
258
+ ```
259
+
228
260
  ### Advanced
229
261
 
230
262
  You can add definitions when initializing the schema.
@@ -244,7 +276,7 @@ schema = UserTableSchema.new do
244
276
  end
245
277
  ```
246
278
 
247
- You can also omit columns by defining `:omitted`.
279
+ You can also omit columns by using `:omitted`.
248
280
  ```ruby
249
281
  class UserTableSchema
250
282
  include TableStructure::Schema
@@ -265,7 +297,7 @@ context = { admin: true }
265
297
  schema = UserTableSchema.new(context: context)
266
298
  ```
267
299
 
268
- You can also omit columns by specifying `nil_definitions_ignored: true`.
300
+ You can also omit columns by using `:nil_definitions_ignored` option.
269
301
  If this option is set to `true` and `column(s)` difinition returns `nil`, the difinition is ignored.
270
302
  ```ruby
271
303
  class SampleTableSchema
@@ -327,37 +359,27 @@ end
327
359
  class SampleTableSchema
328
360
  include TableStructure::Schema
329
361
 
330
- columns ->(table) { UserTableSchema.new(context: table) }
362
+ columns UserTableSchema
331
363
  ## or
332
- # columns UserTableSchema
364
+ # columns ->(table) { UserTableSchema.new(context: table) }
333
365
 
334
- columns ->(table) { PetTableSchema.new(context: table) }
366
+ columns PetTableSchema
335
367
  ## or
336
- # columns PetTableSchema
368
+ # columns ->(table) { PetTableSchema.new(context: table) }
337
369
 
338
- columns ->(table) { QuestionTableSchema.new(context: table) }
370
+ columns QuestionTableSchema
339
371
  ## or
340
- # columns QuestionTableSchema
372
+ # columns ->(table) { QuestionTableSchema.new(context: table) }
341
373
  end
342
-
343
- context = {
344
- questions: [
345
- { id: 'Q1', text: 'Do you like sushi?' },
346
- { id: 'Q2', text: 'Do you like yakiniku?' },
347
- { id: 'Q3', text: 'Do you like ramen?' }
348
- ]
349
- }
350
-
351
- schema = SampleTableSchema.new(context: context)
352
374
  ```
353
375
 
354
376
  You can also concatenate or merge the schema classes.
355
377
  Both create a schema class, with a few differences.
356
378
  - `+`
357
379
  - Similar to nesting the schemas.
358
- `column_converter` or `context_builder` works only to columns in the schema that they was defined.
380
+ `column_converter` works only to columns in the schema that they was defined.
359
381
  - `merge`
360
- - If there are some definitions of `column_converter` or `context_builder` with the same name in the schemas to be merged, the one in the schema that is merged last will work to all columns.
382
+ - If there are some definitions of `column_converter` with the same name in the schemas to be merged, the one in the schema that is merged last will work to all columns.
361
383
 
362
384
  ```ruby
363
385
  class UserTableSchema
@@ -444,30 +466,9 @@ class SampleTableSchema
444
466
  end
445
467
  ```
446
468
 
447
- You can also use only `TableStructure::Schema` instance.
448
- ```erb
449
- <% @schema.create_table(row_type: :hash) do |table| %>
450
- <table>
451
- <thead>
452
- <tr>
453
- <% table.header.each do |key, value| %>
454
- <th class="<%= key %>"><%= value %></th>
455
- <% end %>
456
- </tr>
457
- </thead>
469
+ ## Sample with docker
458
470
 
459
- <tbody>
460
- <% table.body(@items).each do |row| %>
461
- <tr>
462
- <% row.each do |key, value| %>
463
- <td class="<%= key %>"><%= value %></td>
464
- <% end %>
465
- </tr>
466
- <% end %>
467
- </tbody>
468
- </table>
469
- <% end %>
470
- ```
471
+ https://github.com/jsmmr/ruby_table_structure_sample
471
472
 
472
473
  ## Contributing
473
474
 
@@ -21,14 +21,16 @@ module TableStructure
21
21
  require 'table_structure/schema/definition/columns/attributes'
22
22
  require 'table_structure/schema/definition/columns/schema_class'
23
23
  require 'table_structure/schema/definition/columns/schema_instance'
24
- require 'table_structure/schema/column_converters'
25
- require 'table_structure/schema/context_builders'
26
- require 'table_structure/schema/row_builders'
27
- require 'table_structure/schema/keys_generator'
28
- require 'table_structure/schema/table'
24
+ require 'table_structure/schema/column_converter'
25
+ require 'table_structure/schema/row_builder'
26
+ require 'table_structure/schema/key_converter'
29
27
  require 'table_structure/schema/columns/attributes'
30
28
  require 'table_structure/schema/columns/schema'
31
29
  require 'table_structure/schema/utils'
30
+ require 'table_structure/table'
31
+ require 'table_structure/table/column_converter'
32
+ require 'table_structure/table/context_builder'
33
+ require 'table_structure/table/row_builder'
32
34
  require 'table_structure/table/iterator'
33
35
  require 'table_structure/writer'
34
36
  require 'table_structure/csv/writer'
@@ -2,21 +2,106 @@
2
2
 
3
3
  module TableStructure
4
4
  class Iterator
5
- def initialize(schema_or_writer, **options)
6
- if schema_or_writer.is_a?(Schema)
7
- schema = schema_or_writer
8
- @writer = Writer.new(schema, **options)
9
- elsif schema_or_writer.is_a?(Writer)
10
- warn "[TableStructure] Pass Writer as an argument has been deprecated. Pass Schema instead. #{caller_locations(1, 1)}"
11
- @writer = schema_or_writer
12
- else
13
- raise ::TableStructure::Error, "Must be either Schema or Writer. #{schema_or_writer}"
5
+ def initialize(
6
+ schema,
7
+ header: { context: nil },
8
+ row_type: :array,
9
+ **deprecated_options
10
+ )
11
+ if deprecated_options.key?(:header_omitted)
12
+ header_omitted = deprecated_options[:header_omitted]
13
+ warn "[TableStructure] `header_omitted: #{!!header_omitted}` option has been deprecated. Use `header: #{!header_omitted}` option instead."
14
+ header = !header_omitted
15
+ end
16
+
17
+ if deprecated_options.key?(:header_context)
18
+ header_context = deprecated_options[:header_context]
19
+ warn '[TableStructure] `:header_context` option has been deprecated. Use `header: { context: ... }` option instead.'
20
+ header = { context: header_context }
21
+ end
22
+
23
+ if deprecated_options.key?(:result_type)
24
+ warn '[TableStructure] `:result_type` option has been deprecated. Use `:row_type` option instead.'
25
+ row_type = deprecated_options[:result_type]
26
+ end
27
+
28
+ unless schema.is_a?(Schema)
29
+ raise ::TableStructure::Error, "Must be use Schema. #{schema}"
30
+ end
31
+
32
+ @schema = schema
33
+ @options = {
34
+ header: header,
35
+ row_type: row_type
36
+ }
37
+ end
38
+
39
+ def iterate(
40
+ items,
41
+ **deprecated_options,
42
+ &block
43
+ )
44
+ header = @options[:header]
45
+ row_type = @options[:row_type]
46
+
47
+ if deprecated_options.key?(:header)
48
+ header = deprecated_options[:header]
49
+ warn '[TableStructure] Use :header option on initialize method.'
50
+ end
51
+
52
+ if deprecated_options.key?(:header_omitted)
53
+ header_omitted = deprecated_options[:header_omitted]
54
+ warn "[TableStructure] `header_omitted: #{!!header_omitted}` option has been deprecated. Use `header: #{!header_omitted}` option instead."
55
+ header = !header_omitted
14
56
  end
57
+
58
+ if deprecated_options.key?(:header_context)
59
+ header_context = deprecated_options[:header_context]
60
+ warn '[TableStructure] `:header_context` option has been deprecated. Use `header: { context: ... }` option instead.'
61
+ header = { context: header_context }
62
+ end
63
+
64
+ if deprecated_options.key?(:row_type)
65
+ row_type = deprecated_options[:row_type]
66
+ warn '[TableStructure] Use :row_type option on initialize method.'
67
+ end
68
+
69
+ if deprecated_options.key?(:result_type)
70
+ warn '[TableStructure] `:result_type` option has been deprecated. Use `:row_type` option instead.'
71
+ row_type = deprecated_options[:result_type]
72
+ end
73
+
74
+ items = enumerize(items)
75
+
76
+ enum =
77
+ Table::Iterator
78
+ .new(
79
+ Table.new(@schema, row_type: row_type),
80
+ header: header
81
+ )
82
+ .iterate(items)
83
+
84
+ if block_given?
85
+ enum =
86
+ enum
87
+ .lazy
88
+ .map { |row| block.call(row) }
89
+ end
90
+
91
+ enum
15
92
  end
16
93
 
17
- def iterate(items, **options, &block)
18
- # TODO: Change not to use Writer.
19
- Enumerator.new { |y| @writer.write(items, to: y, **options, &block) }
94
+ private
95
+
96
+ def enumerize(items)
97
+ if items.respond_to?(:each)
98
+ items
99
+ elsif items.respond_to?(:call)
100
+ warn "[TableStructure] Use `Enumerator` to wrap items instead of `lambda`. The use of `lambda` has been deprecated. #{items}"
101
+ Enumerator.new { |y| items.call(y) }
102
+ else
103
+ raise ::TableStructure::Error, "Must be enumerable. #{items}"
104
+ end
20
105
  end
21
106
  end
22
107
  end
@@ -21,15 +21,12 @@ module TableStructure
21
21
  end
22
22
  end
23
23
 
24
- MyDefinition = Struct.new(
25
- :name,
26
- :columns,
27
- :context_builders,
28
- :column_converters,
29
- :row_builders,
30
- :context,
31
- :options
32
- )
24
+ attr_reader :columns,
25
+ :context_builders,
26
+ :column_converters,
27
+ :key_converter,
28
+ :row_builders,
29
+ :context
33
30
 
34
31
  def initialize(
35
32
  name: self.class.name,
@@ -42,20 +39,27 @@ module TableStructure
42
39
  **deprecated_options,
43
40
  &block
44
41
  )
45
- unless deprecated_options.empty?
46
- caller_location = caller_locations(1, 1)
47
- deprecated_options.keys.each do |k|
48
- warn "[TableStructure] Specify :#{k} option on Writer or Iterator. #{caller_location}"
49
- end
42
+ if deprecated_options.key?(:row_type)
43
+ raise ::TableStructure::Error, 'Use :row_type option with Table, Writer or Iterator.'
44
+ end
45
+
46
+ if deprecated_options.key?(:result_type)
47
+ raise ::TableStructure::Error, ':result_type option has been deprecated. Use :row_type option instead.'
50
48
  end
51
49
 
52
- options = {
53
- name_prefix: name_prefix,
54
- name_suffix: name_suffix,
55
- key_prefix: key_prefix,
56
- key_suffix: key_suffix,
57
- nil_definitions_ignored: nil_definitions_ignored
58
- }.merge!(self.class.options).merge!(deprecated_options)
50
+ options =
51
+ [
52
+ self.class.options,
53
+ {
54
+ name_prefix: name_prefix,
55
+ name_suffix: name_suffix,
56
+ key_prefix: key_prefix,
57
+ key_suffix: key_suffix,
58
+ nil_definitions_ignored: nil_definitions_ignored
59
+ },
60
+ deprecated_options
61
+ ]
62
+ .reduce({}, &:merge!)
59
63
 
60
64
  schema_classes = [self.class]
61
65
 
@@ -63,90 +67,65 @@ module TableStructure
63
67
  schema_classes << ::TableStructure::Schema.create_class(&block)
64
68
  end
65
69
 
66
- context_builders = ContextBuilders.new(
67
- schema_classes.map(&:context_builders).reduce({}, &:merge!)
68
- )
70
+ @context_builders =
71
+ schema_classes
72
+ .map(&:context_builders)
73
+ .reduce({}, &:merge!)
69
74
 
70
- column_converters = ColumnConverters.new(
71
- schema_classes.map(&:column_converters).reduce({}, &:merge!)
72
- )
75
+ table_context_builder = @context_builders.delete(:table)
76
+
77
+ @context = table_context_builder ? table_context_builder.call(context) : context
73
78
 
74
- row_builders = RowBuilders.new(
75
- schema_classes.map(&:row_builders).reduce({}, &:merge!)
79
+ @column_converters =
80
+ schema_classes
81
+ .map(&:column_converters)
82
+ .reduce({}, &:merge!)
83
+ .merge(
84
+ ColumnConverter.create_optional_converters(
85
+ name_prefix: options.delete(:name_prefix),
86
+ name_suffix: options.delete(:name_suffix)
87
+ )
88
+ )
89
+
90
+ @key_converter = KeyConverter.new(
91
+ prefix: options.delete(:key_prefix),
92
+ suffix: options.delete(:key_suffix)
76
93
  )
77
94
 
78
- table_context = context_builders.build_for_table(context)
95
+ @row_builders =
96
+ RowBuilder.prepend_default_builders(
97
+ schema_classes
98
+ .map(&:row_builders)
99
+ .reduce({}, &:merge!)
100
+ )
79
101
 
80
- columns =
102
+ @columns =
81
103
  Definition::Columns::Compiler
82
104
  .new(
83
105
  name,
84
106
  schema_classes.map(&:column_definitions).reduce([], &:concat),
85
- options
107
+ { nil_definitions_ignored: options.delete(:nil_definitions_ignored) }
86
108
  )
87
- .compile(table_context)
109
+ .compile(@context)
88
110
 
89
- @_definition_ =
90
- MyDefinition.new(
91
- name,
92
- columns,
93
- context_builders,
94
- column_converters,
95
- row_builders,
96
- table_context,
97
- options
98
- )
111
+ @options = options
99
112
  end
100
113
 
101
- def create_table(row_type: :array, **deprecated_options)
102
- options = @_definition_.options.merge(deprecated_options)
114
+ def create_table(row_type: :array, **deprecated_options, &block)
115
+ warn '[TableStructure] `TableStructure::Schema#create_table` has been deprecated. Use `TableStructure::Table.new` instead.'
116
+
117
+ options = @options.merge(deprecated_options)
103
118
 
104
119
  if options.key?(:result_type)
105
120
  warn '[TableStructure] `:result_type` option has been deprecated. Use `:row_type` option instead.'
106
121
  options[:row_type] = options[:result_type]
107
122
  end
108
123
 
109
- keys_generator_options = {
110
- prefix: options[:key_prefix],
111
- suffix: options[:key_suffix]
112
- }
113
-
114
- keys_generator = KeysGenerator.new(
115
- **keys_generator_options
116
- )
117
-
118
- table = Table.new(
119
- columns: @_definition_.columns,
120
- context: @_definition_.context,
121
- keys_generator: keys_generator
122
- )
123
-
124
- @_definition_
125
- .context_builders
126
- .extend_methods_for(table)
127
-
128
- column_converters_options = {
129
- name_prefix: options[:name_prefix],
130
- name_suffix: options[:name_suffix]
131
- }
132
-
133
- @_definition_
134
- .column_converters
135
- .extend_methods_for(table, **column_converters_options)
136
-
137
- row_builders_options = {
138
- row_type: options[:row_type] || row_type
139
- }
140
-
141
- @_definition_
142
- .row_builders
143
- .extend_methods_for(table, **row_builders_options)
124
+ ::TableStructure::Table.new(self, row_type: options[:row_type] || row_type, &block)
125
+ end
144
126
 
145
- if block_given?
146
- yield table
147
- else
148
- table
149
- end
127
+ def contain_callable?(attribute)
128
+ @columns.any? { |column| column.contain_callable?(attribute) }
150
129
  end
151
130
  end
152
131
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ module Schema
5
+ module ColumnConverter
6
+ class << self
7
+ def create_optional_converters(
8
+ name_prefix: nil,
9
+ name_suffix: nil
10
+ )
11
+ column_converters = {}
12
+
13
+ if name_prefix
14
+ column_converters[:_name_prepender_] =
15
+ create_prepender(name_prefix, header: true, body: false)
16
+ end
17
+
18
+ if name_suffix
19
+ column_converters[:_name_appender_] =
20
+ create_appender(name_suffix, header: true, body: false)
21
+ end
22
+
23
+ column_converters
24
+ end
25
+
26
+ private
27
+
28
+ def create_prepender(string, **options)
29
+ Definition::ColumnConverter.new(
30
+ lambda { |val, *|
31
+ val.nil? ? val : "#{string}#{val}"
32
+ },
33
+ **options
34
+ )
35
+ end
36
+
37
+ def create_appender(string, **options)
38
+ Definition::ColumnConverter.new(
39
+ lambda { |val, *|
40
+ val.nil? ? val : "#{val}#{string}"
41
+ },
42
+ **options
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -5,7 +5,8 @@ module TableStructure
5
5
  module Columns
6
6
  class Schema
7
7
  def initialize(schema)
8
- @table = schema.create_table
8
+ @schema = schema
9
+ @table = ::TableStructure::Table.new(schema)
9
10
  end
10
11
 
11
12
  def names(header_context, _table_context)
@@ -25,7 +26,7 @@ module TableStructure
25
26
  end
26
27
 
27
28
  def contain_callable?(attribute)
28
- @table.send(:contain_callable?, attribute)
29
+ @schema.contain_callable?(attribute)
29
30
  end
30
31
  end
31
32
  end
@@ -2,13 +2,13 @@
2
2
 
3
3
  module TableStructure
4
4
  module Schema
5
- class KeysGenerator
5
+ class KeyConverter
6
6
  def initialize(prefix: nil, suffix: nil)
7
7
  @prefix = prefix
8
8
  @suffix = suffix
9
9
  end
10
10
 
11
- def generate(keys)
11
+ def convert(keys)
12
12
  return keys unless has_any_options?
13
13
 
14
14
  keys.map do |key|
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ module Schema
5
+ module RowBuilder
6
+ DEFAULT_ROW_BUILDERS = {
7
+ _to_hash_: Definition::RowBuilder.new(
8
+ lambda { |values, keys, *|
9
+ keys.map.with_index { |key, i| [key || i, values[i]] }.to_h
10
+ },
11
+ enabled_row_types: [:hash]
12
+ )
13
+ }.freeze
14
+
15
+ class << self
16
+ def prepend_default_builders(builders)
17
+ DEFAULT_ROW_BUILDERS.merge(builders)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ class Table
5
+ def initialize(schema, row_type: :array)
6
+ @columns = schema.columns
7
+ @context = schema.context
8
+ @key_converter = schema.key_converter
9
+
10
+ ContextBuilder.create_module(
11
+ schema.context_builders,
12
+ apply_to_name: schema.contain_callable?(:name),
13
+ apply_to_value: schema.contain_callable?(:value),
14
+ context: schema.context
15
+ ) { |mod| extend mod }
16
+
17
+ ColumnConverter.create_module(
18
+ schema.column_converters,
19
+ context: schema.context
20
+ ) { |mod| extend mod }
21
+
22
+ RowBuilder.create_module(
23
+ schema.row_builders,
24
+ row_type: row_type,
25
+ keys: keys,
26
+ context: schema.context
27
+ ) { |mod| extend mod }
28
+
29
+ yield self if block_given?
30
+ end
31
+
32
+ def header(context: nil)
33
+ row_values(:names, context)
34
+ end
35
+
36
+ def body(items)
37
+ Enumerator.new do |y|
38
+ items.each { |item| y << data(context: item) }
39
+ end
40
+ end
41
+
42
+ def rows(items)
43
+ warn '[TableStructure] `TableStructure::Table#rows(items)` has been deprecated. Use `TableStructure::Table#body(items)` instead.'
44
+ body(items)
45
+ end
46
+
47
+ def row(context: nil)
48
+ warn '[TableStructure] `TableStructure::Table#row(context: ...)` has been deprecated. Use `TableStructure::Table#body(items)` instead.'
49
+ data(context: context)
50
+ end
51
+
52
+ private
53
+
54
+ def data(context: nil)
55
+ row_values(:values, context)
56
+ end
57
+
58
+ def keys
59
+ @keys ||= @key_converter.convert(@columns.map(&:keys).flatten)
60
+ end
61
+
62
+ def size
63
+ @size ||= @columns.map(&:size).reduce(0, &:+)
64
+ end
65
+
66
+ def row_values(method, context)
67
+ @columns
68
+ .map { |column| column.send(method, context, @context) }
69
+ .flatten
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ class Table::ColumnConverter
5
+ class ColumnConvertible < Module
6
+ def initialize(methods)
7
+ methods.each do |name, method|
8
+ define_method(name, &method)
9
+ end
10
+ end
11
+ end
12
+
13
+ class << self
14
+ def create_module(converters, context:)
15
+ return if converters.empty?
16
+
17
+ header_converters = converters.select { |_k, v| v.applicable_to_header? }
18
+ body_converters = converters.select { |_k, v| v.applicable_to_body? }
19
+
20
+ methods = {}
21
+
22
+ unless header_converters.empty?
23
+ methods[:header] = create_method(header_converters, context)
24
+ end
25
+
26
+ unless body_converters.empty?
27
+ methods[:data] = create_method(body_converters, context)
28
+ end
29
+
30
+ yield ColumnConvertible.new(methods)
31
+ end
32
+
33
+ private
34
+
35
+ def create_method(converters, table_context)
36
+ proc do |context: nil|
37
+ super(context: context).map do |val|
38
+ converters.reduce(val) do |val, (_, converter)|
39
+ converter.call(val, context, table_context)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ class Table::ContextBuilder
5
+ class ContextBuildable < Module
6
+ def initialize(methods)
7
+ methods.each do |name, method|
8
+ define_method(name, &method)
9
+ end
10
+ end
11
+ end
12
+
13
+ class << self
14
+ def create_module(
15
+ builders,
16
+ apply_to_name: true,
17
+ apply_to_value: true,
18
+ context:
19
+ )
20
+ return if builders.empty?
21
+
22
+ header_builder = builders[:header] # will remove
23
+ row_builder = builders[:row]
24
+
25
+ methods = {}
26
+
27
+ if apply_to_name && builders.key?(:header)
28
+ methods[:header] = create_method(builders[:header])
29
+ end
30
+
31
+ if apply_to_value && builders.key?(:row)
32
+ methods[:data] = create_method(builders[:row])
33
+ end
34
+
35
+ yield ContextBuildable.new(methods)
36
+ end
37
+
38
+ private
39
+
40
+ def create_method(builder)
41
+ return if builder.nil?
42
+
43
+ proc do |context: nil|
44
+ super(context: builder.call(context))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,23 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TableStructure
4
- module Table
5
- class Iterator
6
- def initialize(table, header: { context: nil })
7
- @table = table
8
- @header_options = header
9
- end
4
+ class Table::Iterator
5
+ def initialize(table, header: { context: nil })
6
+ @table = table
7
+ @header_options = header
8
+ end
10
9
 
11
- def iterate(items)
12
- ::Enumerator.new do |y|
13
- if @header_options
14
- header_context = @header_options.is_a?(Hash) ? @header_options[:context] : nil
15
- y << @table.header(context: header_context)
16
- end
17
- @table
18
- .body(items)
19
- .each { |row| y << row }
10
+ def iterate(items)
11
+ ::Enumerator.new do |y|
12
+ if @header_options
13
+ header_context = @header_options.is_a?(Hash) ? @header_options[:context] : nil
14
+ y << @table.header(context: header_context)
20
15
  end
16
+ @table
17
+ .body(items)
18
+ .each { |row| y << row }
21
19
  end
22
20
  end
23
21
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ class Table::RowBuilder
5
+ class ResultBuildable < Module
6
+ def initialize(methods)
7
+ methods.each do |name, method|
8
+ define_method(name, &method)
9
+ end
10
+ end
11
+ end
12
+
13
+ class << self
14
+ def create_module(builders, row_type:, keys:, context:)
15
+ return if builders.empty?
16
+
17
+ builders = builders.select { |_k, v| v.enabled?(row_type) }
18
+ return if builders.empty?
19
+
20
+ yield ResultBuildable.new(
21
+ header: create_method(builders, keys, context),
22
+ data: create_method(builders, keys, context)
23
+ )
24
+ end
25
+
26
+ private
27
+
28
+ def create_method(builders, table_keys, table_context)
29
+ proc do |context: nil|
30
+ builders
31
+ .reduce(super(context: context)) do |vals, (_, builder)|
32
+ builder.call(vals, table_keys, context, table_context)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TableStructure
4
- VERSION = '0.3.20'
4
+ VERSION = '0.3.21'
5
5
  end
@@ -46,7 +46,7 @@ module TableStructure
46
46
 
47
47
  if deprecated_options.key?(:header)
48
48
  header = deprecated_options[:header]
49
- warn '[TableStructure] Specify :header option as an argument for initialize method.'
49
+ warn '[TableStructure] Use :header option on initialize method.'
50
50
  end
51
51
 
52
52
  if deprecated_options.key?(:header_omitted)
@@ -63,7 +63,7 @@ module TableStructure
63
63
 
64
64
  if deprecated_options.key?(:row_type)
65
65
  row_type = deprecated_options[:row_type]
66
- warn '[TableStructure] Specify :row_type option as an argument for initialize method.'
66
+ warn '[TableStructure] Use :row_type option on initialize method.'
67
67
  end
68
68
 
69
69
  if deprecated_options.key?(:result_type)
@@ -71,28 +71,13 @@ module TableStructure
71
71
  row_type = deprecated_options[:result_type]
72
72
  end
73
73
 
74
- items = enumerize(items)
74
+ output = Output.new(to, method: method)
75
75
 
76
- @schema.create_table(row_type: row_type) do |table|
77
- output = Output.new(to, method: method)
76
+ Iterator
77
+ .new(@schema, header: header, row_type: row_type)
78
+ .iterate(items, &block)
79
+ .each { |row| output.write(row) }
78
80
 
79
- enum =
80
- Table::Iterator
81
- .new(
82
- table,
83
- header: header
84
- )
85
- .iterate(items)
86
-
87
- if block_given?
88
- enum =
89
- enum
90
- .lazy
91
- .map { |row| block.call(row) }
92
- end
93
-
94
- enum.each { |row| output.write(row) }
95
- end
96
81
  nil
97
82
  end
98
83
 
@@ -108,16 +93,5 @@ module TableStructure
108
93
  @output.send(@method, values)
109
94
  end
110
95
  end
111
-
112
- def enumerize(items)
113
- if items.respond_to?(:each)
114
- items
115
- elsif items.respond_to?(:call)
116
- warn "[TableStructure] Use `Enumerator` to wrap items instead of `lambda`. The use of `lambda` has been deprecated. #{items}"
117
- Enumerator.new { |y| items.call(y) }
118
- else
119
- raise ::TableStructure::Error, "Must be enumerable. #{items}"
120
- end
121
- end
122
96
  end
123
97
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: table_structure
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.20
4
+ version: 0.3.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - jsmmr
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-29 00:00:00.000000000 Z
11
+ date: 2020-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -75,10 +75,9 @@ files:
75
75
  - lib/table_structure/iterator.rb
76
76
  - lib/table_structure/schema.rb
77
77
  - lib/table_structure/schema/class_methods.rb
78
- - lib/table_structure/schema/column_converters.rb
78
+ - lib/table_structure/schema/column_converter.rb
79
79
  - lib/table_structure/schema/columns/attributes.rb
80
80
  - lib/table_structure/schema/columns/schema.rb
81
- - lib/table_structure/schema/context_builders.rb
82
81
  - lib/table_structure/schema/definition/column_converter.rb
83
82
  - lib/table_structure/schema/definition/columns/attributes.rb
84
83
  - lib/table_structure/schema/definition/columns/compiler.rb
@@ -93,11 +92,14 @@ files:
93
92
  - lib/table_structure/schema/dsl/context_builder.rb
94
93
  - lib/table_structure/schema/dsl/option.rb
95
94
  - lib/table_structure/schema/dsl/row_builder.rb
96
- - lib/table_structure/schema/keys_generator.rb
97
- - lib/table_structure/schema/row_builders.rb
98
- - lib/table_structure/schema/table.rb
95
+ - lib/table_structure/schema/key_converter.rb
96
+ - lib/table_structure/schema/row_builder.rb
99
97
  - lib/table_structure/schema/utils.rb
98
+ - lib/table_structure/table.rb
99
+ - lib/table_structure/table/column_converter.rb
100
+ - lib/table_structure/table/context_builder.rb
100
101
  - lib/table_structure/table/iterator.rb
102
+ - lib/table_structure/table/row_builder.rb
101
103
  - lib/table_structure/version.rb
102
104
  - lib/table_structure/writer.rb
103
105
  - table_structure.gemspec
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableStructure
4
- module Schema
5
- class ColumnConverters
6
- def initialize(converters)
7
- @header_converters = converters.select { |_k, v| v.applicable_to_header? }
8
- @body_converterss = converters.select { |_k, v| v.applicable_to_body? }
9
- end
10
-
11
- def extend_methods_for(table, name_prefix:, name_suffix:)
12
- table_context = table.instance_variable_get(:@context)
13
-
14
- header_converters =
15
- @header_converters
16
- .merge(
17
- _prepend_prefix_: create_prepender(name_prefix),
18
- _append_suffix_: create_appender(name_suffix)
19
- )
20
- .reject { |_k, v| v.nil? }
21
-
22
- body_converterss = @body_converterss
23
-
24
- methods =
25
- {
26
- header: create_method(header_converters, table_context),
27
- data: create_method(body_converterss, table_context)
28
- }
29
- .reject { |_k, v| v.nil? }
30
-
31
- return if methods.empty?
32
-
33
- table.extend ColumnConvertible.new(methods)
34
- end
35
-
36
- private
37
-
38
- def create_prepender(prefix)
39
- return unless prefix
40
-
41
- Definition::ColumnConverter.new(
42
- lambda { |val, *|
43
- val.nil? ? val : "#{prefix}#{val}"
44
- },
45
- header: true,
46
- body: false
47
- )
48
- end
49
-
50
- def create_appender(suffix)
51
- return unless suffix
52
-
53
- Definition::ColumnConverter.new(
54
- lambda { |val, *|
55
- val.nil? ? val : "#{val}#{suffix}"
56
- },
57
- header: true,
58
- body: false
59
- )
60
- end
61
-
62
- def create_method(converters, table_context)
63
- return if converters.empty?
64
-
65
- proc do |context: nil|
66
- super(context: context).map do |val|
67
- converters.reduce(val) do |val, (_, converter)|
68
- converter.call(val, context, table_context)
69
- end
70
- end
71
- end
72
- end
73
- end
74
-
75
- class ColumnConvertible < Module
76
- def initialize(methods)
77
- methods.each do |name, method|
78
- define_method(name, &method)
79
- end
80
- end
81
- end
82
- end
83
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableStructure
4
- module Schema
5
- class ContextBuilders
6
- def initialize(builders)
7
- @table_builder = builders[:table]
8
- @header_builder = builders[:header]
9
- @row_builder = builders[:row]
10
- end
11
-
12
- def build_for_table(context)
13
- if @table_builder
14
- @table_builder.call(context)
15
- else
16
- context
17
- end
18
- end
19
-
20
- def extend_methods_for(table)
21
- methods = {}
22
-
23
- if table.send(:contain_callable?, :name)
24
- methods[:header] = create_method(@header_builder)
25
- end
26
- if table.send(:contain_callable?, :value)
27
- methods[:data] = create_method(@row_builder)
28
- end
29
-
30
- methods.reject! { |_k, v| v.nil? }
31
- return if methods.empty?
32
-
33
- table.extend ContextBuildable.new(methods)
34
- end
35
-
36
- private
37
-
38
- def create_method(builder)
39
- return if builder.nil?
40
-
41
- proc do |context: nil|
42
- super(context: builder.call(context))
43
- end
44
- end
45
- end
46
-
47
- class ContextBuildable < Module
48
- def initialize(methods)
49
- methods.each do |name, method|
50
- define_method(name, &method)
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableStructure
4
- module Schema
5
- class RowBuilders
6
- DEFAULT_BUILDERS = {
7
- _to_hash_: ::TableStructure::Schema::Definition::RowBuilder.new(
8
- lambda { |values, keys, *|
9
- keys.map.with_index { |key, i| [key || i, values[i]] }.to_h
10
- },
11
- enabled_row_types: [:hash]
12
- )
13
- }.freeze
14
-
15
- def initialize(builders)
16
- @builders = builders
17
- end
18
-
19
- def extend_methods_for(table, row_type: :array)
20
- builders =
21
- DEFAULT_BUILDERS
22
- .merge(@builders)
23
- .select { |_k, v| v.enabled?(row_type) }
24
-
25
- return if builders.empty?
26
-
27
- table_context = table.instance_variable_get(:@context)
28
- table_keys = table.send(:keys)
29
-
30
- table.extend ResultBuildable.new(
31
- header: create_method(builders, table_keys, table_context),
32
- data: create_method(builders, table_keys, table_context)
33
- )
34
- end
35
-
36
- private
37
-
38
- def create_method(builders, table_keys, table_context)
39
- return if builders.empty?
40
-
41
- proc do |context: nil|
42
- builders
43
- .reduce(super(context: context)) do |vals, (_, builder)|
44
- builder.call(vals, table_keys, context, table_context)
45
- end
46
- end
47
- end
48
- end
49
-
50
- class ResultBuildable < Module
51
- def initialize(methods)
52
- methods.each do |name, method|
53
- define_method(name, &method)
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableStructure
4
- module Schema
5
- class Table
6
- def initialize(
7
- columns:,
8
- context:,
9
- keys_generator:
10
- )
11
- @columns = columns
12
- @context = context
13
- @keys_generator = keys_generator
14
- end
15
-
16
- def header(context: nil)
17
- row_values(:names, context)
18
- end
19
-
20
- def row(context: nil)
21
- warn '[TableStructure] `TableStructure::Schema::Table#row(context:)` has been deprecated. Use `TableStructure::Schema::Table#body(items)` instead.'
22
- data(context: context)
23
- end
24
-
25
- def body(items)
26
- Enumerator.new do |y|
27
- items.each { |item| y << data(context: item) }
28
- end
29
- end
30
-
31
- def rows(items)
32
- warn '[TableStructure] `TableStructure::Schema::Table#rows(items)` has been deprecated. Use `TableStructure::Schema::Table#body(items)` instead.'
33
- body(items)
34
- end
35
-
36
- private
37
-
38
- def data(context: nil)
39
- row_values(:values, context)
40
- end
41
-
42
- def keys
43
- @keys ||= @keys_generator.generate(@columns.map(&:keys).flatten)
44
- end
45
-
46
- def size
47
- @size ||= @columns.map(&:size).reduce(0, &:+)
48
- end
49
-
50
- def row_values(method, context)
51
- @columns
52
- .map { |column| column.send(method, context, @context) }
53
- .flatten
54
- end
55
-
56
- def contain_callable?(attribute)
57
- @columns.any? { |column| column.contain_callable?(attribute) }
58
- end
59
- end
60
- end
61
- end