table_structure 0.3.21 → 0.4.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +37 -0
  4. data/Gemfile.lock +7 -7
  5. data/README.md +84 -78
  6. data/lib/table_structure.rb +6 -12
  7. data/lib/table_structure/csv/writer.rb +4 -41
  8. data/lib/table_structure/iterator.rb +48 -89
  9. data/lib/table_structure/schema.rb +63 -76
  10. data/lib/table_structure/schema/class_methods.rb +2 -2
  11. data/lib/table_structure/schema/column_builder_factory.rb +75 -0
  12. data/lib/table_structure/schema/columns/attributes.rb +14 -9
  13. data/lib/table_structure/schema/columns/schema.rb +14 -9
  14. data/lib/table_structure/schema/composite_class.rb +40 -0
  15. data/lib/table_structure/schema/definition/columns/compiler.rb +7 -3
  16. data/lib/table_structure/schema/definition/columns/validator.rb +2 -6
  17. data/lib/table_structure/schema/dsl/column_builder.rb +29 -0
  18. data/lib/table_structure/schema/dsl/context_builder.rb +3 -5
  19. data/lib/table_structure/schema/dsl/row_builder.rb +5 -5
  20. data/lib/table_structure/schema/{key_converter.rb → keys_builder.rb} +2 -2
  21. data/lib/table_structure/schema/row_context_builder_factory.rb +30 -0
  22. data/lib/table_structure/table.rb +31 -56
  23. data/lib/table_structure/utils.rb +40 -0
  24. data/lib/table_structure/version.rb +1 -1
  25. data/lib/table_structure/writer.rb +7 -54
  26. metadata +8 -14
  27. data/lib/table_structure/schema/column_converter.rb +0 -48
  28. data/lib/table_structure/schema/definition/column_converter.rb +0 -31
  29. data/lib/table_structure/schema/definition/context_builder.rb +0 -17
  30. data/lib/table_structure/schema/definition/row_builder.rb +0 -25
  31. data/lib/table_structure/schema/dsl/column_converter.rb +0 -34
  32. data/lib/table_structure/schema/dsl/option.rb +0 -19
  33. data/lib/table_structure/schema/row_builder.rb +0 -22
  34. data/lib/table_structure/table/column_converter.rb +0 -46
  35. data/lib/table_structure/table/context_builder.rb +0 -49
  36. data/lib/table_structure/table/iterator.rb +0 -22
  37. data/lib/table_structure/table/row_builder.rb +0 -38
@@ -2,106 +2,65 @@
2
2
 
3
3
  module TableStructure
4
4
  class Iterator
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
5
+ class HeaderOptions
6
+ attr_reader :enabled, :context, :step
7
+ alias enabled? enabled
8
+
9
+ def initialize(options)
10
+ @enabled = !!options
11
+ if options.is_a?(Hash)
12
+ @context = options[:context]
13
+ @step = options[:step]
14
+ end
15
+
16
+ validate
15
17
  end
16
18
 
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
19
+ private
22
20
 
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]
21
+ def validate
22
+ if @step
23
+ raise ::TableStructure::Error, ':step must be numeric.' unless @step.is_a?(Numeric)
24
+ raise ::TableStructure::Error, ':step must be positive number.' unless @step.positive?
25
+ end
26
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
27
  end
38
28
 
39
- def iterate(
40
- items,
41
- **deprecated_options,
42
- &block
29
+ def initialize(
30
+ schema,
31
+ header: { context: nil, step: nil },
32
+ row_type: :array
43
33
  )
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
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)
34
+ @table = Table.new(schema, row_type: row_type)
35
+ @header_options = HeaderOptions.new(header)
36
+ end
83
37
 
84
- if block_given?
85
- enum =
86
- enum
87
- .lazy
88
- .map { |row| block.call(row) }
38
+ def iterate(items, &block)
39
+ raise ::TableStructure::Error, "Must be enumerable. #{items}" unless items.respond_to?(:each)
40
+
41
+ table_enum = ::Enumerator.new do |y|
42
+ body_enum = @table.body(items)
43
+
44
+ if @header_options.enabled?
45
+ header_row = @table.header(context: @header_options.context)
46
+ y << header_row
47
+
48
+ if @header_options.step
49
+ loop do
50
+ @header_options.step.times { y << body_enum.next }
51
+ y << header_row
52
+ end
53
+ else
54
+ body_enum.each { |row| y << row }
55
+ end
56
+ else
57
+ body_enum.each { |row| y << row }
58
+ end
89
59
  end
90
60
 
91
- enum
92
- end
93
-
94
- private
61
+ table_enum = table_enum.lazy.map(&block) if block_given?
95
62
 
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
63
+ table_enum
105
64
  end
106
65
  end
107
66
  end
@@ -3,29 +3,25 @@
3
3
  module TableStructure
