patrun 0.1.0 → 0.1.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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +22 -0
  3. data/README.md +317 -0
  4. metadata +13 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 26be7b15694fcdb3af30b2cf505e1f5446e75094
4
- data.tar.gz: 8d6c257f618b6c0acdbfa7ff47a2cb02c0225014
3
+ metadata.gz: 0a552276944c28770e97035adabe76cb38332489
4
+ data.tar.gz: 651c5a0732b33481159413c8e74c91985092708b
5
5
  SHA512:
6
- metadata.gz: e08f11b282afa711d2b25cff516780804d1647cb0b51960edcca2768ddefc289647f401b36839f17e9b0c4ad9c5e116219beb2eaacf3c8627e1bae572fa2b2e4
7
- data.tar.gz: f4903d54e56315f32e43964d746d07a1b5c7ce45f2954286f1f285128268369dfeb0770ec22530a6256c35d8531551a1b09406a345ff257e3c6b7d5f3589dd2b
6
+ metadata.gz: 4afd8163e39836f303d037928e2c891dbe9528223ee2bdad1ec31bd0f9f22a708790e393aba63fe3eb1895e5176dc0e674c7e24e7dacfe704498e2b03d149290
7
+ data.tar.gz: 4b3dcae0a76b89bcbb2efa3db29352e0243a35547fa16489d967ceb918fcde4347e910e0a91c974f01c616c9037644c4cb3551d103c1148037d2a4ed97a1fa60
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015
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.
22
+
data/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # patrun-ruby
2
+
3
+ ### A fast pattern matcher on Ruby object properties.
4
+
5
+ Need to pick out an object based on a subset of its properties? Say you've got:
6
+
7
+ ```Ruby
8
+ { :x => 1 } -> A
9
+ { :x => 1, :y => 1 } -> B
10
+ { :x => 1, :y => 2 } -> C
11
+ ```
12
+
13
+ Then patrun can give you the following results:
14
+
15
+ ```Ruby
16
+ { :x => 1 } -> A
17
+ { :x => 2 } -> no match
18
+ { :x => 1, :y => 1 } -> B
19
+ { :x => 1, :y => 2 } -> C
20
+ { :x => 2, :y => 2 } -> no match
21
+ { :y => 1 } -> no match
22
+ ```
23
+
24
+ It's basically _query-by-example_ for property sets.
25
+
26
+
27
+ ### Support
28
+
29
+ If you're using this library, feel free to contact me on twitter if you have any questions! :) [@colmharte](http://twitter.com/colmharte)
30
+
31
+
32
+ Current Version: 0.1.1
33
+
34
+ Tested on: Ruby 2.0.0p481
35
+
36
+
37
+ ### Quick example
38
+
39
+ Here's how you register some patterns, and then search for matches:
40
+
41
+ ```Ruby
42
+ require('patrun')
43
+
44
+ pm = Patrun.new()
45
+ pm.add({:a => 1},'A').add({:b => 2},'B')
46
+
47
+ # prints A
48
+ puts pm.find({:a => 1})
49
+
50
+ # prints nil
51
+ puts pm.find({:a => 2})
52
+
53
+ # prints A, :b => 1 is ignored, it was never registered
54
+ puts pm.find({:a => 1, :b => 1})
55
+
56
+ # prints B, :c => 3 is ignored, it was never registered
57
+ puts pm.find({:b => 2, :c => 3})
58
+ ```
59
+
60
+ You're matching a subset, so your input can contain any number of other properties.
61
+
62
+
63
+ ## Install
64
+
65
+ ```sh
66
+ gem install patrun
67
+ ```
68
+
69
+
70
+ # The Why
71
+
72
+ This module lets you build a simple decision tree so you can avoid
73
+ writing _if_ statements. It tries to make the minimum number of
74
+ comparisons necessary to pick out the most specific match.
75
+
76
+ This is very useful for handling situations where you have lots of
77
+ "cases", some of which have "sub-cases", and even "sub-sub-sub-cases".
78
+
79
+ For example, here are some sales tax rules:
80
+
81
+ * default: no sales tax
82
+ * here's a list of countries with known rates: Ireland: 23%, UK: 20%, Germany: 19%, ...
83
+ * but wait, that's only standard rates, here's [the other rates](http://www.vatlive.com/vat-rates/european-vat-rates/eu-vat-rates/)
84
+ * Oh, and we also have the USA, where we need to worry about each state...
85
+
86
+ Do this:
87
+
88
+ ```Ruby
89
+ # queries return a Proc, in case there is some
90
+ # really custom logic (and there is, see US, NY below)
91
+ # in the normal case, just pass the rate back out with
92
+ # an identity function
93
+ # also record the rate for custom printing later
94
+ I = Proc.new { | val |
95
+ rate = Proc.new {
96
+ val
97
+ }
98
+ rate
99
+ }
100
+
101
+ salestax = Patrun.new()
102
+ salestax
103
+ .add({}, I.call(0.0) )
104
+ .add({ :country => 'IE' }, I.call(0.25) )
105
+ .add({ :country => 'UK' }, I.call(0.20) )
106
+ .add({ :country => 'DE' }, I.call(0.19) )
107
+ .add({ :country => 'IE', :type => 'reduced' }, I.call(0.135) )
108
+ .add({ :country => 'IE', :type => 'food' }, I.call(0.048) )
109
+ .add({ :country => 'UK', :type => 'food' }, I.call(0.0) )
110
+ .add({ :country => 'DE', type:'reduced' }, I.call(0.07) )
111
+ .add({ :country => 'US' }, I.call(0.0) ) # no federeal rate (yet!)
112
+ .add({ :country => 'US', :state => 'AL' }, I.call(0.04) )
113
+ .add({ :country => 'US', :state => 'AL', city:'Montgomery' }, I.call(0.10) )
114
+ .add({ :country => 'US', :state => 'NY' }, I.call(0.07) )
115
+
116
+ under110 = Proc.new { | net |
117
+ net < 110 ? 0.0 : salestax.find( {:country => 'US', :state => 'NY'}).call(net)
118
+ }
119
+
120
+ salestax.add({ :country => 'US', :state => 'NY', :type => 'reduced' }, under110)
121
+
122
+ puts "Default rate: #{salestax.find({}).call(99)}"
123
+
124
+ puts "Standard rate in Ireland on E99: #{salestax.find({country:'IE'}).call(99)}"
125
+
126
+ puts "Food rate in Ireland on E99: #{salestax.find({country:'IE',type:'food'}).call(99)}"
127
+
128
+ puts "Reduced rate in Germany on E99: #{salestax.find({country:'IE',type:'reduced'}).call(99)}"
129
+
130
+ puts "Standard rate in Alabama on $99: #{salestax.find({country:'US',state:'AL'}).call(99)}"
131
+
132
+ puts "Standard rate in Montgomery, Alabama on $99: #{salestax.find({country:'US',state:'AL',city:'Montgomery'}).call(99)}"
133
+
134
+ puts "Reduced rate in New York for clothes on $99: #{salestax.find({country:'US',state:'NY',type:'reduced'}).call(199)}"
135
+
136
+
137
+ # prints:
138
+ # Default rate: 0
139
+ # Standard rate in Ireland on E99: 0.25
140
+ # Food rate in Ireland on E99: 0.048
141
+ # Reduced rate in Germany on E99: 0.135
142
+ # Standard rate in Alabama on $99: 0.04
143
+ # Standard rate in Montgomery, Alabama on $99: 0.1
144
+ # Reduced rate in New York for clothes on $99: 0.0
145
+ ```
146
+
147
+ You can take a look a the decision tree at any time:
148
+
149
+ ```Ruby
150
+
151
+ # print out patterns, using a custom format function
152
+ puts salestax.toString( Proc.new { | f | ":#{f.call(99)}"})
153
+
154
+
155
+ # prints:
156
+ -> :0.0
157
+ city=Montgomery, country=US, state=AL -> :0.1
158
+ country=IE -> :0.25
159
+ country=IE, type=reduced -> :0.135
160
+ country=IE, type=food -> :0.048
161
+ country=UK -> :0.2
162
+ country=UK, type=food -> :0.0
163
+ country=DE -> :0.19
164
+ country=DE, type=reduced -> :0.07
165
+ country=US -> :0.0
166
+ country=US, state=AL -> :0.04
167
+ country=US, state=NY -> :0.07
168
+ country=US, state=NY, type=reduced -> :0.0
169
+ ```
170
+
171
+
172
+ # The Rules
173
+
174
+ * 1: More specific matches beat less specific matches. That is, more property values beat fewer.
175
+ * 2: Property names are checked in alphabetical order.
176
+
177
+ And that's it.
178
+
179
+
180
+ # Customization
181
+
182
+ You can customize the way that data is stored. For example, you might want to add a constant property to each pattern.
183
+
184
+ To do this, you provide a custom function when you create the _patrun_ object:
185
+
186
+ ```Ruby
187
+ alwaysAddFoo = Patrun.new( Proc.new{ | pm, pat, data |
188
+ pat['foo'] = true
189
+ })
190
+
191
+ alwaysAddFoo.add( {:a => 1}, "bar" )
192
+
193
+ alwaysAddFoo.find( {:a => 1} ) # nothing!
194
+ alwaysAddFoo.find( {:a => 1, :foo => true} ) # == "bar"
195
+ ```
196
+
197
+ Your custom function can also return a modifer function for found
198
+ data, and optionally a modifier for removing data.
199
+
200
+ Here's an example that modifies found data:
201
+
202
+ ```Ruby
203
+ upperify = Patrun.new( Proc.new { | pm, pat, data |
204
+ Proc.new { | pm, pat, data |
205
+ data.to_s.upcase()
206
+ }
207
+ })
208
+
209
+ upperify.add( {:a => 1}, "bar" )
210
+
211
+ upperify.find( {:a => 1} ) # BAR
212
+ ```
213
+
214
+ Finally, here's an example that allows you to add multiple matches for a given pattern:
215
+
216
+ ```Ruby
217
+ many = Patrun.new( Proc.new { | pm, pat, data |
218
+ items = pm.find(pat,true) || []
219
+ items.push(data)
220
+
221
+ {:find => Proc.new { | pm, args, data |
222
+ 0 < items.length ? items : nil
223
+ },
224
+ :remove => Proc.new { | pm, args, data |
225
+ items.pop()
226
+ 0 == items.length
227
+ }
228
+ }
229
+ })
230
+
231
+ many.add( {:a => 1}, 'A' )
232
+ many.add( {:a => 1}, 'B' )
233
+ many.add( {:b => 1}, 'C' )
234
+
235
+ many.find( {:a => 1} ) # [ 'A', 'B' ]
236
+ many.find( {:b => 1} ) # [ 'C' ]
237
+
238
+ many.remove( {:a => 1} )
239
+ many.find( {:a => 1} ) # [ 'A' ]
240
+
241
+ many.remove( {:b => 1} )
242
+ many.find( {:b => 1} ) # nil
243
+ ```
244
+
245
+
246
+ # API
247
+
248
+ ## patrun( custom )
249
+
250
+ Generates a new pattern matcher instance. Optionally provide a customisation Proc.
251
+
252
+
253
+ ## .add( {...pattern...}, object )
254
+
255
+ Register a pattern, and the object that will be returned if an input
256
+ matches. Both keys and values are considered to be strings. Other
257
+ types are converted to strings.
258
+
259
+ ## .find( {...subject...}[, exact] )
260
+
261
+ Return the unique match for this subject, or nil if not found. The
262
+ properties of the subject are matched against the patterns previously
263
+ added, and the most specifc pattern wins. Unknown properties in the
264
+ subject are ignored. You can optionally provide a second boolean
265
+ parameter, _exact_. If true, then all properties of the subject must
266
+ match.
267
+
268
+
269
+ ## .list( {...pattern-partial...}[, exact] )
270
+
271
+ Return the list of registered patterns that contain this partial
272
+ pattern. You can use wildcards for property values. Omitted values
273
+ are *not* equivalent to a wildcard of _"*"_, you must specify each
274
+ property explicitly. You can optionally provide a second boolean
275
+ parameter, _exact_. If true, then only those patterns matching the
276
+ pattern-partial exactly are returned.
277
+
278
+ ```Ruby
279
+ pm = Patrun.new()
280
+ .add({:a => 1, :b => 1},'B1')
281
+ .add({:a => 1, :b => 2},'B2')
282
+
283
+ # finds nothing: []
284
+ puts pm.list({:a => 1})
285
+
286
+ # finds:
287
+ # [ { match: { :a => '1', :b => '1' }, :data => 'B1' },
288
+ # { match: { :a => '1', :b => '2' }, :data => 'B2' } ]
289
+ puts pm.list({:a => 1, :b => '*'})
290
+ ```
291
+
292
+ If you provide no pattern argument at all, _list_ will list all patterns that have been added.
293
+ ```Ruby
294
+ # finds everything
295
+ puts pm.list()
296
+ ```
297
+
298
+ ## .remove( {...pattern...} )
299
+
300
+ Remove this pattern, and it's object, from the matcher.
301
+
302
+
303
+ ## .toString( [proc][, tree] )
304
+
305
+ Generate a string representation of the decision tree for debugging. Optionally provide a formatting Proc for objects.
306
+
307
+ * proc: format proc for data, optional
308
+ * tree: boolean flag, if true, print an indented tree rather than a list of patterns, default: false
309
+
310
+ ## .toJSON()
311
+
312
+ Generate JSON representation of the tree.
313
+
314
+
315
+ # Development
316
+
317
+ From the Irish patr&uacute;n: [pattern](http://www.focloir.ie/en/dictionary/ei/pattern). Pronounced _pah-troon_.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patrun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Colm Harte
@@ -14,15 +14,24 @@ description: A fast pattern matcher on Ruby object properties.
14
14
  email: colm.harte@nearform.com
15
15
  executables: []
16
16
  extensions: []
17
- extra_rdoc_files: []
17
+ extra_rdoc_files:
18
+ - README.md
19
+ - LICENSE
18
20
  files:
19
21
  - lib/patrun.rb
20
- homepage: http://rubygems.org/gems/patrun
22
+ - README.md
23
+ - LICENSE
24
+ homepage: https://github.com/colmharte/patrun-ruby
21
25
  licenses:
22
26
  - MIT
23
27
  metadata: {}
24
28
  post_install_message:
25
- rdoc_options: []
29
+ rdoc_options:
30
+ - --main
31
+ - README.md
32
+ - --title
33
+ - Fast Pattern Matcher
34
+ - --line-numbers
26
35
  require_paths:
27
36
  - lib
28
37
  required_ruby_version: !ruby/object:Gem::Requirement