mobj 1.6.9 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +157 -1
- data/lib/mobj.rb +31 -6
- metadata +1 -1
data/README.md
CHANGED
@@ -3,14 +3,170 @@ mobj
|
|
3
3
|
|
4
4
|
Some utilities that I keep using.
|
5
5
|
|
6
|
+
Of particular note may be the string tokenizer/path walker.
|
7
|
+
|
6
8
|
Example
|
7
9
|
===
|
8
10
|
|
9
11
|
Read the code.
|
10
12
|
|
13
|
+
No, really. Give me some examples, dork.
|
14
|
+
===
|
15
|
+
|
16
|
+
Fine.
|
17
|
+
|
18
|
+
### Toking Up
|
19
|
+
|
20
|
+
This is really the most useful part of the entire library, when you get right down to it.
|
21
|
+
String is given a method called "tokenize" that allows it's contents to be split up into
|
22
|
+
a structured path of tokens. This tokenized path can then be used to do some pretty amazing
|
23
|
+
things in terms of walking through arbitrarily complex trees of data.
|
24
|
+
|
25
|
+
For example, given this object:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
obj = {
|
29
|
+
name: { first: "Joe", last: "Smith" },
|
30
|
+
ids: [ 1, 3, 5, 16, 941, 13, 100, 3, 0, 104 ],
|
31
|
+
auth_tokens: [ { provider: { name: "example.com", id:123 }, token: { auth: "123456", expire: "10-20-2012" } },
|
32
|
+
{ provider: { name: "site.com", id:265 }, token: { authentication_token: "891011", date: "10-20-2013" } }
|
33
|
+
],
|
34
|
+
primary_key: { path: "auth_tokens.provider" }
|
35
|
+
}
|
36
|
+
```
|
37
|
+
|
38
|
+
... Easily traverse it's data using rules embedded into a simple string, like so:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# Walk a simple object path:
|
42
|
+
"name.first".walk(obj)
|
43
|
+
#=> "Joe"
|
44
|
+
|
45
|
+
# Select multiple items out of an object:
|
46
|
+
"name.first,last".walk(obj)
|
47
|
+
#=> ["Joe", "Smith"]
|
48
|
+
|
49
|
+
# Or indexes (and ranges) in an array:
|
50
|
+
"ids[1, 3, 5 - 7, 9+]".walk(obj)
|
51
|
+
#=> [3, 16, 13, 100, 3, 104]
|
52
|
+
|
53
|
+
# Use regular expressions or method calls as selection keys:
|
54
|
+
"auth_tokens.token./^auth/.*to_i".walk(obj)
|
55
|
+
#=> [ 123456, 891011 ]
|
56
|
+
|
57
|
+
# Choose the first element that doesn't return nil:
|
58
|
+
"auth_tokens.token.expire|date".walk(obj)
|
59
|
+
#=> [ "10-20-2012", "10-20-2013" ]
|
60
|
+
|
61
|
+
# Provide default values when everything is nil:
|
62
|
+
"/auth/.provider,token.auth|authentication_token|~N/A".walk(obj)
|
63
|
+
#=> ["N/A", "N/A", "123456", "891011"]
|
64
|
+
|
65
|
+
# Even look up keys based on the values in other fields:
|
66
|
+
"{{primary_key.path}}.id".walk(obj)
|
67
|
+
#=> [123, 265]
|
68
|
+
|
69
|
+
```
|
70
|
+
|
71
|
+
And much, much more, though you'd probably want to check out the tests to see how it really works.
|
72
|
+
|
73
|
+
### Much Ado about Nulling
|
74
|
+
|
75
|
+
Every so often you find yourself in a situation where you want nil to behave like "null"
|
76
|
+
(i.e. you want it to silently allow unknown method calls to simply do nothing instead of throwing
|
77
|
+
and exception). All classes are now given a "null!" method that alters the behavior of nil for
|
78
|
+
the remainder of the line:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
obj = FooObject.new
|
82
|
+
|
83
|
+
if obj.null!.foo.bar.baz
|
84
|
+
puts "Acts like normal"
|
85
|
+
end
|
86
|
+
|
87
|
+
obj = nil
|
88
|
+
|
89
|
+
if obj.null!.foo.bar.baz
|
90
|
+
puts "Won't throw and exception and evaluates to nil"
|
91
|
+
end
|
92
|
+
|
93
|
+
if obj.foo.bar.baz
|
94
|
+
puts "Will throw an exception as normal"
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
### When you just want to be contrary
|
99
|
+
|
100
|
+
Sometimes you just want to write everything in dot notation for no reason.
|
101
|
+
Or maybe you have a reason, and that reason is impressing your co workers with your
|
102
|
+
ruby tricks:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
if foo && foo.bar
|
106
|
+
foo.baz
|
107
|
+
else
|
108
|
+
foo.biz
|
109
|
+
end
|
110
|
+
```
|
111
|
+
... becomes ...
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
foo.when.bar.then.baz.else.biz
|
115
|
+
```
|
116
|
+
|
117
|
+
### Sequestration, HashEx, MatchEx, etc
|
118
|
+
|
119
|
+
Found myself writing these things sooo many times that I just dumped them into my package of stuff:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
foo.compact.size == 1 ? foo.compact.first : foo.compact
|
123
|
+
```
|
124
|
+
... becomes ...
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
foo.sequester
|
128
|
+
```
|
129
|
+
|
130
|
+
Hash utils:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
hash = { symbol: "sym", "string" => "string", :nilval => nil }
|
134
|
+
|
135
|
+
hash.symbol == hash[:symbol] == hash[:symbol]
|
136
|
+
#=> These are equvilent
|
137
|
+
|
138
|
+
hash.string == hash[:string] == hash["string"]
|
139
|
+
#=> These are also equvilent
|
140
|
+
|
141
|
+
hash.string?
|
142
|
+
#=> true
|
143
|
+
|
144
|
+
hash.nilval?
|
145
|
+
#=> false
|
146
|
+
|
147
|
+
hash.unknown?
|
148
|
+
#=> True
|
149
|
+
```
|
150
|
+
|
151
|
+
Matching stuff
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
match = "Joe Bob".match(/(?<first_name>\w+) (?<first_name>)/)
|
155
|
+
|
156
|
+
match.to_hash
|
157
|
+
#=> Creates a hash of named captures
|
158
|
+
|
159
|
+
match.first_name if match.first_name?
|
160
|
+
#=> Or just use them directly as method names
|
161
|
+
|
162
|
+
"foo,bar,baz".matches(/([^,]+),/) do |match|
|
163
|
+
#=> same as "string#scan", but returns the actual MatchData
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
11
167
|
---
|
12
168
|
|
13
169
|
Caveats and such
|
14
170
|
===
|
15
171
|
|
16
|
-
|
172
|
+
All of them.
|
data/lib/mobj.rb
CHANGED
@@ -5,6 +5,8 @@ module Mobj
|
|
5
5
|
klass = class << self; self end
|
6
6
|
klass.superclass
|
7
7
|
end
|
8
|
+
def null!() self end
|
9
|
+
def nil!() self end
|
8
10
|
end
|
9
11
|
|
10
12
|
class ::Fixnum
|
@@ -244,15 +246,33 @@ module Mobj
|
|
244
246
|
end
|
245
247
|
elsif path.is_a?(Array)
|
246
248
|
path.map { |pth| obj[pth.sym] }
|
249
|
+
elsif path[0] == '*' && obj.respond_to?(path[1..-1].sym)
|
250
|
+
obj.__send__(path[1..-1].sym)
|
251
|
+
elsif obj.respond_to? :[]
|
252
|
+
if obj.is_a?(Hash)
|
253
|
+
obj[path.sym]
|
254
|
+
else
|
255
|
+
obj[path.to_s.to_i] if path.to_s =~ /^\d+$/
|
256
|
+
end
|
247
257
|
elsif obj.respond_to?(path.sym)
|
248
258
|
obj.__send__(path.sym)
|
249
|
-
elsif obj.respond_to? :[]
|
250
|
-
obj[path.sym]
|
251
259
|
else
|
252
260
|
nil
|
253
261
|
end
|
254
262
|
end
|
255
263
|
|
264
|
+
|
265
|
+
|
266
|
+
def find(obj, match)
|
267
|
+
if obj.is_a?(Array)
|
268
|
+
obj.map do |child|
|
269
|
+
find(child, match)
|
270
|
+
end
|
271
|
+
elsif obj.respond_to?(:keys)
|
272
|
+
obj.keys.map { |key| key if key.match(match) }.compact.map{|key| obj[key] }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
256
276
|
def walk(obj, root = obj)
|
257
277
|
obj, root = Circle.wrap(obj), Circle.wrap(root)
|
258
278
|
val = case @type
|
@@ -261,7 +281,7 @@ module Mobj
|
|
261
281
|
when :path
|
262
282
|
extract(obj, @path)
|
263
283
|
when :regex
|
264
|
-
obj
|
284
|
+
find(obj, @path)
|
265
285
|
when :up
|
266
286
|
if obj.respond_to? :parent
|
267
287
|
obj.__mobj__parent || obj.__mobj__parent
|
@@ -269,7 +289,11 @@ module Mobj
|
|
269
289
|
obj.__mobj__parent
|
270
290
|
end
|
271
291
|
when :any
|
272
|
-
|
292
|
+
if obj.is_a?(Array)
|
293
|
+
obj.map { |o| walk(o, root) }
|
294
|
+
else
|
295
|
+
@path.return_first { |token| token.walk(obj, root) }
|
296
|
+
end
|
273
297
|
when :all
|
274
298
|
matches = @path.map { |token| token.walk(obj, root) }
|
275
299
|
matches.compact.size == @path.size ? matches : nil
|
@@ -277,6 +301,7 @@ module Mobj
|
|
277
301
|
@path.map { |token| token.walk(obj, root) }
|
278
302
|
when :lookup
|
279
303
|
lookup = @path.walk(obj)
|
304
|
+
puts "lookup '#{lookup}':#{obj}:#{@path}"
|
280
305
|
if lookup.is_a?(Array)
|
281
306
|
lookup.flatten.map { |lu| lu.tokenize.walk(root) }.flatten(1)
|
282
307
|
else
|
@@ -322,7 +347,7 @@ module Mobj
|
|
322
347
|
lookup = /\{\{(?<lookup>.*?)\}\}/
|
323
348
|
up = /(?<up>\^)/
|
324
349
|
path = /(?<path>[^\.\[]+)/
|
325
|
-
indexes = /(?<indexes>[\d
|
350
|
+
indexes = /(?<indexes>[\d\+\.,\s-]+)/
|
326
351
|
|
327
352
|
matcher = /#{lit}|#{regex}|#{lookup}|#{up}|#{path}(?:\[#{indexes}\])?/
|
328
353
|
|
@@ -349,7 +374,7 @@ module Mobj
|
|
349
374
|
|
350
375
|
unless ands.size + ors.size + eachs.size > 3
|
351
376
|
options = {}
|
352
|
-
index_matcher =
|
377
|
+
index_matcher = /\s*(?<low>\d+)\s*(?:(?:\.\s*\.\s*(?<ex>\.)?\s*|-?)\s*(?<high>-?\d+|\+))?\s*/
|
353
378
|
|
354
379
|
options[:indexes] = match.indexes.matches(index_matcher).map do |index|
|
355
380
|
if index.high?
|