mobj 1.6.9 → 1.7.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.
- 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?
|