patrun 0.1.0

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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/patrun.rb +406 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 26be7b15694fcdb3af30b2cf505e1f5446e75094
4
+ data.tar.gz: 8d6c257f618b6c0acdbfa7ff47a2cb02c0225014
5
+ SHA512:
6
+ metadata.gz: e08f11b282afa711d2b25cff516780804d1647cb0b51960edcca2768ddefc289647f401b36839f17e9b0c4ad9c5e116219beb2eaacf3c8627e1bae572fa2b2e4
7
+ data.tar.gz: f4903d54e56315f32e43964d746d07a1b5c7ce45f2954286f1f285128268369dfeb0770ec22530a6256c35d8531551a1b09406a345ff257e3c6b7d5f3589dd2b
data/lib/patrun.rb ADDED
@@ -0,0 +1,406 @@
1
+ class Patrun
2
+ def initialize(custom = nil)
3
+ @top = {}
4
+ @custom = custom
5
+ end
6
+
7
+
8
+ def add(pat, data)
9
+
10
+ customizer = nil
11
+ if @custom
12
+ customizer = @custom.call(self, pat, data)
13
+ end
14
+
15
+ pat = pat.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
16
+
17
+ keys = pat.keys.sort()
18
+
19
+ keymap = @top
20
+ valmap = nil
21
+
22
+ i = 0
23
+ while i < keys.length
24
+ key = keys[i]
25
+ val = pat[keys[i]]
26
+
27
+ if nil == val
28
+ next
29
+ end
30
+
31
+ valmap = keymap[:v]
32
+ if valmap && key == keymap[:k]
33
+ keymap = valmap[val] || (valmap[val] = {})
34
+
35
+ elsif !keymap[:k]
36
+ keymap[:k] = key
37
+
38
+ keymap[:v] = {}
39
+
40
+ keymap = keymap[:v][val] = {}
41
+
42
+ else
43
+ if key < keymap[:k]
44
+ curv = keymap[:v]
45
+ curs = keymap[:s]
46
+ keymap[:v] = {}
47
+ keymap[:s] = {:k => keymap[:k], :v => curv, :s => curs}
48
+
49
+ keymap[:k] = key
50
+ keymap = keymap[:v][val] = {}
51
+ else
52
+ valmap = keymap[:v]
53
+ keymap = keymap[:s] || (keymap[:s] = {})
54
+ i-= 1
55
+ end
56
+ end
57
+ i += 1
58
+ end
59
+
60
+ if data != nil && keymap
61
+ keymap[:d] = data
62
+ if customizer
63
+ keymap[:f] = isFunction(customizer) ? customizer : (customizer.class == Hash ? customizer[:find] : false)
64
+ if (customizer.class == Hash)
65
+ keymap[:r] = isFunction(customizer[:remove]) ? customizer[:remove] : nil
66
+ end
67
+ end
68
+ end
69
+
70
+ self
71
+ end
72
+
73
+
74
+
75
+ def findexact(pat)
76
+ find(pat, true)
77
+ end
78
+
79
+ def find(pat, exact = false)
80
+
81
+ pat = pat.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
82
+
83
+ if nil == pat
84
+ return nil
85
+ end
86
+
87
+ keymap = @top
88
+ data = @top[:d] || nil
89
+ finalfind = @top[:f]
90
+ key = nil
91
+ stars = []
92
+ foundkeys = {}
93
+ patlen = pat.keys.length
94
+
95
+
96
+ loop do
97
+ key = keymap[:k]
98
+
99
+ if keymap[:v]
100
+ nextkeymap = keymap[:v][pat[key]]
101
+ if nextkeymap
102
+ foundkeys[key] = true
103
+
104
+ if keymap[:s]
105
+ stars.push(keymap[:s])
106
+ end
107
+
108
+ data = nil == nextkeymap[:d] ? nil : nextkeymap[:d]
109
+ finalfind = nextkeymap[:f]
110
+ keymap = nextkeymap
111
+
112
+ else
113
+ keymap = keymap[:s]
114
+ end
115
+
116
+ else
117
+ keymap = nil
118
+ end
119
+
120
+ if nil == keymap && nil == data && 0 < stars.length
121
+ keymap = stars.pop()
122
+ end
123
+
124
+ break if !keymap
125
+ end
126
+
127
+ # special case for default with no properties
128
+ if nil == data && 0 == patlen && nil != top[:d]
129
+ data = top[:d]
130
+ finalfind = top[:f]
131
+ end
132
+
133
+ if exact && foundkeys.keys.length != patlen
134
+ data = nil
135
+ end
136
+
137
+ if finalfind
138
+ data = finalfind.call(self, pat, data)
139
+ end
140
+
141
+ data
142
+ end
143
+
144
+ def remove(pat)
145
+ keymap = @top
146
+ data = nil
147
+ key = nil
148
+ path = []
149
+
150
+ loop do
151
+ key = keymap[:k]
152
+
153
+ if keymap[:v]
154
+ nextkeymap = keymap[:v][pat[key]]
155
+ if nextkeymap
156
+ path.push({:km => keymap,:v => pat[key]})
157
+ data = nextkeymap[:d]
158
+ keymap = nextkeymap
159
+
160
+ else
161
+ keymap = keymap[:s]
162
+ end
163
+
164
+ else
165
+ keymap = nil
166
+ end
167
+
168
+ break if !keymap
169
+ end
170
+
171
+ if nil != data
172
+ part = path[path.length-1]
173
+ if part && part[:km] && part[:km][:v]
174
+ point = part[:km][:v][part[:v]]
175
+ if !point[:r] || point[:r].call(self, pat, point[:d])
176
+ point.delete(:d)
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+
183
+
184
+
185
+ # values can be verbatim, glob, or array of globs
186
+ def list(pat = nil, exact = false)
187
+
188
+ acc = []
189
+
190
+ if @top[:d]
191
+
192
+ acc.push({:match => {},
193
+ :data => @top[:d],
194
+ :find => top[:f]
195
+ })
196
+ end
197
+
198
+ if pat != nil
199
+ pat = pat.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
200
+ end
201
+ descend(pat, exact, @top, {}, merge({}, pat), acc)
202
+
203
+
204
+ acc
205
+ end
206
+
207
+
208
+ def toString(dstr = nil, tree = false)
209
+ defaultFormat = Proc.new { | d |
210
+ isFunction(d) ? "<#{d}>" : "<#{d}>"
211
+ }
212
+
213
+ tree = isBoolean(dstr) ? dstr : tree
214
+ tree = nil == tree ? false : tree
215
+
216
+ dstr = isFunction(dstr) ? dstr : defaultFormat
217
+
218
+ str = []
219
+ o = []
220
+
221
+ walk(@top,o,0,[], str, dstr)
222
+
223
+ return tree ? o.join('') : str.join("\n")
224
+ end
225
+
226
+
227
+ def inspect
228
+ this.toString()
229
+ end
230
+
231
+
232
+ def toJSON(indent)
233
+
234
+ @top.to_json()
235
+
236
+ end
237
+
238
+ private
239
+
240
+
241
+
242
+ def indent(o,d)
243
+ for i in 0..d
244
+ o.push(' ')
245
+ end
246
+ end
247
+
248
+
249
+ def descend(pat, exact, keymap, match, missing, acc)
250
+
251
+ if keymap[:v]
252
+ key = keymap[:k]
253
+ patVal = pat ? (nil == pat[key] ? ( exact ? nil : '*' ) : pat[key]) : '*'
254
+
255
+ itermatch = merge({}, match)
256
+ itermissing = merge({}, missing)
257
+ nextkeymap = nil
258
+
259
+
260
+ for val in keymap[:v].keys
261
+
262
+ if gexval(patVal, val)
263
+
264
+ valitermatch = deepCopy(itermatch)
265
+ valitermatch[key] = val
266
+
267
+ valitermissing = merge({}, itermissing)
268
+ valitermissing.delete(key)
269
+
270
+ nextkeymap = keymap[:v][val]
271
+
272
+ if 0 == valitermissing.keys.length &&
273
+ nextkeymap &&
274
+ nextkeymap[:d]
275
+
276
+ acc.push({
277
+ :match => valitermatch,
278
+ :data => nextkeymap[:d],
279
+ :find => nextkeymap[:f]
280
+ })
281
+ end
282
+
283
+ if nextkeymap && nextkeymap[:v]
284
+
285
+ descend(pat, exact,
286
+ nextkeymap,
287
+ merge({}, valitermatch),
288
+ merge({}, valitermissing),
289
+ acc)
290
+ end
291
+ end
292
+ end
293
+
294
+ nextkeymap = keymap[:s]
295
+ if nextkeymap
296
+ descend(pat, exact,
297
+ nextkeymap,
298
+ merge({}, itermatch),
299
+ merge({}, itermissing),
300
+ acc)
301
+ end
302
+ end
303
+ end
304
+
305
+ def walk(n,o,counter,vs,str, dstr)
306
+
307
+ if nil != n[:d]
308
+ indent(o,counter)
309
+ o.push(dstr.call(n[:d]))
310
+
311
+ str.push("#{vs.join(', ')} -> #{dstr.call(n[:d])}")
312
+ end
313
+
314
+ if n[:k]
315
+ o.push("\n")
316
+ indent(o,counter)
317
+ o.push("#{n[:k]}:")
318
+ end
319
+
320
+ if n[:v]
321
+ counter =+ 1
322
+ for p in n[:v].keys
323
+ o.push("\n")
324
+ indent(o,counter)
325
+ o.push("#{p} ->")
326
+
327
+ vsc = deepCopy(vs)
328
+ vsc.push("#{n[:k]}=#{p}")
329
+
330
+ walk(n[:v][p],o,counter + 1,vsc,str,dstr)
331
+ end
332
+
333
+ if n[:s]
334
+ o.push("\n")
335
+ indent(o,counter)
336
+ o.push('* ->')
337
+
338
+ vsc = deepCopy(vs)
339
+ walk(n[:s],o,counter + 1,vsc,str,dstr)
340
+ end
341
+ end
342
+ end
343
+
344
+
345
+ def isBoolean(obj)
346
+
347
+ if obj != nil && obj.class == TrueClass || obj.class == FalseClass
348
+
349
+ true
350
+ else
351
+ false
352
+ end
353
+ end
354
+
355
+ def deepCopy(o)
356
+ Marshal.load(Marshal.dump(o))
357
+ end
358
+
359
+ def isFunction(param)
360
+ if param != nil && param.class == Proc
361
+ true
362
+ else
363
+ false
364
+ end
365
+ end
366
+
367
+ def merge(source, destination)
368
+ if destination != nil
369
+ source.merge(destination)
370
+ else
371
+ source
372
+ end
373
+ end
374
+
375
+ def gexval(pattern, value)
376
+
377
+
378
+ pattern = escregexp(pattern)
379
+
380
+ # use [\s\S] instead of . to match newlines
381
+ pattern = pattern.gsub(/\\\*/,'[\\s\\S]*')
382
+ pattern = pattern.gsub(/\\\?/,'[\\s\\S]')
383
+
384
+ # escapes ** and *?
385
+ pattern = pattern.gsub(/\[\\s\\S\]\*\[\\s\\S\]\*/,'\\\*')
386
+ pattern = pattern.gsub(/\[\\s\\S\]\*\[\\s\\S\]/,'\\\?')
387
+
388
+ pattern = "^#{pattern}$"
389
+
390
+ if value.to_s.match(pattern) != nil
391
+ true
392
+ else
393
+ false
394
+ end
395
+
396
+ end
397
+
398
+ def escregexp(restr)
399
+ if restr
400
+ restr.to_s.gsub(/[-\[\]{}()*+?.,\\^$|#\s]/) { "\\#{$&}" }
401
+ else
402
+ ""
403
+ end
404
+ end
405
+
406
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: patrun
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Colm Harte
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A fast pattern matcher on Ruby object properties.
14
+ email: colm.harte@nearform.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/patrun.rb
20
+ homepage: http://rubygems.org/gems/patrun
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.0.14
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Fast pattern matcher
44
+ test_files: []