csv_decision2 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +3 -0
  3. data/.coveralls.yml +2 -0
  4. data/.gitignore +14 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +30 -0
  7. data/.travis.yml +6 -0
  8. data/CHANGELOG.md +85 -0
  9. data/Dockerfile +6 -0
  10. data/Gemfile +7 -0
  11. data/LICENSE +21 -0
  12. data/README.md +356 -0
  13. data/benchmarks/rufus_decision.rb +158 -0
  14. data/csv_decision2.gemspec +38 -0
  15. data/doc/CSVDecision/CellValidationError.html +143 -0
  16. data/doc/CSVDecision/Columns/Default.html +589 -0
  17. data/doc/CSVDecision/Columns/Dictionary.html +801 -0
  18. data/doc/CSVDecision/Columns/Entry.html +508 -0
  19. data/doc/CSVDecision/Columns.html +1259 -0
  20. data/doc/CSVDecision/Constant.html +254 -0
  21. data/doc/CSVDecision/Data.html +479 -0
  22. data/doc/CSVDecision/Decide.html +302 -0
  23. data/doc/CSVDecision/Decision.html +1011 -0
  24. data/doc/CSVDecision/Defaults.html +291 -0
  25. data/doc/CSVDecision/Dictionary/Entry.html +1147 -0
  26. data/doc/CSVDecision/Dictionary.html +426 -0
  27. data/doc/CSVDecision/Error.html +139 -0
  28. data/doc/CSVDecision/FileError.html +143 -0
  29. data/doc/CSVDecision/Function.html +240 -0
  30. data/doc/CSVDecision/Guard.html +245 -0
  31. data/doc/CSVDecision/Header.html +647 -0
  32. data/doc/CSVDecision/Index.html +741 -0
  33. data/doc/CSVDecision/Input.html +404 -0
  34. data/doc/CSVDecision/Load.html +296 -0
  35. data/doc/CSVDecision/Matchers/Constant.html +484 -0
  36. data/doc/CSVDecision/Matchers/Function.html +511 -0
  37. data/doc/CSVDecision/Matchers/Guard.html +503 -0
  38. data/doc/CSVDecision/Matchers/Matcher.html +507 -0
  39. data/doc/CSVDecision/Matchers/Numeric.html +415 -0
  40. data/doc/CSVDecision/Matchers/Pattern.html +491 -0
  41. data/doc/CSVDecision/Matchers/Proc.html +704 -0
  42. data/doc/CSVDecision/Matchers/Range.html +379 -0
  43. data/doc/CSVDecision/Matchers/Symbol.html +426 -0
  44. data/doc/CSVDecision/Matchers.html +1567 -0
  45. data/doc/CSVDecision/Numeric.html +259 -0
  46. data/doc/CSVDecision/Options.html +443 -0
  47. data/doc/CSVDecision/Parse.html +282 -0
  48. data/doc/CSVDecision/Paths.html +742 -0
  49. data/doc/CSVDecision/Result.html +1200 -0
  50. data/doc/CSVDecision/Scan/InputHashes.html +369 -0
  51. data/doc/CSVDecision/Scan.html +313 -0
  52. data/doc/CSVDecision/ScanRow.html +866 -0
  53. data/doc/CSVDecision/Symbol.html +256 -0
  54. data/doc/CSVDecision/Table.html +1470 -0
  55. data/doc/CSVDecision/TableValidationError.html +143 -0
  56. data/doc/CSVDecision/Validate.html +422 -0
  57. data/doc/CSVDecision.html +621 -0
  58. data/doc/_index.html +471 -0
  59. data/doc/class_list.html +51 -0
  60. data/doc/css/common.css +1 -0
  61. data/doc/css/full_list.css +58 -0
  62. data/doc/css/style.css +499 -0
  63. data/doc/file.README.html +421 -0
  64. data/doc/file_list.html +56 -0
  65. data/doc/frames.html +17 -0
  66. data/doc/index.html +421 -0
  67. data/doc/js/app.js +248 -0
  68. data/doc/js/full_list.js +216 -0
  69. data/doc/js/jquery.js +4 -0
  70. data/doc/method_list.html +1163 -0
  71. data/doc/top-level-namespace.html +110 -0
  72. data/docker-compose.yml +13 -0
  73. data/lib/csv_decision/columns.rb +192 -0
  74. data/lib/csv_decision/data.rb +92 -0
  75. data/lib/csv_decision/decision.rb +196 -0
  76. data/lib/csv_decision/defaults.rb +47 -0
  77. data/lib/csv_decision/dictionary.rb +180 -0
  78. data/lib/csv_decision/header.rb +83 -0
  79. data/lib/csv_decision/index.rb +107 -0
  80. data/lib/csv_decision/input.rb +121 -0
  81. data/lib/csv_decision/load.rb +36 -0
  82. data/lib/csv_decision/matchers/constant.rb +74 -0
  83. data/lib/csv_decision/matchers/function.rb +56 -0
  84. data/lib/csv_decision/matchers/guard.rb +142 -0
  85. data/lib/csv_decision/matchers/numeric.rb +44 -0
  86. data/lib/csv_decision/matchers/pattern.rb +94 -0
  87. data/lib/csv_decision/matchers/range.rb +95 -0
  88. data/lib/csv_decision/matchers/symbol.rb +149 -0
  89. data/lib/csv_decision/matchers.rb +220 -0
  90. data/lib/csv_decision/options.rb +124 -0
  91. data/lib/csv_decision/parse.rb +165 -0
  92. data/lib/csv_decision/paths.rb +78 -0
  93. data/lib/csv_decision/result.rb +204 -0
  94. data/lib/csv_decision/scan.rb +117 -0
  95. data/lib/csv_decision/scan_row.rb +142 -0
  96. data/lib/csv_decision/table.rb +101 -0
  97. data/lib/csv_decision/validate.rb +85 -0
  98. data/lib/csv_decision.rb +45 -0
  99. data/spec/csv_decision/columns_spec.rb +251 -0
  100. data/spec/csv_decision/constant_spec.rb +36 -0
  101. data/spec/csv_decision/data_spec.rb +50 -0
  102. data/spec/csv_decision/decision_spec.rb +19 -0
  103. data/spec/csv_decision/examples_spec.rb +242 -0
  104. data/spec/csv_decision/index_spec.rb +58 -0
  105. data/spec/csv_decision/input_spec.rb +55 -0
  106. data/spec/csv_decision/load_spec.rb +28 -0
  107. data/spec/csv_decision/matchers/function_spec.rb +82 -0
  108. data/spec/csv_decision/matchers/guard_spec.rb +170 -0
  109. data/spec/csv_decision/matchers/numeric_spec.rb +47 -0
  110. data/spec/csv_decision/matchers/pattern_spec.rb +183 -0
  111. data/spec/csv_decision/matchers/range_spec.rb +70 -0
  112. data/spec/csv_decision/matchers/symbol_spec.rb +67 -0
  113. data/spec/csv_decision/options_spec.rb +94 -0
  114. data/spec/csv_decision/parse_spec.rb +44 -0
  115. data/spec/csv_decision/table_spec.rb +683 -0
  116. data/spec/csv_decision_spec.rb +7 -0
  117. data/spec/data/invalid/empty.csv +0 -0
  118. data/spec/data/invalid/invalid_header1.csv +4 -0
  119. data/spec/data/invalid/invalid_header2.csv +4 -0
  120. data/spec/data/invalid/invalid_header3.csv +4 -0
  121. data/spec/data/invalid/invalid_header4.csv +4 -0
  122. data/spec/data/valid/benchmark_regexp.csv +10 -0
  123. data/spec/data/valid/index_example.csv +13 -0
  124. data/spec/data/valid/multi_column_index.csv +10 -0
  125. data/spec/data/valid/multi_column_index2.csv +12 -0
  126. data/spec/data/valid/options_in_file1.csv +5 -0
  127. data/spec/data/valid/options_in_file2.csv +5 -0
  128. data/spec/data/valid/options_in_file3.csv +13 -0
  129. data/spec/data/valid/regular_expressions.csv +11 -0
  130. data/spec/data/valid/simple_constants.csv +5 -0
  131. data/spec/data/valid/simple_example.csv +10 -0
  132. data/spec/data/valid/valid.csv +4 -0
  133. data/spec/spec_helper.rb +106 -0
  134. metadata +352 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Input do
