csv_decision 0.1.0 → 0.2.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 +26 -2
- data/csv_decision.gemspec +1 -1
- data/doc/CSVDecision/CellValidationError.html +2 -2
- data/doc/CSVDecision/Columns/Default.html +203 -23
- data/doc/CSVDecision/Columns/Dictionary.html +118 -25
- data/doc/CSVDecision/Columns.html +213 -31
- data/doc/CSVDecision/Data.html +1 -1
- data/doc/CSVDecision/Decide.html +1 -1
- data/doc/CSVDecision/Decision.html +1 -1
- data/doc/CSVDecision/Defaults.html +291 -0
- data/doc/CSVDecision/Dictionary/Entry.html +584 -47
- data/doc/CSVDecision/Dictionary.html +20 -20
- data/doc/CSVDecision/Error.html +2 -2
- data/doc/CSVDecision/FileError.html +1 -1
- data/doc/CSVDecision/Header.html +110 -33
- data/doc/CSVDecision/Input.html +1 -1
- data/doc/CSVDecision/Load.html +1 -1
- data/doc/CSVDecision/Matchers/Constant.html +12 -37
- data/doc/CSVDecision/Matchers/Function.html +1 -1
- data/doc/CSVDecision/Matchers/Guard.html +15 -13
- data/doc/CSVDecision/Matchers/Matcher.html +1 -1
- data/doc/CSVDecision/Matchers/Numeric.html +13 -21
- data/doc/CSVDecision/Matchers/Pattern.html +14 -14
- data/doc/CSVDecision/Matchers/Proc.html +1 -1
- data/doc/CSVDecision/Matchers/Range.html +13 -58
- data/doc/CSVDecision/Matchers/Symbol.html +1 -1
- data/doc/CSVDecision/Matchers.html +9 -9
- data/doc/CSVDecision/Options.html +1 -1
- data/doc/CSVDecision/Parse.html +90 -19
- data/doc/CSVDecision/Result.html +1 -1
- data/doc/CSVDecision/ScanRow.html +17 -17
- data/doc/CSVDecision/Table.html +50 -48
- data/doc/CSVDecision/TableValidationError.html +143 -0
- data/doc/CSVDecision/Validate.html +422 -0
- data/doc/CSVDecision.html +8 -8
- data/doc/_index.html +33 -1
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +27 -3
- data/doc/index.html +27 -3
- data/doc/method_list.html +193 -89
- data/doc/top-level-namespace.html +1 -1
- data/lib/csv_decision/columns.rb +28 -27
- data/lib/csv_decision/defaults.rb +47 -0
- data/lib/csv_decision/dictionary.rb +104 -112
- data/lib/csv_decision/header.rb +13 -10
- data/lib/csv_decision/input.rb +53 -5
- data/lib/csv_decision/matchers/constant.rb +1 -2
- data/lib/csv_decision/matchers/guard.rb +3 -2
- data/lib/csv_decision/matchers/numeric.rb +4 -6
- data/lib/csv_decision/matchers/pattern.rb +6 -8
- data/lib/csv_decision/matchers/range.rb +1 -3
- data/lib/csv_decision/matchers.rb +7 -7
- data/lib/csv_decision/parse.rb +24 -3
- data/lib/csv_decision/scan_row.rb +16 -16
- data/lib/csv_decision/table.rb +3 -7
- data/lib/csv_decision/validate.rb +85 -0
- data/lib/csv_decision.rb +3 -1
- data/spec/csv_decision/columns_spec.rb +38 -22
- data/spec/csv_decision/examples_spec.rb +17 -0
- data/spec/csv_decision/matchers/range_spec.rb +0 -32
- data/spec/csv_decision/table_spec.rb +39 -0
- metadata +7 -2
@@ -51,9 +51,6 @@ module CSVDecision
|
|
51
51
|
# Negation sign prefixed to ranges and functions.
|
52
52
|
NEGATE = '!'
|
53
53
|
|
54
|
-
# Cell constants and functions specified by prefixing the value with one of these 3 symbols.
|
55
|
-
EQUALS = '==|:=|='
|
56
|
-
|
57
54
|
# All regular expressions used for matching are anchored inside their own
|
58
55
|
# non-capturing group.
|
59
56
|
#
|
@@ -63,6 +60,9 @@ module CSVDecision
|
|
63
60
|
Regexp.new("\\A(?:#{value})\\z").freeze
|
64
61
|
end
|
65
62
|
|
63
|
+
# Cell constants and functions specified by prefixing the value with one of these 3 symbols.
|
64
|
+
EQUALS = '==|:=|='
|
65
|
+
|
66
66
|
EQUALS_RE = regexp(EQUALS)
|
67
67
|
private_constant :EQUALS_RE
|
68
68
|
|
@@ -71,7 +71,7 @@ module CSVDecision
|
|
71
71
|
# @param operator [String]
|
72
72
|
# @return [String]
|
73
73
|
def self.normalize_operator(operator)
|
74
|
-
EQUALS_RE.match(operator) ? '==' : operator
|
74
|
+
EQUALS_RE.match?(operator) ? '==' : operator
|
75
75
|
end
|
76
76
|
|
77
77
|
# Regular expression used to recognise a numeric string with or without a decimal point.
|
@@ -135,9 +135,9 @@ module CSVDecision
|
|
135
135
|
|
136
136
|
# @param options (see CSVDecision.parse)
|
137
137
|
def initialize(options)
|
138
|
-
|
139
|
-
@ins =
|
140
|
-
@outs =
|
138
|
+
matchers = options[:matchers].collect { |klass| klass.new(options) }
|
139
|
+
@ins = matchers.select(&:ins?)
|
140
|
+
@outs = matchers.select(&:outs?)
|
141
141
|
end
|
142
142
|
|
143
143
|
# Parse the row's input columns using the input matchers.
|
data/lib/csv_decision/parse.rb
CHANGED
@@ -9,6 +9,9 @@ module CSVDecision
|
|
9
9
|
class Error < StandardError; end
|
10
10
|
|
11
11
|
# Error validating a cell when parsing input table data.
|
12
|
+
class TableValidationError < Error; end
|
13
|
+
|
14
|
+
# Error validating a cell when parsing input table cell data.
|
12
15
|
class CellValidationError < Error; end
|
13
16
|
|
14
17
|
# Table parsing error message enhanced to include the file being processed.
|
@@ -82,13 +85,31 @@ module CSVDecision
|
|
82
85
|
# These override any options passed as parameters to the parse method.
|
83
86
|
table.options = Options.from_csv(rows: table.rows, options: options).freeze
|
84
87
|
|
88
|
+
# Matchers
|
89
|
+
matchers = CSVDecision::Matchers.new(options)
|
90
|
+
|
85
91
|
# Parse the header row
|
86
|
-
table.columns =
|
92
|
+
table.columns = parse_header(table: table, matchers: matchers)
|
87
93
|
|
88
|
-
|
94
|
+
# Parse the table's the data rows.
|
95
|
+
parse_data(table: table, matchers: matchers)
|
89
96
|
end
|
90
97
|
private_class_method :parse_table
|
91
98
|
|
99
|
+
def self.parse_header(table:, matchers:)
|
100
|
+
# Parse the header row
|
101
|
+
table.columns = CSVDecision::Columns.new(table)
|
102
|
+
|
103
|
+
# Parse the defaults row if present
|
104
|
+
return table.columns if table.columns.defaults.blank?
|
105
|
+
|
106
|
+
table.columns.defaults =
|
107
|
+
Defaults.parse(columns: table.columns, matchers: matchers.outs, row: table.rows.shift)
|
108
|
+
|
109
|
+
table.columns
|
110
|
+
end
|
111
|
+
private_class_method :parse_header
|
112
|
+
|
92
113
|
def self.parse_data(table:, matchers:)
|
93
114
|
table.rows.each_with_index do |row, index|
|
94
115
|
# Mutate the row if we find anything other than a simple string constant in its
|
@@ -201,7 +222,7 @@ module CSVDecision
|
|
201
222
|
|
202
223
|
add_ins_symbols(columns: columns, cell: cell)
|
203
224
|
end
|
204
|
-
private_class_method :ins_cell_dictionary
|
225
|
+
# private_class_method :ins_cell_dictionary
|
205
226
|
|
206
227
|
def self.add_ins_symbols(columns:, cell:)
|
207
228
|
Array(cell.symbols).each do |symbol|
|
@@ -27,6 +27,21 @@ module CSVDecision
|
|
27
27
|
invalid_constant?(type: :constant, column: column)
|
28
28
|
end
|
29
29
|
|
30
|
+
# Evaluate the cell proc against the column's input value and/or input hash.
|
31
|
+
#
|
32
|
+
# @param proc [CSVDecision::Proc] Proc in the table cell.
|
33
|
+
# @param value [Object] Value supplied in the input hash corresponding to this column.
|
34
|
+
# @param hash [{Symbol=>Object}] Input hash with symbolized keys.
|
35
|
+
def self.eval_matcher(proc:, hash:, value: nil)
|
36
|
+
function = proc.function
|
37
|
+
|
38
|
+
# A symbol guard expression just needs to be passed the input hash
|
39
|
+
return function[hash] if proc.type == :guard
|
40
|
+
|
41
|
+
# All other procs can take one or two args
|
42
|
+
function.arity == 1 ? function[value] : function[value, hash]
|
43
|
+
end
|
44
|
+
|
30
45
|
def self.scan_matchers(column:, matchers:, cell:)
|
31
46
|
matchers.each do |matcher|
|
32
47
|
# Guard function only accepts the same matchers as an output column.
|
@@ -62,21 +77,6 @@ module CSVDecision
|
|
62
77
|
end
|
63
78
|
private_class_method :invalid_constant?
|
64
79
|
|
65
|
-
# Evaluate the cell proc against the column's input value and/or input hash.
|
66
|
-
#
|
67
|
-
# @param proc [CSVDecision::Proc] Proc in the table cell.
|
68
|
-
# @param value [Object] Value supplied in the input hash corresponding to this column.
|
69
|
-
# @param hash [{Symbol=>Object}] Input hash with symbolized keys.
|
70
|
-
def self.eval_matcher(proc:, hash:, value: nil)
|
71
|
-
function = proc.function
|
72
|
-
|
73
|
-
# A symbol guard expression just needs to be passed the input hash
|
74
|
-
return function[hash] if proc.type == :guard
|
75
|
-
|
76
|
-
# All other procs can take one or two args
|
77
|
-
function.arity == 1 ? function[value] : function[value, hash]
|
78
|
-
end
|
79
|
-
|
80
80
|
# @return [Array<Integer>] Column indices for simple constants.
|
81
81
|
attr_reader :constants
|
82
82
|
|
@@ -91,7 +91,7 @@ module CSVDecision
|
|
91
91
|
# Scan all the specified +columns+ (e.g., inputs) in the given +data+ row using the +matchers+
|
92
92
|
# array supplied.
|
93
93
|
#
|
94
|
-
# @param row [Array] Data row.
|
94
|
+
# @param row [Array<String>] Data row - still just all string constants.
|
95
95
|
# @param columns [Array<Columns::Entry>] Array of column dictionary entries.
|
96
96
|
# @param matchers [Array<Matchers::Matcher>] Array of table cell matchers.
|
97
97
|
# @return [Array] Data row with anything not a string constant replaced with a Proc or a
|
data/lib/csv_decision/table.rb
CHANGED
@@ -11,12 +11,13 @@ module CSVDecision
|
|
11
11
|
#
|
12
12
|
# @note Input hash keys may or may not be symbolized.
|
13
13
|
# @param input [Hash] Input hash.
|
14
|
-
# @return [
|
14
|
+
# @return [{Symbol => Object, Array<Object>}] Decision hash.
|
15
15
|
def decide(input)
|
16
16
|
Decide.decide(table: self, input: input, symbolize_keys: true)
|
17
17
|
end
|
18
18
|
|
19
|
-
# Unsafe version of decide -
|
19
|
+
# Unsafe version of decide - may mutate the input hash and assumes the input
|
20
|
+
# hash is symbolized.
|
20
21
|
#
|
21
22
|
# @param input (see #decide)
|
22
23
|
# @note Input hash must have its keys symbolized.
|
@@ -56,11 +57,6 @@ module CSVDecision
|
|
56
57
|
# @api private
|
57
58
|
attr_accessor :if_rows
|
58
59
|
|
59
|
-
# @return Array<CSVDecision::Table>] pre-loaded tables passed to this decision table
|
60
|
-
# at load time. Used to allow this decision table to lookup values in other
|
61
|
-
# decision tables. (Planned feature.)
|
62
|
-
# attr_reader :tables
|
63
|
-
|
64
60
|
# Iterate through all data rows of the decision table, with an optional
|
65
61
|
# first and last row index given.
|
66
62
|
#
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# CSV Decision: CSV based Ruby decision tables.
|
4
|
+
# Created December 2017.
|
5
|
+
# @author Brett Vickers <brett@phillips-vickers.com>
|
6
|
+
# See LICENSE and README.md for details.
|
7
|
+
module CSVDecision
|
8
|
+
# Parse and validate the column names in the header row.
|
9
|
+
# These methods are only required at table load time.
|
10
|
+
# @api private
|
11
|
+
module Validate
|
12
|
+
# These column types do not need a name.
|
13
|
+
COLUMN_TYPE_ANONYMOUS = Set.new(%i[guard if]).freeze
|
14
|
+
private_constant :COLUMN_TYPE_ANONYMOUS
|
15
|
+
|
16
|
+
# Validate a column header cell and return its type and name.
|
17
|
+
#
|
18
|
+
# @param cell [String] Header cell.
|
19
|
+
# @param index [Integer] The header column's index.
|
20
|
+
# @return [Array<(Symbol, Symbol)>] Column type and column name symbols.
|
21
|
+
def self.column(cell:, index:)
|
22
|
+
match = Header::COLUMN_TYPE.match(cell)
|
23
|
+
raise CellValidationError, 'column name is not well formed' unless match
|
24
|
+
|
25
|
+
column_type = match['type']&.downcase&.to_sym
|
26
|
+
column_name = column_name(type: column_type, name: match['name'], index: index)
|
27
|
+
|
28
|
+
[column_type, column_name]
|
29
|
+
rescue CellValidationError => exp
|
30
|
+
raise CellValidationError, "header column '#{cell}' is not valid as the #{exp.message}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Validate the column name against the dictionary of column names.
|
34
|
+
#
|
35
|
+
# @param columns [Symbol=>[false, Integer]] Column name dictionary.
|
36
|
+
# @param name [Symbol] Column name.
|
37
|
+
# @param out [false, Integer] False if an input column, otherwise the column index of
|
38
|
+
# the output column.
|
39
|
+
# @return [void]
|
40
|
+
# @raise [CellValidationError] Column name invalid.
|
41
|
+
def self.name(columns:, name:, out:)
|
42
|
+
return unless (in_out = columns[name])
|
43
|
+
|
44
|
+
return validate_out_name(in_out: in_out, name: name) if out
|
45
|
+
validate_in_name(in_out: in_out, name: name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.column_name(type:, name:, index:)
|
49
|
+
# if: columns are named after their index, which is an integer and so cannot
|
50
|
+
# clash with other column name types, which are symbols.
|
51
|
+
return index if type == :if
|
52
|
+
|
53
|
+
return format_column_name(name) if name.present?
|
54
|
+
|
55
|
+
return if COLUMN_TYPE_ANONYMOUS.member?(type)
|
56
|
+
raise CellValidationError, 'column name is missing'
|
57
|
+
end
|
58
|
+
private_class_method :column_name
|
59
|
+
|
60
|
+
def self.format_column_name(name)
|
61
|
+
column_name = name.strip.tr("\s", '_')
|
62
|
+
|
63
|
+
return column_name.to_sym if Header.column_name?(column_name)
|
64
|
+
raise CellValidationError, "column name '#{name}' contains invalid characters"
|
65
|
+
end
|
66
|
+
private_class_method :format_column_name
|
67
|
+
|
68
|
+
def self.validate_out_name(in_out:, name:)
|
69
|
+
if in_out == :in
|
70
|
+
raise CellValidationError, "output column name '#{name}' is also an input column"
|
71
|
+
end
|
72
|
+
|
73
|
+
raise CellValidationError, "output column name '#{name}' is duplicated"
|
74
|
+
end
|
75
|
+
private_class_method :validate_out_name
|
76
|
+
|
77
|
+
def self.validate_in_name(in_out:, name:)
|
78
|
+
# in: columns may be duped
|
79
|
+
return if in_out == :in
|
80
|
+
|
81
|
+
raise CellValidationError, "output column name '#{name}' is also an input column"
|
82
|
+
end
|
83
|
+
private_class_method :validate_in_name
|
84
|
+
end
|
85
|
+
end
|
data/lib/csv_decision.rb
CHANGED
@@ -14,10 +14,11 @@ module CSVDecision
|
|
14
14
|
end
|
15
15
|
|
16
16
|
autoload :Columns, 'csv_decision/columns'
|
17
|
-
autoload :Dictionary, 'csv_decision/dictionary'
|
18
17
|
autoload :Data, 'csv_decision/data'
|
19
18
|
autoload :Decide, 'csv_decision/decide'
|
20
19
|
autoload :Decision, 'csv_decision/decision'
|
20
|
+
autoload :Defaults, 'csv_decision/defaults'
|
21
|
+
autoload :Dictionary, 'csv_decision/dictionary'
|
21
22
|
autoload :Header, 'csv_decision/header'
|
22
23
|
autoload :Input, 'csv_decision/input'
|
23
24
|
autoload :Load, 'csv_decision/load'
|
@@ -27,6 +28,7 @@ module CSVDecision
|
|
27
28
|
autoload :Result, 'csv_decision/result'
|
28
29
|
autoload :ScanRow, 'csv_decision/scan_row'
|
29
30
|
autoload :Table, 'csv_decision/table'
|
31
|
+
autoload :Validate, 'csv_decision/validate'
|
30
32
|
|
31
33
|
class Matchers
|
32
34
|
autoload :Constant, 'csv_decision/matchers/constant'
|
@@ -33,10 +33,10 @@ describe CSVDecision::Columns do
|
|
33
33
|
table = CSVDecision.parse(data)
|
34
34
|
|
35
35
|
expect(table.columns).to be_a(CSVDecision::Columns)
|
36
|
-
expect(table.columns.ins[0].to_h).to eq(name: :input, eval: nil, type: :in)
|
37
|
-
expect(table.columns.ins[2].to_h).to eq(name: :input, eval: false, type: :in)
|
38
|
-
expect(table.columns.outs[1].to_h).to eq(name: :output, eval: nil, type: :out)
|
39
|
-
expect(table.columns.outs[3].to_h).to eq(name: :output2, eval: false, type: :out)
|
36
|
+
expect(table.columns.ins[0].to_h).to eq(name: :input, eval: nil, type: :in, set_if: nil)
|
37
|
+
expect(table.columns.ins[2].to_h).to eq(name: :input, eval: false, type: :in, set_if: nil)
|
38
|
+
expect(table.columns.outs[1].to_h).to eq(name: :output, eval: nil, type: :out, set_if: nil)
|
39
|
+
expect(table.columns.outs[3].to_h).to eq(name: :output2, eval: false, type: :out, set_if: nil)
|
40
40
|
|
41
41
|
expect(table.columns.dictionary).to eq(input: :in, output: 1, output2: 3)
|
42
42
|
end
|
@@ -50,13 +50,13 @@ describe CSVDecision::Columns do
|
|
50
50
|
table = CSVDecision.parse(data)
|
51
51
|
|
52
52
|
expect(table.columns).to be_a(CSVDecision::Columns)
|
53
|
-
expect(table.columns.ins[0].to_h).to eq(name: :input, eval: nil, type: :in)
|
54
|
-
expect(table.columns.ins[2].to_h).to eq(name: :input, eval: false, type: :in)
|
55
|
-
expect(table.columns.ins[5].to_h).to eq(name: nil, eval: true, type: :guard)
|
53
|
+
expect(table.columns.ins[0].to_h).to eq(name: :input, eval: nil, type: :in, set_if: nil)
|
54
|
+
expect(table.columns.ins[2].to_h).to eq(name: :input, eval: false, type: :in, set_if: nil)
|
55
|
+
expect(table.columns.ins[5].to_h).to eq(name: nil, eval: true, type: :guard, set_if: nil)
|
56
56
|
|
57
|
-
expect(table.columns.outs[1].to_h).to eq(name: :output, eval: nil, type: :out)
|
58
|
-
expect(table.columns.outs[3].to_h).to eq(name: :output2, eval: false, type: :out)
|
59
|
-
expect(table.columns.outs[4].to_h).to eq(name: :len, eval: true, type: :out)
|
57
|
+
expect(table.columns.outs[1].to_h).to eq(name: :output, eval: nil, type: :out, set_if: nil)
|
58
|
+
expect(table.columns.outs[3].to_h).to eq(name: :output2, eval: false, type: :out, set_if: nil)
|
59
|
+
expect(table.columns.outs[4].to_h).to eq(name: :len, eval: true, type: :out, set_if: nil)
|
60
60
|
|
61
61
|
expect(table.columns.dictionary)
|
62
62
|
.to eq(input: :in, output: 1, output2: 3, len: 4, input2: :in, input3: :in, input4: :in)
|
@@ -74,12 +74,12 @@ describe CSVDecision::Columns do
|
|
74
74
|
table = CSVDecision.parse(data)
|
75
75
|
|
76
76
|
expect(table.columns).to be_a(CSVDecision::Columns)
|
77
|
-
expect(table.columns.ins[0].to_h).to eq(name: :input, eval: nil, type: :in)
|
78
|
-
expect(table.columns.ins[2].to_h).to eq(name: :input, eval: false, type: :in)
|
79
|
-
expect(table.columns.outs[1].to_h).to eq(name: :output, eval: nil, type: :out)
|
80
|
-
expect(table.columns.outs[3].to_h).to eq(name: :output2, eval: false, type: :out)
|
81
|
-
expect(table.columns.outs[4].to_h).to eq(name: :input3, eval: true, type: :out)
|
82
|
-
expect(table.columns.outs[5].to_h).to eq(name: :len, eval: true, type: :out)
|
77
|
+
expect(table.columns.ins[0].to_h).to eq(name: :input, eval: nil, type: :in, set_if: nil)
|
78
|
+
expect(table.columns.ins[2].to_h).to eq(name: :input, eval: false, type: :in, set_if: nil)
|
79
|
+
expect(table.columns.outs[1].to_h).to eq(name: :output, eval: nil, type: :out, set_if: nil)
|
80
|
+
expect(table.columns.outs[3].to_h).to eq(name: :output2, eval: false, type: :out, set_if: nil)
|
81
|
+
expect(table.columns.outs[4].to_h).to eq(name: :input3, eval: true, type: :out, set_if: nil)
|
82
|
+
expect(table.columns.outs[5].to_h).to eq(name: :len, eval: true, type: :out, set_if: nil)
|
83
83
|
|
84
84
|
expect(table.columns.dictionary)
|
85
85
|
.to eq(input: :in, output: 1, output2: 3, len: 5, input2: :in, input3: 4, input4: :in)
|
@@ -118,10 +118,10 @@ describe CSVDecision::Columns do
|
|
118
118
|
result = CSVDecision.parse(file)
|
119
119
|
|
120
120
|
expect(result.columns).to be_a(CSVDecision::Columns)
|
121
|
-
expect(result.columns.ins)
|
122
|
-
|
123
|
-
expect(result.columns.
|
124
|
-
|
121
|
+
expect(result.columns.ins.count).to eq 1
|
122
|
+
expect(result.columns.outs.count).to eq 1
|
123
|
+
expect(result.columns.ins[0].to_h).to eql(name: :input, type: :in, eval: nil, set_if: nil)
|
124
|
+
expect(result.columns.outs[1].to_h).to eql(name: :output, type: :out, eval: nil, set_if: nil)
|
125
125
|
end
|
126
126
|
|
127
127
|
it 'rejects an invalid header column' do
|
@@ -179,7 +179,7 @@ describe CSVDecision::Columns do
|
|
179
179
|
table = CSVDecision.parse(data)
|
180
180
|
|
181
181
|
expect(table.columns.ins[1].to_h)
|
182
|
-
.to eq(name: nil, eval: true, type: :guard)
|
182
|
+
.to eq(name: nil, eval: true, type: :guard, set_if: nil)
|
183
183
|
|
184
184
|
expect(table.columns.input_keys).to eq %i[country CUSIP SEDOL]
|
185
185
|
end
|
@@ -215,7 +215,23 @@ describe CSVDecision::Columns do
|
|
215
215
|
DATA
|
216
216
|
table = CSVDecision.parse(data)
|
217
217
|
|
218
|
-
expect(table.columns.ifs[3].to_h).to eq(name: 3, eval: true, type: :if)
|
218
|
+
expect(table.columns.ifs[3].to_h).to eq(name: 3, eval: true, type: :if, set_if: nil)
|
219
219
|
expect(table.columns.input_keys).to eq %i[country CUSIP SEDOL]
|
220
220
|
end
|
221
|
+
|
222
|
+
it 'recognises the set: columns' do
|
223
|
+
data = <<~DATA
|
224
|
+
set/nil? :country, guard:, set: class, out :PAID, out: len, if:
|
225
|
+
US, , :class.upcase,
|
226
|
+
US, :CUSIP.present?, != PRIVATE, :CUSIP, :PAID.length, :len == 9
|
227
|
+
!=US, :ISIN.present?, != PRIVATE, :ISIN, :PAID.length, :len == 12
|
228
|
+
DATA
|
229
|
+
table = CSVDecision.parse(data)
|
230
|
+
|
231
|
+
expect(table.columns.ins[0].to_h).to eq(name: :country, eval: nil, type: :set, set_if: :nil?)
|
232
|
+
expect(table.columns.ins[1].to_h).to eq(name: nil, eval: true, type: :guard, set_if: nil)
|
233
|
+
expect(table.columns.ins[2].to_h).to eq(name: :class, eval: nil, type: :set, set_if: true)
|
234
|
+
|
235
|
+
expect(table.columns.input_keys).to eq %i[country class CUSIP ISIN]
|
236
|
+
end
|
221
237
|
end
|
@@ -180,4 +180,21 @@ context 'simple examples' do
|
|
180
180
|
expect(table.decide(CUSIP: '12345678', ISIN:'1234567890'))
|
181
181
|
.to eq(ID: '1234567890', ID_type: 'DUMMY', len: 10)
|
182
182
|
end
|
183
|
+
|
184
|
+
it 'recognises the set: columns and uses correct defaults' do
|
185
|
+
data = <<~DATA
|
186
|
+
set/nil? :country, guard:, set: class, out :PAID, out: len, if:
|
187
|
+
US, , :class.upcase,
|
188
|
+
US, :CUSIP.present?, != PRIVATE, :CUSIP, :PAID.length, :len == 9
|
189
|
+
!=US, :ISIN.present?, != PRIVATE, :ISIN, :PAID.length, :len == 12
|
190
|
+
US, :CUSIP.present?, PRIVATE, :CUSIP, :PAID.length,
|
191
|
+
!=US, :ISIN.present?, PRIVATE, :ISIN, :PAID.length,
|
192
|
+
DATA
|
193
|
+
|
194
|
+
table = CSVDecision.parse(data)
|
195
|
+
expect(table.decide(CUSIP: '1234567890', class: 'Private')).to eq(PAID: '1234567890', len: 10)
|
196
|
+
expect(table.decide(CUSIP: '123456789', class: 'Public')).to eq(PAID: '123456789', len: 9)
|
197
|
+
expect(table.decide(ISIN: '123456789', country: 'GB', class: 'public')).to eq({})
|
198
|
+
expect(table.decide(ISIN: '123456789012', country: 'GB', class: 'private')).to eq(PAID: '123456789012', len: 12)
|
199
|
+
end
|
183
200
|
end
|
@@ -10,38 +10,6 @@ describe CSVDecision::Matchers::Range do
|
|
10
10
|
it { is_expected.to respond_to(:matches?).with(1).argument }
|
11
11
|
end
|
12
12
|
|
13
|
-
# context 'cell value matching' do
|
14
|
-
# ranges = {
|
15
|
-
# '-1..1' => { min: '-1', type: '..', max: '1', negate: '' },
|
16
|
-
# '! -1..1' => { min: '-1', type: '..', max: '1', negate: '!' },
|
17
|
-
# '!-1.0..1.1' => { min: '-1.0', type: '..', max: '1.1', negate: '!' },
|
18
|
-
# '!-1.0...1.1' => { min: '-1.0', type: '...', max: '1.1', negate: '!' }
|
19
|
-
# }
|
20
|
-
# ranges.each_pair do |range, expected|
|
21
|
-
# it "matches #{range} as a numeric range" do
|
22
|
-
# match = described_class::NUMERIC_RANGE.match(range)
|
23
|
-
# expect(match['min']).to eq expected[:min]
|
24
|
-
# expect(match['max']).to eq expected[:max]
|
25
|
-
# expect(match['type']).to eq expected[:type]
|
26
|
-
# expect(match['negate']).to eq expected[:negate]
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
# ranges = {
|
31
|
-
# 'a..z' => { min: 'a', type: '..', max: 'z', negate: '' },
|
32
|
-
# '!1...9' => { min: '1', type: '...', max: '9', negate: '!' },
|
33
|
-
# }
|
34
|
-
# ranges.each_pair do |range, expected|
|
35
|
-
# it "matches #{range} as an alphanumeric range" do
|
36
|
-
# match = described_class::ALNUM_RANGE.match(range)
|
37
|
-
# expect(match['min']).to eq expected[:min]
|
38
|
-
# expect(match['max']).to eq expected[:max]
|
39
|
-
# expect(match['type']).to eq expected[:type]
|
40
|
-
# expect(match['negate']).to eq expected[:negate]
|
41
|
-
# end
|
42
|
-
# end
|
43
|
-
# end
|
44
|
-
|
45
13
|
describe '#matches?' do
|
46
14
|
matcher = described_class.new
|
47
15
|
|
@@ -451,5 +451,44 @@ describe CSVDecision::Table do
|
|
451
451
|
end
|
452
452
|
end
|
453
453
|
end
|
454
|
+
|
455
|
+
context 'recognises the set: columns and uses correct defaults' do
|
456
|
+
examples = [
|
457
|
+
{ example: 'evaluates set/nil? and set columns',
|
458
|
+
options: { first_match: true },
|
459
|
+
data: <<~DATA
|
460
|
+
set/nil? :country, in: type, guard:, set: class, out :PAID, out: len, if:
|
461
|
+
US, , , :class.upcase,
|
462
|
+
US, Equity, :CUSIP.present?, != PRIVATE, :CUSIP, :PAID.length, :len == 9
|
463
|
+
!=US, Equity, :ISIN.present?, != PRIVATE, :ISIN, :PAID.length, :len == 12
|
464
|
+
US, , :CUSIP.present?, PRIVATE, :CUSIP, :PAID.length,
|
465
|
+
!=US, , :ISIN.present?, PRIVATE, :ISIN, :PAID.length,
|
466
|
+
DATA
|
467
|
+
},
|
468
|
+
{ example: 'evaluates set/blank? and set columns',
|
469
|
+
options: { first_match: true },
|
470
|
+
data: <<~DATA
|
471
|
+
set/blank? :country, in: type, guard:, set: class, out :PAID, out: len, if:
|
472
|
+
US, , , :class.upcase,
|
473
|
+
US, Equity, :CUSIP.present?, != PRIVATE, :CUSIP, :PAID.length, :len == 9
|
474
|
+
!=US, Equity, :ISIN.present?, != PRIVATE, :ISIN, :PAID.length, :len == 12
|
475
|
+
US, , :CUSIP.present?, PRIVATE, :CUSIP, :PAID.length,
|
476
|
+
!=US, , :ISIN.present?, PRIVATE, :ISIN, :PAID.length,
|
477
|
+
DATA
|
478
|
+
}
|
479
|
+
]
|
480
|
+
examples.each do |test|
|
481
|
+
%i[decide decide!].each do |method|
|
482
|
+
it "#{method} correctly #{test[:example]}" do
|
483
|
+
table = CSVDecision.parse(test[:data], test[:options])
|
484
|
+
|
485
|
+
expect(table.send(method, CUSIP: '1234567890', class: 'Private')).to eq(PAID: '1234567890', len: 10)
|
486
|
+
expect(table.send(method, CUSIP: '123456789', type: 'Equity', class: 'Public')).to eq(PAID: '123456789', len: 9)
|
487
|
+
expect(table.send(method, ISIN: '123456789', country: 'GB', class: 'public')).to eq({})
|
488
|
+
expect(table.send(method, ISIN: '123456789012', country: 'GB', class: 'private')).to eq(PAID: '123456789012', len: 12)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
454
493
|
end
|
455
494
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_decision
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Vickers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -179,6 +179,7 @@ files:
|
|
179
179
|
- doc/CSVDecision/Data.html
|
180
180
|
- doc/CSVDecision/Decide.html
|
181
181
|
- doc/CSVDecision/Decision.html
|
182
|
+
- doc/CSVDecision/Defaults.html
|
182
183
|
- doc/CSVDecision/Dictionary.html
|
183
184
|
- doc/CSVDecision/Dictionary/Entry.html
|
184
185
|
- doc/CSVDecision/Error.html
|
@@ -205,6 +206,8 @@ files:
|
|
205
206
|
- doc/CSVDecision/ScanRow.html
|
206
207
|
- doc/CSVDecision/Symbol.html
|
207
208
|
- doc/CSVDecision/Table.html
|
209
|
+
- doc/CSVDecision/TableValidationError.html
|
210
|
+
- doc/CSVDecision/Validate.html
|
208
211
|
- doc/_index.html
|
209
212
|
- doc/class_list.html
|
210
213
|
- doc/css/common.css
|
@@ -224,6 +227,7 @@ files:
|
|
224
227
|
- lib/csv_decision/data.rb
|
225
228
|
- lib/csv_decision/decide.rb
|
226
229
|
- lib/csv_decision/decision.rb
|
230
|
+
- lib/csv_decision/defaults.rb
|
227
231
|
- lib/csv_decision/dictionary.rb
|
228
232
|
- lib/csv_decision/header.rb
|
229
233
|
- lib/csv_decision/input.rb
|
@@ -241,6 +245,7 @@ files:
|
|
241
245
|
- lib/csv_decision/result.rb
|
242
246
|
- lib/csv_decision/scan_row.rb
|
243
247
|
- lib/csv_decision/table.rb
|
248
|
+
- lib/csv_decision/validate.rb
|
244
249
|
- spec/csv_decision/columns_spec.rb
|
245
250
|
- spec/csv_decision/constant_spec.rb
|
246
251
|
- spec/csv_decision/data_spec.rb
|