csv_decision 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|