6
+ it 'rejects a non-hash or empty hash value' do
7
+ expect { CSVDecision::Input.parse(table: nil, input: [], symbolize_keys: true ) }
8
+ .to raise_error(ArgumentError, 'input must be a non-empty hash')
9
+ expect { CSVDecision::Input.parse(table: nil, input: {}, symbolize_keys: true ) }
10
+ .to raise_error(ArgumentError, 'input must be a non-empty hash')
11
+ end
12
+
13
+ it 'processes input hash with symbolize_keys: true' do
14
+ data = <<~DATA
15
+ IN :input, OUT :output, IN: input1
16
+ input0, output0, input1
17
+ input0, output1,
18
+ DATA
19
+
20
+ table = CSVDecision.parse(data)
21
+
22
+ input = { 'input' => 'input0', input1: 'input1' }
23
+ expected = {
24
+ hash: { input: 'input0', input1: 'input1' },
25
+ scan_cols: { 0 => 'input0', 2 => 'input1'},
26
+ key: 'input0'
27
+ }
28
+
29
+ result = CSVDecision::Input.parse(table: table, input: input, symbolize_keys: true)
30
+
31
+ expect(result).to eql expected
32
+ expect(result[:hash]).not_to equal expected[:hash]
33
+ end
34
+
35
+ it 'processes input hash with symbolize_keys: false' do
36
+ data = <<~DATA
37
+ IN :input, OUT :output, IN: input1
38
+ input0, output0, input1
39
+ input0, output1,
40
+ DATA
41
+
42
+ table = CSVDecision.parse(data)
43
+ input = { input: 'input0', input1: 'input1' }
44
+ expected = {
45
+ hash: input,
46
+ scan_cols: { 0 => 'input0', 2 => 'input1'},
47
+ key: 'input0'
48
+ }
49
+
50
+ result = CSVDecision::Input.parse(table: table, input: input, symbolize_keys: false)
51
+
52
+ expect(result).to eql expected
53
+ expect(result[:hash]).to equal expected[:hash]
54
+ end
55
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Load do
6
+ path = Pathname(File.join(CSVDecision.root, 'spec/data/valid'))
7
+
8
+ it "loads all valid CSV files in the directory" do
9
+ tables = CSVDecision.load(path, first_match: false, regexp_implicit: true)
10
+ expect(tables).to be_a Hash
11
+ expect(tables.count).to eq Dir[path.join('*.csv')].count
12
+
13
+ tables.each_pair do |name, table|
14
+ expect(name).to be_a(Symbol)
15
+ expect(table).to be_a(CSVDecision::Table)
16
+ end
17
+ end
18
+
19
+ it 'rejects an invalid path name' do
20
+ expect { CSVDecision.load('path') }
21
+ .to raise_error(ArgumentError, 'path argument must be a Pathname')
22
+ end
23
+
24
+ it 'rejects an invalid folder name' do
25
+ expect { CSVDecision.load(Pathname('path')) }
26
+ .to raise_error(ArgumentError, 'path argument not a valid folder')
27
+ end
28
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Matchers::Function do
6
+ subject { described_class.new }
7
+
8
+ describe '#new' do
9
+ it { is_expected.to be_a CSVDecision::Matchers::Function }
10
+ it { is_expected.to be_a CSVDecision::Matchers::Matcher }
11
+ it { is_expected.to respond_to(:matches?).with(1).argument }
12
+ end
13
+
14
+ context 'cell value recognition' do
15
+ cells = {
16
+ ':=function' => { operator: ':=', name: 'function' },
17
+ ':=function()' => { operator: ':=', name: 'function', args:'()' },
18
+ ':=function(arg: value)' => { operator: ':=', name: 'function', args:'(arg: value)' },
19
+ ':= !function(arg: value)' => { operator: ':=', negate: '!', name: 'function', args:'(arg: value)' },
20
+ '== ! func(arg: val)' => { operator: '==', negate: '!', name: 'func', args:'(arg: val)' },
21
+ }
22
+ cells.each_pair do |cell, expected|
23
+ it "recognises #{cell} as a function" do
24
+ match = CSVDecision::Matchers::Function::FUNCTION_RE.match(cell)
25
+ expect(match['operator']).to eq expected[:operator]
26
+ expect(match['name']).to eq expected[:name]
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#matches?' do
32
+ matcher = described_class.new
33
+
34
+ # context 'symbol expression matches value to hash data' do
35
+ # examples = [
36
+ # { cell: ':col', value: 0, hash: { col: 0 }, result: true },
37
+ # { cell: ':col', value: '0', hash: { col: '0' }, result: true },
38
+ # { cell: ':col', value: 0, hash: { col: '0' }, result: false },
39
+ # { cell: ':col', value: '0', hash: { col: 0 }, result: false },
40
+ # { cell: ':col', value: 1, hash: { col: 0 }, result: false },
41
+ # { cell: ':key', value: 0, hash: { col: 0 }, result: false },
42
+ # { cell: '!=:col', value: 0, hash: { col: 0 }, result: false },
43
+ # { cell: '!=:col', value: '0', hash: { col: '0' }, result: false },
44
+ # { cell: '!=:col', value: 0, hash: { col: '0' }, result: true },
45
+ # { cell: '!=:col', value: '0', hash: { col: 0 }, result: true },
46
+ # { cell: '!=:col', value: 1, hash: { col: 0 }, result: true },
47
+ # { cell: '!=:key', value: 0, hash: { col: 0 }, result: true },
48
+ # { cell: '>:col', value: 1, hash: { col: 0 }, result: true },
49
+ # { cell: '>:col', value: 0, hash: { col: 1 }, result: false },
50
+ # { cell: '<:col', value: 0, hash: { col: 1 }, result: true },
51
+ # { cell: '<:col', value: 1, hash: { col: 0 }, result: false },
52
+ # { cell: '= :col', value: 0, hash: { col: 0 }, result: true },
53
+ # { cell: '==:col', value: 0, hash: { col: 0 }, result: true },
54
+ # { cell: ':=:col', value: 0, hash: { col: 0 }, result: true },
55
+ # { cell: '= :col', value: '0', hash: { col: 0 }, result: false },
56
+ # { cell: '>=:col', value: 1, hash: { col: 0 }, result: true },
57
+ # { cell: '>=:col', value: 0, hash: { col: 1 }, result: false },
58
+ # { cell: '<=:col', value: 0, hash: { col: 1 }, result: true },
59
+ # { cell: '<=:col', value: 1, hash: { col: 0 }, result: false },
60
+ # { cell: '<=:col', value: '1', hash: { col: 1 }, result: false },
61
+ # ]
62
+ #
63
+ # examples.each do |ex|
64
+ # it "cell #{ex[:cell]} matches value: #{ex[:value]} to hash: #{ex[:hash]}" do
65
+ # proc = matcher.matches?(ex[:cell])
66
+ # expect(proc).to be_a(CSVDecision::Proc)
67
+ # expect(proc.function.call(ex[:value], ex[:hash])).to eq ex[:result]
68
+ # end
69
+ # end
70
+ # end
71
+
72
+ context 'does not match a non-function string' do
73
+ data = ['1', 'abc', 'abc.*def', '-1..1', '0...3']
74
+
75
+ data.each do |cell|
76
+ it "cell #{cell} is not a function" do
77
+ expect(matcher.matches?(cell)).to eq false
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Matchers::Guard do
6
+ subject { described_class.new }
7
+
8
+ describe '#new' do
9
+ it { is_expected.to be_a CSVDecision::Matchers::Guard }
10
+ it { is_expected.to be_a CSVDecision::Matchers::Matcher }
11
+ it { is_expected.to respond_to(:matches?).with(1).argument }
12
+ end
13
+
14
+ describe '#matches?' do
15
+ matcher = described_class.new
16
+
17
+ context 'symbol expression matches hash data' do
18
+ examples = [
19
+ # Integer equality
20
+ { cell: ':col == 0', hash: { col: 0 }, result: true },
21
+ { cell: ':col == 0', hash: { col: '0' }, result: true },
22
+ { cell: ':col == 0', hash: { col: 1 }, result: false },
23
+ { cell: ':col == 0', hash: { col: '1' }, result: false },
24
+ { cell: ':col == 0', hash: { col: nil }, result: false },
25
+ # Integer inequality
26
+ { cell: ':col != 0', hash: { col: 0 }, result: false },
27
+ { cell: ':col != 0', hash: { col: '0' }, result: false },
28
+ { cell: ':col != 0', hash: { col: 1 }, result: true },
29
+ { cell: ':col != 0', hash: { col: '1' }, result: true },
30
+ { cell: ':col != 0', hash: { col: nil }, result: true },
31
+ # Kind of silly, but valid
32
+ { cell: '!:col = 0', hash: { col: 0 }, result: false },
33
+ { cell: '!:col = 0', hash: { col: '0' }, result: false },
34
+ { cell: '!:col = 0', hash: { col: 1 }, result: true },
35
+ { cell: '!:col = 0', hash: { col: '1' }, result: true },
36
+ { cell: '!:col = 0', hash: { col: nil }, result: true },
37
+ # Integer compares
38
+ { cell: ':col > 0', hash: { col: 0 }, result: false },
39
+ { cell: ':col > 0', hash: { col: '0' }, result: false },
40
+ { cell: ':col > 0', hash: { col: 1 }, result: true },
41
+ { cell: ':col > 0', hash: { col: '1' }, result: true },
42
+ { cell: ':col > 0', hash: { col: nil }, result: nil },
43
+ { cell: ':col >=0', hash: { col: 0 }, result: true },
44
+ { cell: ':col >=0', hash: { col: '0' }, result: true },
45
+ { cell: ':col >=0', hash: { col: -1 }, result: false },
46
+ { cell: ':col >=0', hash: { col: '-1' }, result: false },
47
+ { cell: ':col >=0', hash: { col: nil }, result: nil },
48
+ { cell: ':col < 0', hash: { col: 0 }, result: false },
49
+ { cell: ':col < 0', hash: { col: '0' }, result: false },
50
+ { cell: ':col < 0', hash: { col: -1 }, result: true },
51
+ { cell: ':col < 0', hash: { col: '-1' }, result: true },
52
+ { cell: ':col < 0', hash: { col: nil }, result: nil },
53
+ { cell: ':col <=0', hash: { col: 0 }, result: true },
54
+ { cell: ':col <=0', hash: { col: '0' }, result: true },
55
+ { cell: ':col <=0', hash: { col: 1 }, result: false },
56
+ { cell: ':col <=0', hash: { col: '1' }, result: false },
57
+ { cell: ':col <=0', hash: { col: nil }, result: nil },
58
+ # BigDecimal equality
59
+ { cell: ':col == 0.0', hash: { col: BigDecimal('0.0') }, result: true },
60
+ { cell: ':col == 0.0', hash: { col: '0.0' }, result: true },
61
+ { cell: ':col == 0.0', hash: { col: 0 }, result: true },
62
+ { cell: ':col == 0.0', hash: { col: '0' }, result: true },
63
+ { cell: ':col == 0.0', hash: { col: '0.1' }, result: false },
64
+ { cell: ':col == 0.0', hash: { col: 0.0 }, result: false },
65
+ { cell: ':col == 0.0', hash: { col: nil }, result: false },
66
+ # BigDecimal inequality
67
+ { cell: ':col != 0.0', hash: { col: BigDecimal('0.0') }, result: false },
68
+ { cell: ':col != 0.0', hash: { col: '0.0' }, result: false },
69
+ { cell: ':col != 0.0', hash: { col: 0 }, result: false },
70
+ { cell: ':col != 0.0', hash: { col: '0' }, result: false },
71
+ { cell: ':col != 0.0', hash: { col: '0.1' }, result: true },
72
+ { cell: ':col != 0.0', hash: { col: 0.0 }, result: true },
73
+ { cell: ':col != 0.0', hash: { col: nil }, result: true },
74
+ # String compare
75
+ { cell: ':col > m', hash: { col: 0 }, result: nil },
76
+ { cell: ':col > m', hash: { col: 'a' }, result: false },
77
+ { cell: ':col > m', hash: { col: 'n' }, result: true },
78
+ { cell: ':col > m', hash: { col: nil }, result: nil },
79
+ { cell: ':col >=m', hash: { col: 0 }, result: nil },
80
+ { cell: ':col >=m', hash: { col: 'a' }, result: false },
81
+ { cell: ':col >=m', hash: { col: 'n' }, result: true },
82
+ { cell: ':col >=m', hash: { col: 'm' }, result: true },
83
+ { cell: ':col >=m', hash: { col: nil }, result: nil },
84
+ { cell: ':col < m', hash: { col: 0 }, result: nil },
85
+ { cell: ':col < m', hash: { col: 'a' }, result: true },
86
+ { cell: ':col < m', hash: { col: 'n' }, result: false },
87
+ { cell: ':col < m', hash: { col: nil }, result: nil },
88
+ { cell: ':col <=m', hash: { col: 0 }, result: nil },
89
+ { cell: ':col <=m', hash: { col: 'a' }, result: true },
90
+ { cell: ':col <=m', hash: { col: 'n' }, result: false },
91
+ { cell: ':col <=n', hash: { col: 'n' }, result: true },
92
+ { cell: ':col <=m', hash: { col: nil }, result: nil },
93
+ { cell: '!:col <=m', hash: { col: 0 }, result: nil },
94
+ { cell: '!:col <=m', hash: { col: 'a' }, result: false },
95
+ { cell: '!:col <=m', hash: { col: 'n' }, result: true },
96
+ { cell: '!:col <=n', hash: { col: 'n' }, result: false },
97
+ { cell: '!:col <=m', hash: { col: nil }, result: nil },
98
+ # Method calls
99
+ { cell: ':col.nil?', hash: { col: nil }, result: true },
100
+ { cell: ':col.nil?', hash: { col: 0 }, result: false },
101
+ { cell: '!:col.nil?', hash: { col: nil }, result: false },
102
+ { cell: '!:col.nil?', hash: { col: 0 }, result: true },
103
+ { cell: ':col.upcase', hash: { col: 'u' }, result: 'U' },
104
+ { cell: ':col.next', hash: { col: -1 }, result: 0 },
105
+ { cell: ':col.first', hash: { col: '98' }, result: '9' },
106
+ { cell: ':col.last', hash: { col: '98' }, result: '8' },
107
+ { cell: ':col.zero?', hash: { col: 0 }, result: true },
108
+ { cell: ':col.zero?', hash: { col: nil }, result: false },
109
+ # Symbol
110
+ { cell: ':col', hash: { col: true }, result: true },
111
+ { cell: ':col', hash: { col: nil }, result: nil },
112
+ { cell: '!:col', hash: { col: nil }, result: true },
113
+ { cell: '!:col', hash: { col: true }, result: false },
114
+ ]
115
+
116
+ examples.each do |ex|
117
+ it "cell #{ex[:cell]} matches to hash: #{ex[:hash]}" do
118
+ proc = matcher.matches?(ex[:cell])
119
+ expect(proc).to be_a(CSVDecision::Matchers::Proc)
120
+ expect(proc.type).to eq :guard
121
+ expect(proc.function.call(ex[:hash])).to eq ex[:result]
122
+ end
123
+ end
124
+ end
125
+
126
+ context 'does not match a symbol guard condition' do
127
+ data = ['1', 'abc', 'abc.*def', '-1..1', '0...3', ':= true', ':= lookup(:table)', '>= :col']
128
+
129
+ data.each do |cell|
130
+ it "cell #{cell} is not a function" do
131
+ expect(matcher.matches?(cell)).to eq false
132
+ end
133
+ end
134
+ end
135
+
136
+ context 'raises an error for a constant in a guard column' do
137
+ data = <<~DATA
138
+ IN :country, guard : country, out :PAID, out :PAID_type, out :len
139
+ US, :CUSIP.present?, :CUSIP, CUSIP, :PAID.length
140
+ GB, :SEDOL.present?, :SEDOL, SEDOL, :PAID.length
141
+ , :ISIN.present?, :ISIN, ISIN, :PAID.length
142
+ , :SEDOL.present?, :SEDOL, SEDOL, :PAID.length
143
+ , :CUSIP.present?, :CUSIP, CUSIP, :PAID.length
144
+ , := nil, := nil, MISSING, := nil
145
+ DATA
146
+
147
+ specify do
148
+ expect { CSVDecision.parse(data) }
149
+ .to raise_error(CSVDecision::CellValidationError, 'guard: column cannot contain constants')
150
+ end
151
+ end
152
+
153
+ context 'raises an error for a constant in an if column' do
154
+ data = <<~DATA
155
+ IN :country, guard : country, out :PAID, out :PAID_type, if:
156
+ US, :CUSIP.present?, :CUSIP, CUSIP, TRUE
157
+ GB, :SEDOL.present?, :SEDOL, SEDOL,
158
+ , :ISIN.present?, :ISIN, ISIN,
159
+ , :SEDOL.present?, :SEDOL, SEDOL,
160
+ , :CUSIP.present?, :CUSIP, CUSIP,
161
+ , := nil, := nil, MISSING, := nil
162
+ DATA
163
+
164
+ specify do
165
+ expect { CSVDecision.parse(data) }
166
+ .to raise_error(CSVDecision::CellValidationError, 'if: column cannot contain constants')
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Matchers::Numeric do
6
+ subject { described_class.new }
7
+
8
+ describe '#new' do
9
+ it { is_expected.to be_a CSVDecision::Matchers::Numeric }
10
+ it { is_expected.to respond_to(:matches?).with(1).argument }
11
+ end
12
+
13
+ describe '#matches?' do
14
+ matcher = described_class.new
15
+
16
+ context 'comparison matches value' do
17
+ data = [
18
+ ['< 1', 0],
19
+ ['< 1', '0'],
20
+ ['> 1', 5],
21
+ ['!= 1', 0],
22
+ ['> 1', '5'],
23
+ ['>= 1.1', BigDecimal('1.1')],
24
+ ['<=-1.1', BigDecimal('-12')]
25
+ ]
26
+
27
+ data.each do |cell, value|
28
+ it "comparision #{cell} matches #{value}" do
29
+ proc = matcher.matches?(cell)
30
+ expect(proc).to be_a(CSVDecision::Matchers::Proc)
31
+ expect(proc.type).to eq :proc
32
+ expect(proc.function[value]).to eq true
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'does not match non-numeric comparision' do
38
+ data = ['1', ':column', ':= nil', ':= true', ':= 0', 'abc', 'abc.*def', '-1..1', '0...3']
39
+
40
+ data.each do |cell|
41
+ it "cell #{cell} is not a comparision}" do
42
+ expect(matcher.matches?(cell)).to eq false
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Matchers::Pattern do
6
+ subject { described_class.new }
7
+
8
+ describe '#new' do
9
+ it { is_expected.to be_a CSVDecision::Matchers::Pattern }
10
+ it { is_expected.to respond_to(:matches?).with(1).argument }
11
+ end
12
+
13
+ describe '#matches?' do
14
+ context 'recognises regular expressions with implicit option' do
15
+ matcher = described_class.new(regexp_implicit: true)
16
+
17
+ expressions = [
18
+ '!~Jerk',
19
+ '!=Jerk',
20
+ '=~ Jerk.+',
21
+ 'a.+c',
22
+ '=~AB|BC|CD',
23
+ 'TRP\.CRD',
24
+ '=~'
25
+ ]
26
+
27
+ expressions.each do |cell|
28
+ it "recognises regexp #{cell}" do
29
+ proc = matcher.matches?(cell)
30
+ expect(proc).to be_a CSVDecision::Matchers::Proc
31
+ expect(proc.type).to eq :proc
32
+ expect(proc.function).to be_a ::Proc
33
+ expect(proc.function.arity).to eq -1
34
+ end
35
+ end
36
+ end
37
+
38
+ context 'recognises regular expressions with explicit option' do
39
+ matcher = described_class.new(regexp_implicit: true)
40
+
41
+ expressions = [
42
+ '!~Jerk',
43
+ '!=Jerk',
44
+ '=~ Jerk.+',
45
+ '=~a.+c',
46
+ '=~AB|BC|CD',
47
+ '=~ TRP\.CRD'
48
+ ]
49
+
50
+ expressions.each do |cell|
51
+ it "recognises regexp #{cell}" do
52
+ proc = matcher.matches?(cell)
53
+ expect(proc).to be_a CSVDecision::Matchers::Proc
54
+ expect(proc.type).to eq :proc
55
+ expect(proc.function).to be_a ::Proc
56
+ expect(proc.function.arity).to eq -1
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'matches regular expressions with implicit option' do
62
+ matcher = described_class.new(regexp_implicit: true)
63
+
64
+ expressions = [
65
+ %w[!~Jerk Jerks],
66
+ %w[!=Jerk Jerks],
67
+ ['=~ Jerk.+', 'Jerks'],
68
+ %w[a.+c abc],
69
+ %w[=~AB|BC|CD CD],
70
+ %w[TRP\.CRD TRP.CRD],
71
+ %w[=~ =~]
72
+ ]
73
+
74
+ expressions.each do |cell, value|
75
+ it "matches regexp #{cell} to value #{value}" do
76
+ proc = matcher.matches?(cell)
77
+ expect(proc.function[value]).to be_truthy
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'does not match regular expressions with implicit option' do
83
+ matcher = described_class.new(regexp_implicit: true)
84
+
85
+ expressions = [
86
+ %w[!~Jerk Jerk],
87
+ %w[!=Jerk Jerk],
88
+ ['=~ Jerk.+', 'Jerk'],
89
+ %w[a.+c abcd],
90
+ %w[=~AB|BC|CD C],
91
+ %w[TRP\.CRD TRPxCRD]
92
+ ]
93
+
94
+ expressions.each do |cell, value|
95
+ it "matches regexp #{cell} to value #{value}" do
96
+ proc = matcher.matches?(cell)
97
+ expect(proc.function[value]).to be_falsey
98
+ end
99
+ end
100
+ end
101
+
102
+ context 'matches regular expressions with explicit option' do
103
+ matcher = described_class.new(regexp_implicit: false)
104
+
105
+ expressions = [
106
+ %w[!~Jerk Jerks],
107
+ %w[!=Jerk Jerks],
108
+ ['=~ Jerk.+', 'Jerks'],
109
+ %w[=~a.+c abc],
110
+ %w[=~AB|BC|CD CD],
111
+ %w[=~TRP\.CRD TRP.CRD]
112
+ ]
113
+
114
+ expressions.each do |cell, value|
115
+ it "matches regexp #{cell} to value #{value}" do
116
+ proc = matcher.matches?(cell)
117
+ expect(proc.function[value]).to be_truthy
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'does not match regular expressions with explicit option' do
123
+ matcher = described_class.new(regexp_implicit: false)
124
+
125
+ expressions = [
126
+ %w[!~Jerk Jerk],
127
+ %w[!=Jerk Jerk],
128
+ ['=~ Jerk.+', 'Jerk'],
129
+ %w[=~a.+c abcd],
130
+ %w[=~AB|BC|CD C],
131
+ %w[=~TRP\.CRD TRPxCRD]
132
+ ]
133
+
134
+ expressions.each do |cell, value|
135
+ it "matches regexp #{cell} to value #{value}" do
136
+ proc = matcher.matches?(cell)
137
+ expect(proc.function[value]).to be_falsey
138
+ end
139
+ end
140
+ end
141
+
142
+ context 'does not recognise non-regular expressions with implicit option' do
143
+ matcher = described_class.new(regexp_implicit: true)
144
+
145
+ expressions = [
146
+ 'Jerk',
147
+ ':Jerk',
148
+ '=~:Jerk',
149
+ ':= nil',
150
+ ':= 100.0'
151
+ ]
152
+
153
+ expressions.each do |cell|
154
+ it "does not match string #{cell}" do
155
+ proc = matcher.matches?(cell)
156
+ expect(proc).to be_falsey
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'does not recognise non-regular expressions with explicit option' do
162
+ matcher = described_class.new(regexp_implicit: false)
163
+
164
+ expressions = [
165
+ 'Jerk',
166
+ ':Jerk',
167
+ '=~:Jerk',
168
+ 'a.+c',
169
+ '*.OPT.*',
170
+ ':= nil',
171
+ ':= 100.0',
172
+ '=~'
173
+ ]
174
+
175
+ expressions.each do |cell|
176
+ it "does not match string #{cell}" do
177
+ proc = matcher.matches?(cell)
178
+ expect(proc).to be_falsey
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../lib/csv_decision'
4
+
5
+ describe CSVDecision::Matchers::Range do
6
+ subject { described_class.new }
7
+
8
+ describe '#new' do
9
+ it { is_expected.to be_a CSVDecision::Matchers::Range }
10
+ it { is_expected.to respond_to(:matches?).with(1).argument }
11
+ end
12
+
13
+ describe '#matches?' do
14
+ matcher = described_class.new
15
+
16
+ context 'range matches value' do
17
+ data = [
18
+ [ '-1..+4', 0],
19
+ ['!-1..+4', 5],
20
+ ['1.1...4', 3],
21
+ %w[a..z a],
22
+ %w[a..z z],
23
+ %w[a..z m],
24
+ %w[!-1..1 1.1],
25
+ ['! -1..1', BigDecimal('1.1')],
26
+ [ '-1..1', BigDecimal('1')]
27
+ ]
28
+
29
+ data.each do |cell, value|
30
+ it "range #{cell} matches #{value}" do
31
+ proc = matcher.matches?(cell)
32
+ expect(proc).to be_a(CSVDecision::Matchers::Proc)
33
+ expect(proc.type).to eq :proc
34
+ expect(proc.function[value]).to eq true
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'range does not match value' do
40
+ data = [
41
+ [ '-1..+4', 5],
42
+ ['!-1..+4', 2],
43
+ %w[a...z z],
44
+ %w[!a..z m],
45
+ %w[-1..1 1.1],
46
+ ['-1..1', BigDecimal('1.1')],
47
+ ['-1..1', BigDecimal('1.1')]
48
+ ]
49
+
50
+ data.each do |cell, value|
51
+ it "range #{cell} does not match #{value}" do
52
+ proc = matcher.matches?(cell)
53
+ expect(proc).to be_a(CSVDecision::Matchers::Proc)
54
+ expect(proc.type).to eq :proc
55
+ expect(proc.function[value]).to eq false
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'does not match a range' do
61
+ data = ['1', ':column', ':= nil', ':= true', 'abc', 'abc.*def']
62
+
63
+ data.each do |cell|
64
+ it "cell #{cell} is not a range}" do
65
+ expect(matcher.matches?(cell)).to eq false
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end