csv_decision2 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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