patrun 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +22 -0
- data/README.md +317 -0
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a552276944c28770e97035adabe76cb38332489
|
4
|
+
data.tar.gz: 651c5a0732b33481159413c8e74c91985092708b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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ú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.
|
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
|
-
|
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
|