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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0a1c5b8a4584339463eb67ce93820087378a62c7edc0b63b61c199b008739604
4
+ data.tar.gz: e091e00ea144d06fefc692e23639e282f64150305ee5864354d53b91928d9f0d
5
+ SHA512:
6
+ metadata.gz: 744dd6194daf1902d0eab2e302b82cab87c15c3fe9e0413b7f32b68138ccb3267f6601110fec732a8738596b403bb55a7b9b855e20adbaa0eb3f9dc982b53d5b
7
+ data.tar.gz: d977d550fab93f91ecae7b6ba668383c6bbc14538e7dd798d632cc2e986bc4a9eb89ff141db5d25d89efd0c63391e7858a4f761745a4294dfaa70eb131384ee4
data/.codeclimate.yml ADDED
@@ -0,0 +1,3 @@
1
+ exclude_patterns:
2
+ - "doc/"
3
+ - "spec/"
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: Rr2wjGPimrqmxVhIHHN29kGxcaQnSh4Xs
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ *~
2
+ #*#
3
+ *old
4
+ *.bak
5
+ *.gem
6
+ .bundle
7
+ Gemfile.lock
8
+ pkg/*
9
+
10
+ # IDE files
11
+ .idea
12
+ .DS_Store
13
+ coverage/
14
+ .yardoc/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require 'spec_helper'
data/.rubocop.yml ADDED
@@ -0,0 +1,30 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3.0
3
+
4
+ Metrics/MethodLength:
5
+ CountComments: false
6
+ Max: 15
7
+
8
+ Metrics/LineLength:
9
+ Max: 100
10
+
11
+ Layout/TrailingWhitespace:
12
+ Enabled: false
13
+
14
+ Layout/SpaceInsideArrayPercentLiteral:
15
+ Enabled: false
16
+
17
+ Layout/AlignArray:
18
+ Enabled: false
19
+
20
+ Layout/IdentArray:
21
+ Enabled: false
22
+
23
+ Layout/ExtraSpacing:
24
+ Enabled: false
25
+
26
+ Style/StringLiterals:
27
+ Enabled: false
28
+
29
+ Style/WordArray:
30
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 2.7.0
5
+ script:
6
+ - bundle exec rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,85 @@
1
+ ## v0.5.1, 12 February 2018.
2
+ *Bug Fix*
3
+ - Fix bug when using a path to scan for a first match.
4
+
5
+ ## v0.5.0, 11 February 2018.
6
+ *Additions*
7
+ - Add the ability to specify an input hash path using path: column types.
8
+
9
+ ## v0.4.1, 10 February 2018.
10
+ *Additions*
11
+ - For consistency, input columns may now use the form `!nil?` to negate 0-arity Ruby methods.
12
+ (Typical SME users can be somewhat lax with syntax.)
13
+
14
+ ## v0.4.0, 28 January 2018.
15
+ *Additions*
16
+ - Input columns may now use 0-arity Ruby methods to implement conditional logic.
17
+ For example `.present?` or `nil?`. Negation is also supported - e.g., `!.nil?`.
18
+
19
+ ## v0.3.2, 28 January 2018.
20
+ *Changes*
21
+ - Refactor code and update documentation.
22
+
23
+ ## v0.3.1, 21 January 2018.
24
+ *Changes*
25
+ - Optimize code.
26
+
27
+ ## v0.3.0, 20 January 2018.
28
+ *Additions*
29
+ - Index one or more text-only input columns for faster lookup performance.
30
+
31
+ ## v0.2.0, 13 January 2018.
32
+ *Additions*
33
+ - Set values in the input hash either as a default or unconditionally.
34
+
35
+ ## v0.1.0, 5 January 2018.
36
+ *Additions*
37
+ - Implement more checks on output columns that are duplicated or
38
+ reference columns that appear after them.
39
+
40
+ ## v0.0.9, 5 January 2018.
41
+ *Additions*
42
+ - Output column if: filter conditions.
43
+
44
+ ## v0.0.8, 31 December 2017.
45
+ *Additions*
46
+ - Guard conditions can use `=~` and `!~` for regular expressions.
47
+
48
+ *Fixes*
49
+ - Bug with column symbol expression not recognising >= and <=.
50
+
51
+ ## v0.0.7, 30 December 2017.
52
+ *Additions*
53
+ - Guard conditions using column symbols and expressions.
54
+ - Guard columns.
55
+ - Symbol functions (0-arity) in output columns.
56
+ - Update YARD documentation.
57
+
58
+ ## v0.0.6, 26 December 2017.
59
+ *Additions*
60
+ - Update YARD documentation.
61
+
62
+ ## v0.0.5, 26 December 2017.
63
+ *Additions*
64
+ - Update YARD documentation.
65
+
66
+ ## v0.0.4, 26 December 2017.
67
+ *Additions*
68
+ - Adds symbol expressions for input columns.
69
+ - Adds non-string constants for output columns.
70
+ - Support Ruby 2.5.0
71
+ - Include YARD documentation.
72
+
73
+ *Changes*
74
+ - Move `benchmark.rb` to `benchmarks` folder and rename to `rufus_decision.rb`
75
+
76
+ ## v0.0.3, 18 December 2017.
77
+ *Additions*
78
+ - Add non-string constants for input columns.
79
+
80
+ ## v0.0.2, 18 December 2017.
81
+ *Additions*
82
+ - Adds accumulate option.
83
+
84
+ ## v0.0.1, 18 December 2017.
85
+ - Initial release
data/Dockerfile ADDED
@@ -0,0 +1,6 @@
1
+ FROM ruby:3.3.0
2
+ RUN mkdir -p /app
3
+ WORKDIR /app
4
+ COPY Gemfile csv_decision.gemspec ./
5
+ RUN gem update bundler
6
+ # RUN bundle install
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'coveralls', require: false
4
+
5
+ gemspec
6
+
7
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Brett Vickers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # CSV Decision
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/csv_decision.svg)](https://badge.fury.io/rb/csv_decision)
4
+ [![Build Status](https://travis-ci.org/bpvickers/csv_decision.svg?branch=master)](https://travis-ci.org/bpvickers/csv_decision)
5
+ [![Coverage Status](https://coveralls.io/repos/github/bpvickers/csv_decision/badge.svg?branch=master)](https://coveralls.io/github/bpvickers/csv_decision?branch=master)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/466a6c52e8f6a3840967/maintainability)](https://codeclimate.com/github/bpvickers/csv_decision/maintainability)
7
+ [![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license)
8
+
9
+ ### INFORMATION
10
+
11
+ This repo is created while waiting the owner to support latest version
12
+
13
+ ### CSV based Ruby decision tables
14
+
15
+ `csv_decision2` is a RubyGem for CSV based
16
+ [decision tables](https://en.wikipedia.org/wiki/Decision_table).
17
+ It accepts decision tables implemented as a
18
+ [CSV file](https://en.wikipedia.org/wiki/Comma-separated_values),
19
+ which can then be used to execute complex conditional logic against an input hash,
20
+ producing a decision as an output hash.
21
+
22
+ ### Why use `csv_decision2`?
23
+
24
+ Typical "business logic" is notoriously illogical - full of corner cases and one-off
25
+ exceptions.
26
+ A decision table can express data-based decisions in a way that comes naturally
27
+ to subject matter experts, who typically use spreadsheet models.
28
+ Business logic can be encapsulated in a table, avoiding the need for tortuous conditional
29
+ expressions.
30
+
31
+ This gem and the examples below take inspiration from
32
+ [rufus/decision](https://github.com/jmettraux/rufus-decision).
33
+ (That gem is no longer maintained and CSV Decision has better
34
+ decision-time performance, at the expense of slower table parse times and more memory --
35
+ see `benchmarks/rufus_decision.rb`.)
36
+
37
+ ### Installation
38
+
39
+ To get started, just add `csv_decision2` to your `Gemfile`, and then run `bundle`:
40
+
41
+ ```ruby
42
+ gem 'csv_decision2'
43
+ ```
44
+
45
+ or simply
46
+
47
+ ```bash
48
+ gem install csv_decision2
49
+ ```
50
+
51
+ ### Simple example
52
+
53
+ This table considers two input conditions: `topic` and `region`, labeled `in:`.
54
+ Certain combinations yield an output value for `team_member`, labeled `out:`.
55
+
56
+ ```
57
+ in:topic | in:region | out:team_member
58
+ ---------+------------+----------------
59
+ sports | Europe | Alice
60
+ sports | | Bob
61
+ finance | America | Charlie
62
+ finance | Europe | Donald
63
+ finance | | Ernest
64
+ politics | Asia | Fujio
65
+ politics | America | Gilbert
66
+ politics | | Henry
67
+ | | Zach
68
+ ```
69
+
70
+ When the topic is `finance` and the region is `Europe` the team member `Donald`
71
+ is selected. This is a "first match" decision table in that as soon as a match is made
72
+ execution stops and a single output row (hash) is returned.
73
+
74
+ The ordering of rows matters. `Ernest`, who is in charge of `finance` for the rest of
75
+ the world, except for `America` and `Europe`, _must_ come after his colleagues
76
+ `Charlie` and `Donald`. `Zach` has been placed last, catching all the input combos
77
+ not matching any other row.
78
+
79
+ Here's the example as code:
80
+
81
+ ```ruby
82
+ # Valid CSV string
83
+ data = <<~DATA
84
+ in :topic, in :region, out :team_member
85
+ sports, Europe, Alice
86
+ sports, , Bob
87
+ finance, America, Charlie
88
+ finance, Europe, Donald
89
+ finance, , Ernest
90
+ politics, Asia, Fujio
91
+ politics, America, Gilbert
92
+ politics, , Henry
93
+ , , Zach
94
+ DATA
95
+
96
+ table = CSVDecision.parse(data)
97
+
98
+ table.decide(topic: 'finance', region: 'Europe') #=> { team_member: 'Donald' }
99
+ table.decide(topic: 'sports', region: nil) #=> { team_member: 'Bob' }
100
+ table.decide(topic: 'culture', region: 'America') #=> { team_member: 'Zach' }
101
+ ```
102
+
103
+ An empty `in:` cell means "matches any value".
104
+
105
+ Note that all column header names are symbolized, so it's actually more accurate to write
106
+ `in :topic`; however spaces before and after the `:` do not matter.
107
+
108
+ If you have cloned this gem's git repo, then the example can also be run by loading
109
+ the table from a CSV file:
110
+
111
+ ```ruby
112
+ table = CSVDecision.parse(Pathname('spec/data/valid/simple_example.csv'))
113
+ ```
114
+
115
+ We can also load this same table using the option: `first_match: false`, which means that
116
+ _all_ matching rows will be accumulated into an array of hashes.
117
+
118
+ ```ruby
119
+ table = CSVDecision.parse(data, first_match: false)
120
+ table.decide(topic: 'finance', region: 'Europe') #=> { team_member: %w[Donald Ernest Zach] }
121
+ ```
122
+
123
+ For more examples see `spec/csv_decision/table_spec.rb`.
124
+ Complete documentation of all table parameters is in the code - see
125
+ `lib/csv_decision/parse.rb` and `lib/csv_decision/table.rb`.
126
+
127
+ ### CSV Decision features
128
+
129
+ - Either returns the first matching row as a hash (default), or accumulates all matches
130
+ as an array of hashes (i.e., `parse` option `first_match: false` or CSV file option
131
+ `accumulate`).
132
+ - Fast decision-time performance (see `benchmarks` folder). Automatically indexes all
133
+ constants-only columns that do not contain any empty strings.
134
+ - In addition to strings, can match basic Ruby constants (e.g., `=nil`),
135
+ regular expressions (e.g., `=~ on|off`), comparisons (e.g., `> 100.0` ) and
136
+ Ruby-style ranges (e.g., `1..10`)
137
+ - Can compare an input column versus another input hash key - e.g., `> :column`.
138
+ - Any cell starting with `#` is treated as a comment, and comments may appear anywhere in
139
+ the table.
140
+ - Column symbol expressions or Ruby methods (0-arity) may be used in input columns for
141
+ matching - e.g., `:column.zero?` or `:column == 0`.
142
+ - May also use Ruby methods in output columns - e.g., `:column.length`.
143
+ - Accepts data as a file, CSV string or an array of arrays.
144
+
145
+ #### Constants other than strings
146
+
147
+ Although `csv_decision` is string oriented, it does recognise other types of constant
148
+ present in the input hash. Specifically, the following classes are recognized:
149
+ `Integer`, `BigDecimal`, `NilClass`, `TrueClass` and `FalseClass`.
150
+
151
+ This is accomplished by prefixing the value with one of the operators `=`, `==` or `:=`.
152
+ (The syntax is intentionally lax.)
153
+
154
+ For example:
155
+
156
+ ```ruby
157
+ data = <<~DATA
158
+ in :constant, out :value
159
+ :=nil, :=nil
160
+ ==false, ==false
161
+ =true, =true
162
+ = 0, = 0
163
+ :=100.0, :=100.0
164
+ DATA
165
+
166
+ table = CSVDecision.parse(data)
167
+ table.decide(constant: nil) # returns value: nil
168
+ table.decide(constant: 0) # returns value: 0
169
+ table.decide(constant: BigDecimal('100.0')) # returns value: BigDecimal('100.0')
170
+ ```
171
+
172
+ #### Column header symbols
173
+
174
+ All input and output column names are symbolized, and those symbols may be used to form
175
+ simple expressions that refer to values in the input hash.
176
+
177
+ For example:
178
+
179
+ ```ruby
180
+ data = <<~DATA
181
+ in :node, in :parent, out :top?
182
+ , == :node, yes
183
+ , , no
184
+ DATA
185
+
186
+ table = CSVDecision.parse(data)
187
+ table.decide(node: 0, parent: 0) # returns top?: 'yes'
188
+ table.decide(node: 1, parent: 0) # returns top?: 'no'
189
+ ```
190
+
191
+ Note that there is no need to include an input column for `:node` in the decision
192
+ table - it just needs to be present in the input hash. The expression, `== :node` should be
193
+ read as `:parent == :node`. It can also be shortened to just `:node`, so the above decision
194
+ table may be simplified to:
195
+
196
+ ```ruby
197
+ data = <<~DATA
198
+ in :parent, out :top?
199
+ :node, yes
200
+ , no
201
+ DATA
202
+ ```
203
+
204
+ These comparison operators are also supported: `!=`, `>`, `>=`, `<`, `<=`.
205
+ In addition, you can also apply a Ruby 0-arity method - e.g., `.present?` or `.nil?`. Negation is
206
+ also supported - e.g., `!.nil?`. Note that `.nil?` can also be written as `:= nil?`, and `!.nil?`
207
+ as `:= !nil?`, depending on preference.
208
+
209
+ For more simple examples see `spec/csv_decision/examples_spec.rb`.
210
+
211
+ #### Input `guard` conditions
212
+
213
+ Sometimes it's more convenient to write guard expressions in a single column specialized for that purpose.
214
+ For example:
215
+
216
+ ```ruby
217
+ data = <<~DATA
218
+ in :country, guard:, out :ID, out :ID_type, out :len
219
+ US, :CUSIP.present?, :CUSIP, CUSIP, :ID.length
220
+ GB, :SEDOL.present?, :SEDOL, SEDOL, :ID.length
221
+ , :ISIN.present?, :ISIN, ISIN, :ID.length
222
+ , :SEDOL.present?, :SEDOL, SEDOL, :ID.length
223
+ , :CUSIP.present?, :CUSIP, CUSIP, :ID.length
224
+ , , := nil, := nil, := nil
225
+ DATA
226
+
227
+ table = CSVDecision.parse(data)
228
+ table.decide(country: 'US', CUSIP: '123456789') #=> { ID: '123456789', ID_type: 'CUSIP', len: 9 }
229
+ table.decide(country: 'EU', CUSIP: '123456789', ISIN:'123456789012')
230
+ #=> { ID: '123456789012', ID_type: 'ISIN', len: 12 }
231
+ ```
232
+
233
+ Input `guard:` columns may be anonymous, and must contain non-constant expressions. In addition to
234
+ 0-arity Ruby methods, the following comparison operators are allowed: `==`, `!=`,
235
+ `>`, `>=`, `<` and `<=`. Also, regular expressions are supported - i.e., `=~` and `!~`.
236
+
237
+ #### Output `if` conditions
238
+
239
+ In some situations it is useful to apply filter conditions _after_ all the output
240
+ columns have been derived. For example:
241
+
242
+ ```ruby
243
+ data = <<~DATA
244
+ in :country, guard:, out :ID, out :ID_type, out :len, if:
245
+ US, :CUSIP.present?, :CUSIP, CUSIP8, :ID.length, :len == 8
246
+ US, :CUSIP.present?, :CUSIP, CUSIP9, :ID.length, :len == 9
247
+ US, :CUSIP.present?, :CUSIP, DUMMY, :ID.length,
248
+ , :ISIN.present?, :ISIN, ISIN, :ID.length, :len == 12
249
+ , :ISIN.present?, :ISIN, DUMMY, :ID.length,
250
+ , :CUSIP.present?, :CUSIP, DUMMY, :ID.length,
251
+ DATA
252
+
253
+ table = CSVDecision.parse(data)
254
+ table.decide(country: 'US', CUSIP: '123456789') #=> {ID: '123456789', ID_type: 'CUSIP9', len: 9}
255
+ table.decide(CUSIP: '12345678', ISIN:'1234567890') #=> {ID: '1234567890', ID_type: 'DUMMY', len: 10}
256
+ ```
257
+
258
+ Output `if:` columns may be anonymous, and must contain non-constant expressions. In addition to
259
+ 0-arity Ruby methods, the following comparison operators are allowed: `==`, `!=`,
260
+ `>`, `>=`, `<` and `<=`. Also, regular expressions are supported - i.e., `=~` and `!~`.
261
+
262
+ #### Input `set` columns
263
+
264
+ If you wish to set default values in the input hash, you can use a `set` column rather
265
+ than an `in` column. The data row beneath the header is used to specify the default expression.
266
+ There are three variations: `set` (unconditional default) `set/nil?`(set if `nil?` true)
267
+ and `set/blank?` (set if `blank?` true).
268
+ Note that the `decide!` method will mutate the input hash.
269
+
270
+ ```ruby
271
+ data = <<~DATA
272
+ set/nil? :country, guard:, set: class, out :PAID, out: len, if:
273
+ US, , :class.upcase,
274
+ US, :CUSIP.present?, != PRIVATE, :CUSIP, :PAID.length, :len == 9
275
+ !=US, :ISIN.present?, != PRIVATE, :ISIN, :PAID.length, :len == 12
276
+ US, :CUSIP.present?, PRIVATE, :CUSIP, :PAID.length,
277
+ !=US, :ISIN.present?, PRIVATE, :ISIN, :PAID.length,
278
+ DATA
279
+
280
+ table = CSVDecision.parse(data)
281
+ table.decide(CUSIP: '1234567890', class: 'Private') #=> {PAID: '1234567890', len: 10}
282
+ table.decide(ISIN: '123456789012', country: 'GB', class: 'private') #=> {PAID: '123456789012', len: 12}
283
+
284
+ ```
285
+
286
+ #### Input `path` columns
287
+
288
+ For hashes that contain sub-hashes, it's possible to specify a path for the purposes
289
+ of matching. (Arrays are currently not supported.)
290
+
291
+ ```ruby
292
+ data = <<~DATA
293
+ path:, path:, out :value
294
+ header, , :source_name
295
+ header, metrics, :service_name
296
+ payload, , :amount
297
+ payload, ref_data, :account_id
298
+ DATA
299
+ table = CSVDecision.parse(data, first_match: false)
300
+
301
+ input = {
302
+ header: {
303
+ id: 1, type_cd: 'BUY', source_name: 'Client', client_name: 'AAPL',
304
+ metrics: { service_name: 'Trading', receive_time: '12:00' }
305
+ },
306
+ payload: {
307
+ tran_id: 9, amount: '100.00',
308
+ ref_data: { account_id: '5010', type_id: 'BUYL' }
309
+ }
310
+ }
311
+
312
+ table.decide(input) #=> { value: %w[Client Trading 100.00 5010] }
313
+ ```
314
+
315
+ ### Testing
316
+
317
+ `csv_decision` includes thorough [RSpec](http://rspec.info) tests:
318
+
319
+ ```bash
320
+ # Execute within a clone of the csv_decision Git repository:
321
+ bundle install
322
+ rspec
323
+ ```
324
+
325
+ ### Planned features
326
+
327
+ `csv_decision` is still a work in progress, and will be enhanced to support
328
+ the following features:
329
+
330
+ - Supply a pre-defined library of functions that may be called within input columns to
331
+ implement custom matching logic, or from the output columns to formulate the final
332
+ decision.
333
+ - Built-in lookup functions evaluate other decision tables to implement guard conditions,
334
+ or supply output values.
335
+ - Available functions may be extended with a user-supplied library of Ruby methods
336
+ for custom logic.
337
+ - Output columns may construct interpolated strings containing references to column
338
+ symbols.
339
+
340
+ ### Reasons for the limitations of column expressions
341
+
342
+ The simple column expressions allowed by `csv_decision` are purposely limited for reasons of
343
+ understandability and maintainability. The whole point of this gem is to make decision rules
344
+ easier to express and comprehend as declarative, tabular logic.
345
+ While Ruby makes it easy to execute arbitrary code embedded within a CSV file,
346
+ this could easily result in hard to debug logic that also poses safety risks.
347
+
348
+ ## Changelog
349
+
350
+ See [CHANGELOG.md](./CHANGELOG.md) for a list of changes.
351
+
352
+ ## License
353
+
354
+ CSV Decision &copy; 2017-2018 by [Brett Vickers](mailto:brett@phillips-vickers.com).
355
+ CSV Decision is licensed under the MIT license. Please see the [LICENSE](./LICENSE)
356
+ document for more information.