4
4
  module Schema
5
5
  def self.included(klass)
6
- klass.extend(DSL::ColumnConverter)
6
+ klass.extend(DSL::ColumnBuilder)
7
7
  klass.extend(DSL::ColumnDefinition)
8
8
  klass.extend(DSL::ContextBuilder)
9
- klass.extend(DSL::Option)
10
9
  klass.extend(DSL::RowBuilder)
11
10
  klass.extend(ClassMethods)
12
11
  end
13
12
 
14
13
  def self.create_class(&block)
15
- raise ::TableStructure::Error, 'No block given.' unless block
14
+ raise ::TableStructure::Error, 'No block has been given.' unless block
16
15
 
17
- schema_module = self
18
- Class.new do
19
- include schema_module
16
+ ::Class.new do
17
+ include Schema
20
18
  class_eval(&block)
21
19
  end
22
20
  end
23
21
 
24
- attr_reader :columns,
25
- :context_builders,
26
- :column_converters,
27
- :key_converter,
28
- :row_builders,
22
+ Row = ::Struct.new(:keys, :values, :context)
23
+
24
+ attr_reader :row_builders,
29
25
  :context
30
26
 
31
27
  def initialize(
@@ -36,96 +32,87 @@ module TableStructure
36
32
  key_prefix: nil,
37
33
  key_suffix: nil,
38
34
  nil_definitions_ignored: false,
39
- **deprecated_options,
40
35
  &block
41
36
  )
42
- if deprecated_options.key?(:row_type)
43
- raise ::TableStructure::Error, 'Use :row_type option with Table, Writer or Iterator.'
44
- end
37
+ schema_class = CompositeClass.new.compose(self.class)
38
+ schema_class.compose(Schema.create_class(&block)) if block
45
39
 
46
- if deprecated_options.key?(:result_type)
47
- raise ::TableStructure::Error, ':result_type option has been deprecated. Use :row_type option instead.'
48
- end
49
-
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!)
63
-
64
- schema_classes = [self.class]
65
-
66
- if block_given?
67
- schema_classes << ::TableStructure::Schema.create_class(&block)
68
- end
40
+ context_builders = schema_class.context_builders
69
41
 
70
- @context_builders =
71
- schema_classes
72
- .map(&:context_builders)
73
- .reduce({}, &:merge!)
74
-
75
- table_context_builder = @context_builders.delete(:table)
42
+ table_context_builder = context_builders.delete(:table)
76
43
 
77
44
  @context = table_context_builder ? table_context_builder.call(context) : context
78
45
 
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
- )
46
+ @row_context_builder_factory = RowContextBuilderFactory.new(self, context_builders)
89
47
 
