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
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.