csv_decision 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +43 -19
- data/csv_decision.gemspec +1 -1
- data/doc/CSVDecision.html +6 -6
- data/doc/CSVDecision/CellValidationError.html +1 -1
- data/doc/CSVDecision/Columns.html +124 -42
- data/doc/CSVDecision/Columns/Dictionary.html +101 -7
- data/doc/CSVDecision/Data.html +1 -1
- data/doc/CSVDecision/Decision.html +444 -98
- data/doc/CSVDecision/Defaults.html +1 -1
- data/doc/CSVDecision/Dictionary.html +4 -4
- data/doc/CSVDecision/Dictionary/Entry.html +31 -31
- data/doc/CSVDecision/Error.html +1 -1
- data/doc/CSVDecision/FileError.html +1 -1
- data/doc/CSVDecision/Header.html +2 -2
- data/doc/CSVDecision/Index.html +1 -1
- data/doc/CSVDecision/Input.html +129 -3
- data/doc/CSVDecision/Load.html +1 -1
- data/doc/CSVDecision/Matchers.html +168 -41
- data/doc/CSVDecision/Matchers/Constant.html +7 -7
- data/doc/CSVDecision/Matchers/Function.html +1 -1
- data/doc/CSVDecision/Matchers/Guard.html +16 -16
- data/doc/CSVDecision/Matchers/Matcher.html +13 -13
- data/doc/CSVDecision/Matchers/Numeric.html +8 -14
- data/doc/CSVDecision/Matchers/Pattern.html +10 -10
- data/doc/CSVDecision/Matchers/Proc.html +1 -1
- data/doc/CSVDecision/Matchers/Range.html +1 -1
- data/doc/CSVDecision/Matchers/Symbol.html +19 -29
- data/doc/CSVDecision/Options.html +1 -1
- data/doc/CSVDecision/Parse.html +4 -4
- data/doc/CSVDecision/Paths.html +742 -0
- data/doc/CSVDecision/Result.html +139 -70
- data/doc/CSVDecision/Scan.html +313 -0
- data/doc/CSVDecision/Scan/InputHashes.html +369 -0
- data/doc/CSVDecision/ScanRow.html +1 -1
- data/doc/CSVDecision/Table.html +134 -52
- data/doc/CSVDecision/TableValidationError.html +1 -1
- data/doc/CSVDecision/Validate.html +1 -1
- data/doc/_index.html +26 -5
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +50 -28
- data/doc/index.html +50 -28
- data/doc/method_list.html +234 -98
- data/doc/top-level-namespace.html +1 -1
- data/lib/csv_decision.rb +3 -0
- data/lib/csv_decision/columns.rb +11 -0
- data/lib/csv_decision/decision.rb +82 -56
- data/lib/csv_decision/dictionary.rb +5 -1
- data/lib/csv_decision/header.rb +1 -1
- data/lib/csv_decision/input.rb +14 -11
- data/lib/csv_decision/parse.rb +6 -2
- data/lib/csv_decision/paths.rb +78 -0
- data/lib/csv_decision/result.rb +42 -35
- data/lib/csv_decision/scan.rb +116 -0
- data/lib/csv_decision/table.rb +18 -7
- data/lib/csv_decision/validate.rb +1 -1
- data/spec/csv_decision/columns_spec.rb +14 -0
- data/spec/csv_decision/decision_spec.rb +1 -3
- data/spec/csv_decision/examples_spec.rb +25 -0
- data/spec/csv_decision/table_spec.rb +87 -0
- metadata +7 -2
@@ -100,7 +100,7 @@
|
|
100
100
|
</div>
|
101
101
|
|
102
102
|
<div id="footer">
|
103
|
-
Generated on Sun
|
103
|
+
Generated on Sun Feb 11 10:26:07 2018 by
|
104
104
|
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
105
105
|
0.9.12 (ruby-2.4.0).
|
106
106
|
</div>
|
data/lib/csv_decision.rb
CHANGED
@@ -25,11 +25,14 @@ module CSVDecision
|
|
25
25
|
autoload :Matchers, 'csv_decision/matchers'
|
26
26
|
autoload :Options, 'csv_decision/options'
|
27
27
|
autoload :Parse, 'csv_decision/parse'
|
28
|
+
autoload :Paths, 'csv_decision/paths'
|
28
29
|
autoload :Result, 'csv_decision/result'
|
30
|
+
autoload :Scan, 'csv_decision/scan'
|
29
31
|
autoload :ScanRow, 'csv_decision/scan_row'
|
30
32
|
autoload :Table, 'csv_decision/table'
|
31
33
|
autoload :Validate, 'csv_decision/validate'
|
32
34
|
|
35
|
+
# Cell matchers
|
33
36
|
class Matchers
|
34
37
|
autoload :Constant, 'csv_decision/matchers/constant'
|
35
38
|
autoload :Function, 'csv_decision/matchers/function'
|
data/lib/csv_decision/columns.rb
CHANGED
@@ -115,12 +115,17 @@ module CSVDecision
|
|
115
115
|
# This is actually just a subset of :outs.
|
116
116
|
attr_accessor :ifs
|
117
117
|
|
118
|
+
# @return [Hash{Integer=>Symbol}] All path columns.
|
119
|
+
# This is actually just a subset of :outs.
|
120
|
+
attr_accessor :paths
|
121
|
+
|
118
122
|
def initialize
|
119
123
|
@columns = {}
|
120
124
|
@defaults = {}
|
121
125
|
@ifs = {}
|
122
126
|
@ins = {}
|
123
127
|
@outs = {}
|
128
|
+
@paths = {}
|
124
129
|
end
|
125
130
|
end
|
126
131
|
|
@@ -158,6 +163,12 @@ module CSVDecision
|
|
158
163
|
@dictionary.ifs
|
159
164
|
end
|
160
165
|
|
166
|
+
# path: columns hash keyed by column index.
|
167
|
+
# @return [Hash{Index=>Entry}]
|
168
|
+
def paths
|
169
|
+
@dictionary.paths
|
170
|
+
end
|
171
|
+
|
161
172
|
# @return [Array<Symbol>] All input column symbols.
|
162
173
|
def input_keys
|
163
174
|
@dictionary.columns.select { |_k, v| v == :in }.keys
|
@@ -8,7 +8,7 @@ module CSVDecision
|
|
8
8
|
# Accumulate the matching row(s) and calculate the final result.
|
9
9
|
# @api private
|
10
10
|
class Decision
|
11
|
-
# Main method for making decisions.
|
11
|
+
# Main method for making decisions without a path.
|
12
12
|
#
|
13
13
|
# @param table [CSVDecision::Table] Decision table.
|
14
14
|
# @param input [Hash] Input hash (keys may or may not be symbolized)
|
@@ -17,66 +17,101 @@ module CSVDecision
|
|
17
17
|
# @return [Hash{Symbol=>Object}] Decision result.
|
18
18
|
def self.make(table:, input:, symbolize_keys:)
|
19
19
|
# Parse and transform the hash supplied as input
|
20
|
-
|
20
|
+
data = Input.parse(table: table, input: input, symbolize_keys: symbolize_keys)
|
21
21
|
|
22
22
|
# The decision object collects the results of the search and
|
23
|
-
# calculates the final result
|
24
|
-
|
25
|
-
|
26
|
-
# Use the table's index if present
|
27
|
-
table.index ? decision.index_scan : decision.table_scan
|
23
|
+
# calculates the final result.
|
24
|
+
Decision.new(table: table).scan(data)
|
28
25
|
end
|
29
26
|
|
27
|
+
# @return [Boolean] True if a first match decision table.
|
28
|
+
attr_reader :first_match
|
29
|
+
|
30
|
+
# @return [CSVDecision::Table] Decision table object.
|
31
|
+
attr_reader :table
|
32
|
+
|
30
33
|
# @param table [CSVDecision::Table] Decision table being processed.
|
31
|
-
|
32
|
-
def initialize(table:, input:)
|
34
|
+
def initialize(table:)
|
33
35
|
# The result object is a hash of values, and each value will be an array if this is
|
34
36
|
# a multi-row result for the +first_match: false+ option.
|
35
|
-
@result = Result.new(table: table
|
37
|
+
@result = Result.new(table: table)
|
38
|
+
@first_match = table.options[:first_match]
|
39
|
+
@table = table
|
40
|
+
end
|
41
|
+
|
42
|
+
# Initialize the input data used to make the decision.
|
43
|
+
#
|
44
|
+
# @param data [Hash{Symbol=>Object}] Input hash data structure.
|
45
|
+
# @return [void]
|
46
|
+
def input(data)
|
47
|
+
@result.input(data[:hash])
|
36
48
|
|
37
49
|
# All rows picked by the matching process. An array if +first_match: false+,
|
38
50
|
# otherwise a single row.
|
39
51
|
@rows_picked = []
|
40
52
|
|
41
|
-
@
|
53
|
+
@input = data
|
54
|
+
end
|
42
55
|
|
43
|
-
|
44
|
-
|
56
|
+
# Scan the decision table and produce an output decision.
|
57
|
+
#
|
58
|
+
# @param data [Hash{Symbol=>Object}] Input hash data structure.
|
59
|
+
# @return (see .make)
|
60
|
+
def scan(data)
|
61
|
+
input(data)
|
62
|
+
# Use the table's index if present
|
63
|
+
@table.index ? index_scan : table_scan
|
45
64
|
end
|
46
65
|
|
47
|
-
#
|
66
|
+
# Scan the index for a first match result.
|
48
67
|
#
|
49
|
-
# @
|
50
|
-
|
51
|
-
|
52
|
-
|
68
|
+
# @param scan_cols [Hash{Integer=>Object}]
|
69
|
+
# @param hash [Hash{Symbol=>Object}]
|
70
|
+
# @param index_rows [Array<Integer>]
|
71
|
+
# @return [Hash{Symbol=>Object}]
|
72
|
+
def index_scan_first_match(scan_cols:, hash:, index_rows:)
|
73
|
+
index_rows.each do |start_row, end_row|
|
74
|
+
@table.each(start_row, end_row || start_row) do |row, index|
|
75
|
+
next unless @table.scan_rows[index].match?(row: row, hash: hash, scan_cols: scan_cols)
|
53
76
|
|
54
|
-
|
55
|
-
|
56
|
-
else
|
57
|
-
scan_accumulate(hash: hash, scan_cols: scan_cols)
|
77
|
+
return @result.attributes if first_match_found(row)
|
78
|
+
end
|
58
79
|
end
|
80
|
+
|
81
|
+
{}
|
59
82
|
end
|
60
83
|
|
61
|
-
#
|
84
|
+
# Scan the index for an accumulated result.
|
62
85
|
#
|
63
|
-
# @
|
64
|
-
|
65
|
-
|
66
|
-
|
86
|
+
# @param scan_cols [Hash{Integer=>Object}]
|
87
|
+
# @param hash [Hash{Symbol=>Object}]
|
88
|
+
# @param index_rows [Array<Integer>]
|
89
|
+
# @return [Hash{Symbol=>Object}]
|
90
|
+
def index_scan_accumulate(scan_cols:, hash:, index_rows:)
|
91
|
+
index_rows.each do |start_row, end_row|
|
92
|
+
@table.each(start_row, end_row || start_row) do |row, index|
|
93
|
+
next unless @table.scan_rows[index].match?(row: row, hash: hash, scan_cols: scan_cols)
|
67
94
|
|
68
|
-
|
69
|
-
|
95
|
+
# Accumulate output rows.
|
96
|
+
@rows_picked << row
|
97
|
+
@result.accumulate_outs(row)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
@rows_picked.empty? ? {} : accumulated_result
|
102
|
+
end
|
70
103
|
|
104
|
+
private
|
105
|
+
|
106
|
+
# Use an index to scan the decision table up against the input hash.
|
107
|
+
def index_scan_rows(rows:)
|
71
108
|
if @first_match
|
72
|
-
index_scan_first_match(scan_cols: scan_cols, hash: hash, index_rows:
|
109
|
+
index_scan_first_match(scan_cols: @input[:scan_cols], hash: @input[:hash], index_rows: rows)
|
73
110
|
else
|
74
|
-
index_scan_accumulate(scan_cols: scan_cols, hash: hash, index_rows:
|
111
|
+
index_scan_accumulate(scan_cols: @input[:scan_cols], hash: @input[:hash], index_rows: rows)
|
75
112
|
end
|
76
113
|
end
|
77
114
|
|
78
|
-
private
|
79
|
-
|
80
115
|
def scan_first_match(hash:, scan_cols:)
|
81
116
|
@table.each do |row, index|
|
82
117
|
next unless @table.scan_rows[index].match?(row: row, hash: hash, scan_cols: scan_cols)
|
@@ -99,30 +134,21 @@ module CSVDecision
|
|
99
134
|
@rows_picked.empty? ? {} : accumulated_result
|
100
135
|
end
|
101
136
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
137
|
+
# Scan the decision table up against the input hash.
|
138
|
+
def table_scan
|
139
|
+
if @first_match
|
140
|
+
scan_first_match(hash: @input[:hash], scan_cols: @input[:scan_cols])
|
141
|
+
else
|
142
|
+
scan_accumulate(hash: @input[:hash], scan_cols: @input[:scan_cols])
|
109
143
|
end
|
110
|
-
|
111
|
-
{}
|
112
144
|
end
|
113
145
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
# Accumulate output rows.
|
120
|
-
@rows_picked << row
|
121
|
-
@result.accumulate_outs(row)
|
122
|
-
end
|
123
|
-
end
|
146
|
+
# Use an index to scan the decision table up against the input hash.
|
147
|
+
def index_scan
|
148
|
+
# If the index lookup fails, there's no match.
|
149
|
+
return {} unless (index_rows = Array(@table.index.hash[@input[:key]]))
|
124
150
|
|
125
|
-
|
151
|
+
index_scan_rows(rows: index_rows)
|
126
152
|
end
|
127
153
|
|
128
154
|
def accumulated_result
|
@@ -141,11 +167,11 @@ module CSVDecision
|
|
141
167
|
|
142
168
|
def eval_procs(col:, column:)
|
143
169
|
@rows_picked.each_with_index do |row, index|
|
144
|
-
|
145
|
-
next unless
|
170
|
+
cell = row[col]
|
171
|
+
next unless cell.is_a?(Matchers::Proc)
|
146
172
|
|
147
173
|
# Evaluate the proc and update the result
|
148
|
-
@result.eval_cell_proc(proc:
|
174
|
+
@result.eval_cell_proc(proc: cell, column_name: column.name, index: index)
|
149
175
|
end
|
150
176
|
end
|
151
177
|
|
@@ -33,7 +33,8 @@ module CSVDecision
|
|
33
33
|
out: { type: :out, eval: nil },
|
34
34
|
'out/text': { type: :out, eval: false },
|
35
35
|
guard: { type: :guard, eval: true },
|
36
|
-
if: { type: :if, eval: true }
|
36
|
+
if: { type: :if, eval: true },
|
37
|
+
path: { type: :path, eval: false }
|
37
38
|
}.freeze
|
38
39
|
private_constant :ENTRY
|
39
40
|
|
@@ -142,6 +143,9 @@ module CSVDecision
|
|
142
143
|
|
143
144
|
when :out, :if
|
144
145
|
output_entry(dictionary: dictionary, entry: entry, index: index)
|
146
|
+
|
147
|
+
when :path
|
148
|
+
dictionary.paths[index] = entry
|
145
149
|
end
|
146
150
|
|
147
151
|
dictionary
|
data/lib/csv_decision/header.rb
CHANGED
@@ -10,7 +10,7 @@ module CSVDecision
|
|
10
10
|
module Header
|
11
11
|
# Column types recognised in the header row.
|
12
12
|
COLUMN_TYPE = %r{
|
13
|
-
\A(?<type>in/text|in|out/text|out|guard|if|set/nil\?|set/blank\?|set)
|
13
|
+
\A(?<type>in/text|in|out/text|out|guard|if|set/nil\?|set/blank\?|set|path)
|
14
14
|
\s*:\s*(?<name>\S?.*)\z
|
15
15
|
}xi
|
16
16
|
|
data/lib/csv_decision/input.rb
CHANGED
@@ -13,12 +13,25 @@ module CSVDecision
|
|
13
13
|
def self.parse(table:, input:, symbolize_keys:)
|
14
14
|
validate(input)
|
15
15
|
|
16
|
-
parsed_input =
|
16
|
+
parsed_input =
|
17
|
+
parse_data(table: table, input: symbolize_keys ? input.symbolize_keys : input)
|
17
18
|
|
18
19
|
parsed_input[:key] = parse_key(table: table, hash: parsed_input[:hash]) if table.index
|
19
20
|
parsed_input
|
20
21
|
end
|
21
22
|
|
23
|
+
# @param table [CSVDecision::Table] Decision table.
|
24
|
+
# @param input [Hash] Input hash (keys may or may not be symbolized)
|
25
|
+
# @return [Hash{Symbol=>Object}]
|
26
|
+
def self.parse_data(table:, input:)
|
27
|
+
defaulted_columns = table.columns.defaults
|
28
|
+
|
29
|
+
# Code path optimized for no defaults
|
30
|
+
return parse_cells(table: table, input: input) if defaulted_columns.empty?
|
31
|
+
|
32
|
+
parse_defaulted(table: table, input: input, defaulted_columns: defaulted_columns)
|
33
|
+
end
|
34
|
+
|
22
35
|
def self.parse_key(table:, hash:)
|
23
36
|
return scan_key(table: table, hash: hash) if table.index.columns.count == 1
|
24
37
|
|
@@ -49,16 +62,6 @@ module CSVDecision
|
|
49
62
|
end
|
50
63
|
private_class_method :validate
|
51
64
|
|
52
|
-
def self.parse_input(table:, input:)
|
53
|
-
defaulted_columns = table.columns.defaults
|
54
|
-
|
55
|
-
# Code path optimized for no defaults
|
56
|
-
return parse_cells(table: table, input: input) if defaulted_columns.empty?
|
57
|
-
|
58
|
-
parse_defaulted(table: table, input: input, defaulted_columns: defaulted_columns)
|
59
|
-
end
|
60
|
-
private_class_method :parse_input
|
61
|
-
|
62
65
|
def self.parse_cells(table:, input:)
|
63
66
|
scan_cols = {}
|
64
67
|
table.columns.ins.each_pair do |col, column|
|
data/lib/csv_decision/parse.rb
CHANGED
@@ -28,7 +28,8 @@ module CSVDecision
|
|
28
28
|
#
|
29
29
|
# @param data [Pathname, File, Array<Array<String>>, String] input data given as
|
30
30
|
# a CSV file, array of arrays or CSV string.
|
31
|
-
# @param options [Hash{Symbol=>Object}] Options hash controlling how the table is parsed and
|
31
|
+
# @param options [Hash{Symbol=>Object}] Options hash controlling how the table is parsed and
|
32
|
+
# interpreted.
|
32
33
|
#
|
33
34
|
# @option options [Boolean] :first_match Stop scanning after finding the first row match.
|
34
35
|
# @option options [Boolean] :regexp_implicit Make regular expressions implicit rather than
|
@@ -88,8 +89,11 @@ module CSVDecision
|
|
88
89
|
# Parse table header and data rows with special cell matchers.
|
89
90
|
parse_with_matchers(table: table, matchers: CSVDecision::Matchers.new(options))
|
90
91
|
|
91
|
-
# Build the index if one is indicated
|
92
|
+
# Build the data index if one is indicated
|
92
93
|
Index.build(table: table)
|
94
|
+
|
95
|
+
# Build a paths index if one is indicated
|
96
|
+
Paths.scan(table: table)
|
93
97
|
end
|
94
98
|
private_class_method :parse_table
|
95
99
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# CSV Decision: CSV based Ruby decision tables.
|
4
|
+
# Created December 2017.
|
5
|
+
# @author Brett Vickers.
|
6
|
+
# See LICENSE and README.md for details.
|
7
|
+
module CSVDecision
|
8
|
+
# Build an index for a decision table with one or more input columns
|
9
|
+
# designated as keys
|
10
|
+
# @api private
|
11
|
+
class Paths
|
12
|
+
# Build the index of paths
|
13
|
+
#
|
14
|
+
# @param table [CSVDecision::Table] Decision table being indexed.
|
15
|
+
# @return [CSVDecision::Paths] The built index of paths.
|
16
|
+
def self.scan(table:)
|
17
|
+
# Do we even have paths?
|
18
|
+
columns = table.columns.paths.keys
|
19
|
+
return [] if columns.empty?
|
20
|
+
|
21
|
+
table.paths = Paths.new(table: table, columns: columns).paths
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param current_value [Integer, Array] Current path value.
|
25
|
+
# @param index [Integer] Array row index to be included in the path entry.
|
26
|
+
# @return [Integer, Array] New path key value.
|
27
|
+
def self.value(current_value, index)
|
28
|
+
return [current_value, index] if current_value.is_a?(Integer)
|
29
|
+
|
30
|
+
current_value[-1] = index
|
31
|
+
current_value
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param value [String] Cell value for the path: column.
|
35
|
+
# @return [nil, Symbol] Non-empty string converted to a symbol.
|
36
|
+
def self.symbol(value)
|
37
|
+
value.blank? ? nil : value.to_sym
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Hash] The index hash mapping in input values to one or more data array row indexes.
|
41
|
+
attr_reader :paths
|
42
|
+
|
43
|
+
# @param table [CSVDecision::Table] Decision table.
|
44
|
+
# @param columns [Array<Index>] Array of column indexes to be indexed.
|
45
|
+
def initialize(table:, columns:)
|
46
|
+
@paths = []
|
47
|
+
@columns = columns
|
48
|
+
|
49
|
+
build(table)
|
50
|
+
|
51
|
+
freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def build(table)
|
57
|
+
last_path = nil
|
58
|
+
key = -1
|
59
|
+
rows = nil
|
60
|
+
table.each do |row, index|
|
61
|
+
path = build_path(row: row)
|
62
|
+
if path == last_path
|
63
|
+
rows = Paths.value(rows, index)
|
64
|
+
else
|
65
|
+
rows = index
|
66
|
+
key += 1
|
67
|
+
last_path = path
|
68
|
+
end
|
69
|
+
|
70
|
+
@paths[key] = [path, rows]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_path(row:)
|
75
|
+
@columns.map { |col| Paths.symbol(row[col]) }.compact
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/csv_decision/result.rb
CHANGED
@@ -22,20 +22,26 @@ module CSVDecision
|
|
22
22
|
attr_reader :multi_result
|
23
23
|
|
24
24
|
# (see Decision.initialize)
|
25
|
-
def initialize(table
|
26
|
-
# Attributes hash contains the output decision key value pairs
|
27
|
-
@attributes = {}
|
28
|
-
|
25
|
+
def initialize(table:)
|
29
26
|
@outs = table.columns.outs
|
30
27
|
@outs_functions = table.outs_functions
|
31
|
-
@
|
28
|
+
@table = table
|
29
|
+
end
|
32
30
|
|
31
|
+
# Initialize the object for new input data.
|
32
|
+
#
|
33
|
+
# @param data [Hash{Symbol=>Object}] Input data hash.
|
34
|
+
# @return [void]
|
35
|
+
def input(data)
|
36
|
+
# Attributes hash contains the output decision key value pairs
|
37
|
+
@attributes = {}
|
38
|
+
@multi_result = false
|
33
39
|
# Partial result always copies in the input hash for calculating output functions.
|
34
40
|
# Note that these input key values will not be mutated, as output columns can never
|
35
41
|
# have the same symbol as an input hash key.
|
36
42
|
# However, the rest of this hash is mutated as output column evaluation results
|
37
43
|
# are accumulated.
|
38
|
-
@partial_result =
|
44
|
+
@partial_result = data.slice(*@table.columns.input_keys) if @outs_functions
|
39
45
|
end
|
40
46
|
|
41
47
|
# Common case for building a single row result is just copying output column values to the
|
@@ -57,7 +63,7 @@ module CSVDecision
|
|
57
63
|
# @return [Hash{Symbol=>Object}]
|
58
64
|
def final_result
|
59
65
|
# If there are no if: columns, then nothing needs to be filtered out of this result hash.
|
60
|
-
return @attributes if @
|
66
|
+
return @attributes if @table.columns.ifs.empty?
|
61
67
|
|
62
68
|
@multi_result ? multi_row_result : single_row_result
|
63
69
|
end
|
@@ -91,18 +97,18 @@ module CSVDecision
|
|
91
97
|
# Case where we have a single row result, which either gets returned
|
92
98
|
# or filtered by the if: column conditions.
|
93
99
|
def single_row_result
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@attributes
|
100
|
+
# All if: columns must evaluate to true
|
101
|
+
if @table.columns.ifs.keys.all? { |col| @attributes[col] }
|
102
|
+
# Delete if: columns from final result
|
103
|
+
@table.columns.ifs.each_key { |col| @attributes.delete(col) }
|
104
|
+
return @attributes
|
99
105
|
end
|
100
106
|
|
101
|
-
|
107
|
+
false
|
102
108
|
end
|
103
109
|
|
104
110
|
def multi_row_result
|
105
|
-
@
|
111
|
+
@table.columns.ifs.each_key { |col| check_if_column(col) }
|
106
112
|
|
107
113
|
normalize_result
|
108
114
|
end
|
@@ -131,46 +137,47 @@ module CSVDecision
|
|
131
137
|
count = @attributes.values.first.count
|
132
138
|
@multi_result = count > 1
|
133
139
|
|
134
|
-
|
135
|
-
|
136
|
-
{}
|
137
|
-
|
138
|
-
# Single row array values do not require arrays.
|
139
|
-
when 1
|
140
|
-
@attributes.transform_values!(&:first)
|
140
|
+
return {} if count.zero?
|
141
|
+
return @attributes.transform_values!(&:first) if count == 1
|
141
142
|
|
142
|
-
|
143
|
-
@attributes
|
144
|
-
end
|
143
|
+
@attributes
|
145
144
|
end
|
146
145
|
|
147
146
|
def eval_outs_constants(row:)
|
148
147
|
@outs.each_pair do |col, column|
|
149
|
-
|
150
|
-
next if
|
148
|
+
cell = row[col]
|
149
|
+
next if cell.is_a?(Matchers::Proc)
|
151
150
|
|
152
|
-
@partial_result[column.name] =
|
153
|
-
@attributes[column.name] =
|
151
|
+
@partial_result[column.name] = cell
|
152
|
+
@attributes[column.name] = cell
|
154
153
|
end
|
155
154
|
end
|
156
155
|
|
157
156
|
def eval_outs_procs(row:)
|
158
157
|
@outs.each_pair do |col, column|
|
159
|
-
|
160
|
-
next unless
|
158
|
+
cell = row[col]
|
159
|
+
next unless cell.is_a?(Matchers::Proc)
|
161
160
|
|
162
|
-
|
163
|
-
@partial_result[column.name] = @attributes[column.name]
|
161
|
+
eval_out_proc(cell: cell, column_name: column.name, column_type: column.type)
|
164
162
|
end
|
165
163
|
end
|
166
164
|
|
165
|
+
def eval_out_proc(cell:, column_name:, column_type:)
|
166
|
+
@attributes[column_name] = cell.function[@partial_result]
|
167
|
+
|
168
|
+
# Do not add if: columns to the partial result
|
169
|
+
return if column_type == :if
|
170
|
+
@partial_result[column_name] = @attributes[column_name]
|
171
|
+
end
|
172
|
+
|
167
173
|
def partial_result(index)
|
168
|
-
@attributes.each_pair do |column_name,
|
174
|
+
@attributes.each_pair do |column_name, values|
|
175
|
+
value = values[index]
|
169
176
|
# Delete this column from the partial result in case there is data from a prior result row
|
170
|
-
next @partial_result.delete(column_name) if value
|
177
|
+
next @partial_result.delete(column_name) if value.is_a?(Matchers::Proc)
|
171
178
|
|
172
179
|
# Add this constant value to the partial result row built so far.
|
173
|
-
@partial_result[column_name] = value
|
180
|
+
@partial_result[column_name] = value
|
174
181
|
end
|
175
182
|
|
176
183
|
@partial_result
|