90
- @key_converter = KeyConverter.new(
91
- prefix: options.delete(:key_prefix),
92
- suffix: options.delete(:key_suffix)
48
+ @column_builder_factory = ColumnBuilderFactory.new(
49
+ schema_class.column_builders,
50
+ context: @context,
51
+ name_prefix: name_prefix,
52
+ name_suffix: name_suffix
93
53
  )
94
54
 
95
- @row_builders =
96
- RowBuilder.prepend_default_builders(
97
- schema_classes
98
- .map(&:row_builders)
99
- .reduce({}, &:merge!)
100
- )
55
+ @keys_builder = KeysBuilder.new(
56
+ prefix: key_prefix,
57
+ suffix: key_suffix
58
+ )
59
+
60
+ @row_builders = schema_class.row_builders
101
61
 
102
62
  @columns =
103
63
  Definition::Columns::Compiler
104
64
  .new(
105
65
  name,
106
- schema_classes.map(&:column_definitions).reduce([], &:concat),
107
- { nil_definitions_ignored: options.delete(:nil_definitions_ignored) }
66
+ schema_class.column_definitions,
67
+ nil_definitions_ignored: nil_definitions_ignored
108
68
  )
109
69
  .compile(@context)
70
+ end
110
71
 
111
- @options = options
72
+ def columns_keys
73
+ @columns_keys ||= @keys_builder.build(@columns.map(&:keys).flatten)
112
74
  end
113
75
 
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.'
76
+ def columns_size
77
+ @columns.map(&:size).reduce(0, &:+)
78
+ end
116
79
 
117
- options = @options.merge(deprecated_options)
80
+ def contain_name_callable?
81
+ @columns.any? { |column| column.name_callable? }
82
+ end
118
83
 
119
- if options.key?(:result_type)
120
- warn '[TableStructure] `:result_type` option has been deprecated. Use `:row_type` option instead.'
121
- options[:row_type] = options[:result_type]
122
- end
84
+ def contain_value_callable?
85
+ @columns.any? { |column| column.value_callable? }
86
+ end
123
87
 
124
- ::TableStructure::Table.new(self, row_type: options[:row_type] || row_type, &block)
88
+ def create_header_row_generator
89
+ ::TableStructure::Utils::CompositeCallable.new.compose(
90
+ @row_context_builder_factory.create_header_builder,
91
+ proc do |context|
92
+ values =
93
+ @columns
94
+ .map { |column| column.names(context, @context) }
95
+ .flatten
96
+
97
+ Row.new(columns_keys, values, context)
98
+ end,
99
+ @column_builder_factory.create_header_builder
100
+ )
125
101
  end
126
102
 
127
- def contain_callable?(attribute)
128
- @columns.any? { |column| column.contain_callable?(attribute) }
103
+ def create_data_row_generator
104
+ ::TableStructure::Utils::CompositeCallable.new.compose(
105
+ @row_context_builder_factory.create_data_builder,
106
+ proc do |context|
107
+ values =
108
+ @columns
109
+ .map { |column| column.values(context, @context) }
110
+ .flatten
111
+
112
+ Row.new(columns_keys, values, context)
113
+ end,
114
+ @column_builder_factory.create_data_builder
115
+ )
129
116
  end
130
117
  end
131
118
  end
@@ -36,9 +36,9 @@ module TableStructure
36
36
  .map(&:context_builders)
37
37
  .reduce({}, &:merge!)
38
38
 
39
- @__column_converters__ =
39
+ @__column_builders__ =
40
40
  schema_classes
41
- .map(&:column_converters)
41
+ .map(&:column_builders)
42
42
  .reduce({}, &:merge!)
43
43
 
44
44
  @__row_builders__ =
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableStructure
4
+ module Schema
5
+ class ColumnBuilderFactory
6
+ def initialize(
7
+ builders,
8
+ context: nil,
9
+ name_prefix: nil,
10
+ name_suffix: nil
11
+ )
12
+ @builders = builders
13
+ @context = context
14
+
15
+ optional_builders = {}
16
+
17
+ if name_prefix
18
+ optional_builders[:_name_prepender_] =
19
+ ::TableStructure::Utils::TypedProc.new(
20
+ types: :header
21
+ ) do |val, *|
22
+ val.nil? ? val : "#{name_prefix}#{val}"
23
+ end
24
+ end
25
+
26
+ if name_suffix
27
+ optional_builders[:_name_appender_] =
28
+ ::TableStructure::Utils::TypedProc.new(
29
+ types: :header
30
+ ) do |val, *|
31
+ val.nil? ? val : "#{val}#{name_suffix}"
32
+ end
33
+ end
34
+
35
+ @builders.merge!(optional_builders) unless optional_builders.empty?
36
+ end
37
+
38
+ def create_header_builder
39
+ builders =
40
+ @builders
41
+ .select { |_k, v| v.typed?(:header) }
42
+ .values
43
+
44
+ return if builders.empty?
45
+
46
+ proc do |row|
47
+ row.values = row.values.map do |value|
48
+ builders.reduce(value) do |value, builder|
49
+ builder.call(value, row.context, @context)
50
+ end
51
+ end
52
+ row
53
+ end
54
+ end
55
+
56
+ def create_data_builder
57
+ builders =
58
+ @builders
59
+ .select { |_k, v| v.typed?(:body) }
60
+ .values
61
+
62
+ return if builders.empty?
63
+
64
+ proc do |row|
65
+ row.values = row.values.map do |value|
66
+ builders.reduce(value) do |value, builder|
67
+ builder.call(value, row.context, @context)
68
+ end
69
+ end
70
+ row
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -7,25 +7,30 @@ module TableStructure
7
7
  attr_reader :keys, :size
8
8
 
9
9
  def initialize(name:, key:, value:, size:)
10
- @name = name
10
+ @name_callable = Utils.callable?(name)
11
+ @name = @name_callable ? name : proc { name }
11
12
  @keys = optimize_size([key].flatten, size)
12
- @value = value
13
+ @value_callable = Utils.callable?(value)
14
+ @value = @value_callable ? value : proc { value }
13
15
  @size = size
14
16
  end
15
17
 
16
18
  def names(context, table_context)
17
- name = Utils.evaluate_callable(@name, context, table_context)
18
- optimize_size(name, @size)
19
+ names = @name.call(context, table_context)
20
+ optimize_size(names, @size)
19
21
  end
20
22
 
21
23
  def values(context, table_context)
22
- value = Utils.evaluate_callable(@value, context, table_context)
23
- optimize_size(value, @size)
24
+ values = @value.call(context, table_context)
25
+ optimize_size(values, @size)
24
26
  end
25
27
 
26
- def contain_callable?(attribute)
27
- val = instance_variable_get("@#{attribute}")
28
- Utils.callable?(val)
28
+ def name_callable?
29
+ @name_callable
30
+ end
31
+
32
+ def value_callable?
33
+ @value_callable
29
34
  end
30
35
 
31
36
  private