mobj 1.6.9 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +157 -1
  2. data/lib/mobj.rb +31 -6
  3. 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
- None. It's prefection.
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.keys.map { |key| key if key.match(@path) }.compact.map{|key| obj[key] }
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
- @path.return_first { |token| token.walk(obj, root) }
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 = /(?<low>\d+)(?:(?:\.\.(?<ex>\.)?|-?)(?<high>-?\d+|\+))?/
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?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobj
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.9
4
+ version: 1.7.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: