dottie 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +289 -0
- data/Rakefile +8 -0
- data/dottie.gemspec +26 -0
- data/lib/dottie.rb +153 -0
- data/lib/dottie/ext.rb +3 -0
- data/lib/dottie/ext/array.rb +17 -0
- data/lib/dottie/ext/hash.rb +17 -0
- data/lib/dottie/freckle.rb +49 -0
- data/lib/dottie/helper.rb +6 -0
- data/lib/dottie/methods.rb +76 -0
- data/lib/dottie/version.rb +3 -0
- data/spec/array_spec.rb +55 -0
- data/spec/dottie_spec.rb +434 -0
- data/spec/freckle_spec.rb +274 -0
- data/spec/hash_spec.rb +55 -0
- data/spec/spec_helper.rb +1 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a050415bffe078f5aa5be713416cfb686a9ec913
|
4
|
+
data.tar.gz: 6f9af95ef6a28fbd9a07ae1fdc3b519c9c4a77d9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 46a17f46eb85e0757d222f1e1c2e81ab6e1cccc4657d5e6f723a091edf69013c6553b0b3cfe8e37409bd0c43a46eef739a8f85a7ce4c3f5a2241dea843e9b744
|
7
|
+
data.tar.gz: 06f2654c46e34627a7de943f59c9f2209413a5f3306733a4e34ebdfdbfc7322f89e780610a34ee890893770c23652cc4697cb5390f0be58536eb9f18025b8ff2
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nick Pearson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
# Dottie
|
2
|
+
|
3
|
+
Dottie lets you access a Hash or Array (possibly containing other Hashes and Arrays) using a dot-delimited string as the key. The string is parsed into individual keys, and Dottie traverses the data structure to find the target value. If at any point along the way a node is not found, Dottie will return `nil` rather than raising an error.
|
4
|
+
|
5
|
+
A great place Dottie is useful is when accessing a data structure parsed from a JSON string, such as when consuming a JSON API. Since the only structural elements JSON can contain are objects and arrays, Dottie can easily access anything parsed from JSON.
|
6
|
+
|
7
|
+
Here's a simple example showing a data structure as well as why Dottie is a nice way of accessing the values, especially when certain parts of the data are not guaranteed to be present:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
car = {
|
11
|
+
'color' => 'black',
|
12
|
+
'type' => {
|
13
|
+
'make' => 'Tesla',
|
14
|
+
'model' => 'Model S'
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
# normal Hash access
|
19
|
+
car['color'] # => "black"
|
20
|
+
car['type']['make'] # => "Tesla"
|
21
|
+
car['specs']['mileage'] # => # undefined method `[]' for nil:NilClass
|
22
|
+
|
23
|
+
# with Dottie
|
24
|
+
d = Dottie(car)
|
25
|
+
d['color'] # => "black"
|
26
|
+
d['type.make'] # => "Tesla"
|
27
|
+
d['specs.mileage'] # => nil
|
28
|
+
```
|
29
|
+
|
30
|
+
Here's another example showing a hash containing an array:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
family = {
|
34
|
+
'mom' => 'Alice',
|
35
|
+
'dad' => 'Bob',
|
36
|
+
'kids' => [
|
37
|
+
{ 'name' => 'Carol' },
|
38
|
+
{ 'name' => 'Dan' }
|
39
|
+
]
|
40
|
+
}
|
41
|
+
|
42
|
+
# normal Hash/Array access
|
43
|
+
family['kids'][0]['name'] # => "Carol"
|
44
|
+
family['kids'][2]['name'] # => # undefined method `[]' for nil:NilClass
|
45
|
+
family['pets'][0]['name'] # => # undefined method `[]' for nil:NilClass
|
46
|
+
|
47
|
+
# with Dottie
|
48
|
+
d = Dottie(family)
|
49
|
+
d['kids[0].name'] # => "Carol"
|
50
|
+
d['kids[2].name'] # => nil (array only has two elements)
|
51
|
+
d['pets[0].name'] # => nil ('pets' does not exist)
|
52
|
+
```
|
53
|
+
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
Add this line to your application's Gemfile:
|
57
|
+
|
58
|
+
gem 'dottie'
|
59
|
+
|
60
|
+
And then execute:
|
61
|
+
|
62
|
+
$ bundle
|
63
|
+
|
64
|
+
Or install it yourself as:
|
65
|
+
|
66
|
+
$ gem install dottie
|
67
|
+
|
68
|
+
If you want the mixin behavior described below (where you can call `dottie` and `dottie!` on `Hash` and `Array` objects), require `'dottie/ext'` in your Gemfile:
|
69
|
+
|
70
|
+
gem 'dottie', require: 'dottie/ext'
|
71
|
+
|
72
|
+
## Usage
|
73
|
+
|
74
|
+
First, let's start with some examples of Dottie's behaviors. After that, we'll see how we can access those behaviors.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# Since we have to start somewhere, here's one way of getting Dottie's
|
78
|
+
# behaviors. See the usage options below for different ways of doing
|
79
|
+
# this and to choose the best option for your app.
|
80
|
+
data = Dottie({
|
81
|
+
'a' => { 'b' => 'c' }
|
82
|
+
})
|
83
|
+
|
84
|
+
# access data with normal keys or with Dottie-style keys
|
85
|
+
data['a'] # => {"b"=>"c"}
|
86
|
+
data['a.b'] # => "c"
|
87
|
+
data.fetch('a.b') # => "c"
|
88
|
+
data.has_key?('a.b') # => true
|
89
|
+
|
90
|
+
# store a value in a nested Hash, then check the Hash
|
91
|
+
data['a.b'] = 'd'
|
92
|
+
data['a.b'] # => "d"
|
93
|
+
data.hash # => {"a"=>{"b"=>"d"}}
|
94
|
+
|
95
|
+
# store a value at a new key
|
96
|
+
data['a.e'] = 'f'
|
97
|
+
data.hash # => {"a"=>{"b"=>"d", "e"=>"f"}}
|
98
|
+
|
99
|
+
# store a value deep in a Hash
|
100
|
+
# (Dottie fills in the structure when necessary)
|
101
|
+
h = Dottie({})
|
102
|
+
h['a.b.c'] = 'd'
|
103
|
+
h.hash # => {"a"=>{"b"=>{"c"=>"d"}}}
|
104
|
+
|
105
|
+
# Dottie can also work with nested Arrays
|
106
|
+
complex = Dottie({
|
107
|
+
'a' => [{ 'b' => 'c' }, { 'd' => 'e' }]
|
108
|
+
})
|
109
|
+
complex['a[1].d'] # => "e"
|
110
|
+
|
111
|
+
# change the first array element value
|
112
|
+
complex['a[first].b'] = 'x'
|
113
|
+
complex.hash # => {"a"=>[{"b"=>"x"}, {"d"=>"e"}]}
|
114
|
+
|
115
|
+
# add another array element
|
116
|
+
complex['a[2]'] = 'y'
|
117
|
+
complex.hash # => {"a"=>[{"b"=>"x"}, {"d"=>"e"}, "y"]}
|
118
|
+
```
|
119
|
+
|
120
|
+
### Dottie Usage Options
|
121
|
+
|
122
|
+
There are three basic ways of using Dottie:
|
123
|
+
|
124
|
+
1. By mixing Dottie's behavior into a `Hash`/`Array` (most versatile)
|
125
|
+
2. As a wrapper around a Hash or Array (doesn't modify the `Hash`/`Array`)
|
126
|
+
3. By calling Dottie's module methods directly (more verbose, good for one-off uses)
|
127
|
+
|
128
|
+
Here are examples of each of these usage options:
|
129
|
+
|
130
|
+
#### 1. Using Dottie as a Mixin
|
131
|
+
|
132
|
+
This is the simplest usage. You must `require 'dottie/ext'` in order for the `dottie!` method to be added to `Hash` and `Array`. This can be done in your Gemfile as shown above.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
require 'dottie/ext' # not necessary if done in the Gemfile
|
136
|
+
|
137
|
+
# create a hash and add Dottie's behavior to it
|
138
|
+
hash = {
|
139
|
+
'a' => 'b',
|
140
|
+
'c' => {
|
141
|
+
'd' => 'e'
|
142
|
+
}
|
143
|
+
}.dottie!
|
144
|
+
|
145
|
+
# the hash is still a Hash
|
146
|
+
hash.class # => Hash
|
147
|
+
|
148
|
+
# and it still does normal lookups
|
149
|
+
hash['a'] # => "b"
|
150
|
+
|
151
|
+
# but it can also look up Dottie-style keys now
|
152
|
+
hash['c.d'] # => "e"
|
153
|
+
```
|
154
|
+
|
155
|
+
The `Hash` and `Array` extensions are a nice but optional way to get convenient access to Dottie's behavior on built-in classes. To add Dottie's behavior to a `Hash` or `Array` without the use of the class extensions, you can extend the object with Dottie's methods. This is what the `dottie!` extension methods do internally.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
h = {
|
159
|
+
'a' => { 'b' => 'c' }
|
160
|
+
}
|
161
|
+
h.extend(Dottie::Methods)
|
162
|
+
h['a.b'] # => "c"
|
163
|
+
```
|
164
|
+
|
165
|
+
#### 2. Using Dottie as a Wrapper
|
166
|
+
|
167
|
+
This is the preferred method if you do not wish to modify the `Hash` or `Array` you're working with. In this case, your object will be wrapped in a `Dottie::Freckle`, which will more or less act like the original object. There are a few exceptions to this, such as when checking for equality. (For this, use `.hash` or `.array` to get access to the wrapped object.)
|
168
|
+
|
169
|
+
To wrap an object, use `Dottie()` or `Dottie[]` (which are equivalent), or manually create a `Dottie::Freckle` instance. Or, if you have required `'dottie/ext'`, you can call `dottie` (not `dottie!`) on your object.
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
# create a Hash
|
173
|
+
hash = {
|
174
|
+
'a' => { 'b' => 'c' }
|
175
|
+
}
|
176
|
+
|
177
|
+
# then wrap the hash in a Dottie::Freckle with one of these (all equivalent)
|
178
|
+
d_hash = Dottie(hash)
|
179
|
+
d_hash = Dottie[Hash]
|
180
|
+
d_hash = hash.dottie # only available if 'dottie/ext' has been required
|
181
|
+
|
182
|
+
# regardless of how the Freckle was created, we can now access its data
|
183
|
+
d_hash['a'] # => {"b"=>"c"} (a standard Hash lookup)
|
184
|
+
d_hash['a.b'] # => "c" (a Dottie-style lookup)
|
185
|
+
|
186
|
+
# works the same way with Arrays
|
187
|
+
arr = ['a', { 'b' => 'c' }, 'd']
|
188
|
+
d_arr = Dottie(arr) # or Dottie[arr] or arr.dottie
|
189
|
+
d_arr['[1].b'] #=> "c"
|
190
|
+
|
191
|
+
# or do it in one line
|
192
|
+
d_hash = Dottie({ 'a' => 'b' }) # => <Dottie::Freckle {"a"=>"b"}>
|
193
|
+
d_arr = Dottie(['a', 'b', 'c']) # => <Dottie::Freckle ["a", "b", "c"]>
|
194
|
+
|
195
|
+
# or, use the class extensions (must require 'dottie/ext')
|
196
|
+
d_hash = { 'a' => 'b' }.dottie # => <Dottie::Freckle {"a"=>"b"}>
|
197
|
+
d_arr = ['a', 'b', 'c'].dottie # => <Dottie::Freckle ["a", "b", "c"]>
|
198
|
+
```
|
199
|
+
|
200
|
+
#### 3. Using Dottie's Methods Directly
|
201
|
+
|
202
|
+
This is an easy way to use Dottie's behaviors without modifying your objects and without wrapping them. For this, the syntax is more verbose, but if this suits you, here it is:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
# create a Hash
|
206
|
+
hash = {
|
207
|
+
'a' => { 'b' => 'c' }
|
208
|
+
}
|
209
|
+
|
210
|
+
# perform a Dottie-style lookup
|
211
|
+
Dottie.get(hash, 'a.b') # => "c"
|
212
|
+
|
213
|
+
# perform a standard lookup to verify there's no Dottie behavior
|
214
|
+
hash['a.b'] # => nil (there is no literal "a.b" key)
|
215
|
+
|
216
|
+
# store a value, Dottie-style
|
217
|
+
Dottie.set(hash, 'a.b', 'x')
|
218
|
+
hash # => {"a"=>{"b"=>"x"}}
|
219
|
+
|
220
|
+
# store a value, Hash-style
|
221
|
+
hash['a.b'] = 'z'
|
222
|
+
hash # => {"a"=>{"b"=>"x"}, "a.b"=>"z"} # stored literally as "a.b"
|
223
|
+
|
224
|
+
# let's do another Dottie-style lookup on the modified hash
|
225
|
+
Dottie.get(hash, 'a.b') # => "x"
|
226
|
+
|
227
|
+
# and another standard lookup now that there's a literal "a.b" key
|
228
|
+
hash['a.b'] # => "z"
|
229
|
+
```
|
230
|
+
|
231
|
+
### Traversing Arrays
|
232
|
+
|
233
|
+
Array elements can be targeted with a bracketed index, which is a positive or negative integer or a `first` or `last` named index.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
me = Dottie({
|
237
|
+
'pets' => ['dog', 'cat', 'bird', 'fish']
|
238
|
+
})
|
239
|
+
me['pets[first]'] # => "dog"
|
240
|
+
me['pets[1]'] # => "cat"
|
241
|
+
me['pets[-2]'] # => "bird"
|
242
|
+
me['pets[last]'] # => "fish"
|
243
|
+
```
|
244
|
+
|
245
|
+
### Dottie Key Format
|
246
|
+
|
247
|
+
Dottie uses periods and brackets to delimit key parts. In general, hash keys are strings separated by periods, and array indexes are integers surrounded by brackets. For example, in the key `a.b[1]`, `a` and `b` are strings (hash keys) and `1` is a bracketed integer (an array index). Here's an example of how this key would be used:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
d = Dottie({
|
251
|
+
'a' => {
|
252
|
+
'b' => ['x', 'y', 'z']
|
253
|
+
}
|
254
|
+
})
|
255
|
+
d['a.b[1]'] # => "y"
|
256
|
+
```
|
257
|
+
|
258
|
+
For readability and convenience, `first` and `last`, when enclosed with brackets (such as `[first]`), are treated as named array indexes and are converted to `0` and `-1`, respectively.
|
259
|
+
|
260
|
+
Besides `first` and `last`, any other non-integer string is handled like a normal, dot-delimited string. For example, the key `a[b].c` is equivalent to `a.b.c`.
|
261
|
+
|
262
|
+
Brackets can also be used to quote key segments that contain periods. For example, `a[b.c].d` is interpreted internally as `['a', 'b.c', 'd']`. (If you need to see how a key will be interpreted, use `Dottie#key_parts` in an IRB session.)
|
263
|
+
|
264
|
+
Dottie is forgiving in its key parsing. Periods are optional around brackets. For example, the key `a[1]b[2]c` is the same as `a.[1].b.[2].c`. Ruby-like syntax is preferred, where a period follows each closing bracket but does not preceed any opening brackets. The preferred key in this case is `a[1].b[2].c`.
|
265
|
+
|
266
|
+
### What Dottie Doesn't Do
|
267
|
+
|
268
|
+
When mixing Dottie into a `Hash` or `Array`, or when wrapping one in a `Dottie::Freckle` (which passes unrecognized method calls on to the wrapped object), Dottie provides `[]`, `[]=`, `fetch`, and `has_key?` methods, with the latter two assuming you're working with a `Hash`.
|
269
|
+
|
270
|
+
By design, Dottie does not provide an implementation for `keys`, `each`, or other built-in `Hash` and `Array` methods where the expected behavior might be ambiguous. Dottie is meant to be an easy way to store and retrieve data in a JSON-like data structure (hashes and/or arrays nested within each other) and is not meant as a replacement for any core classes.
|
271
|
+
|
272
|
+
## FAQ
|
273
|
+
|
274
|
+
**Q:** Will Dottie make my life easier?
|
275
|
+
**A:** Probably.
|
276
|
+
|
277
|
+
**Q:** Will Dottie brush my teeth for me?
|
278
|
+
**A:** Probably not.
|
279
|
+
|
280
|
+
**Q:** Why is the wrapper class named `Freckle`?
|
281
|
+
**A:** Why not?
|
282
|
+
|
283
|
+
## Contributing
|
284
|
+
|
285
|
+
1. Fork it
|
286
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
287
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
288
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
289
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/dottie.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dottie/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dottie"
|
8
|
+
spec.version = Dottie::VERSION
|
9
|
+
spec.authors = ["Nick Pearson"]
|
10
|
+
spec.email = ["nick@banyantheory.com"]
|
11
|
+
spec.summary = %q{Deep Hash and Array access with dotted keys}
|
12
|
+
spec.description = %q{Deeply access nested Hash/Array data structures
|
13
|
+
without checking for the existence of every node
|
14
|
+
along the way.}
|
15
|
+
spec.homepage = "https://github.com/nickpearson/dottie"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|
data/lib/dottie.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'dottie/methods'
|
2
|
+
require 'dottie/freckle'
|
3
|
+
require 'dottie/helper'
|
4
|
+
require 'dottie/version'
|
5
|
+
require 'strscan'
|
6
|
+
|
7
|
+
module Dottie
|
8
|
+
|
9
|
+
##
|
10
|
+
# Creates a new Dottie::Freckle from a standard Ruby Hash or Array.
|
11
|
+
|
12
|
+
def self.[](obj)
|
13
|
+
Dottie::Freckle.new(obj)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Gets a value from an object. Does not assume the object has been extended
|
18
|
+
# with Dottie methods.
|
19
|
+
|
20
|
+
def self.get(obj, key)
|
21
|
+
Dottie.key_parts(key).each do |k|
|
22
|
+
obj = case obj
|
23
|
+
when Hash, Array
|
24
|
+
obj[k]
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
obj
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Sets a value in an object, creating missing nodes (Hashes and Arrays) as
|
34
|
+
# needed. Does not assume the object has been extended with Dottie methods.
|
35
|
+
|
36
|
+
def self.set(obj, key, value)
|
37
|
+
key_parts = Dottie.key_parts(key)
|
38
|
+
key_parts.each_with_index do |k, i|
|
39
|
+
# set the value if this is the last key part
|
40
|
+
if i == key_parts.size - 1
|
41
|
+
case obj
|
42
|
+
when Hash, Array
|
43
|
+
obj[k] = value
|
44
|
+
else
|
45
|
+
raise TypeError.new("expected Hash or Array but got #{obj.class.name}")
|
46
|
+
end
|
47
|
+
# otherwise, walk down the tree, creating missing nodes along the way
|
48
|
+
else
|
49
|
+
obj = case obj
|
50
|
+
when Hash, Array
|
51
|
+
# look ahead at the next key to see if an array should be created
|
52
|
+
if key_parts[i + 1].is_a?(Integer)
|
53
|
+
obj[k] ||= []
|
54
|
+
else
|
55
|
+
obj[k] ||= {}
|
56
|
+
end
|
57
|
+
when nil
|
58
|
+
# look at the key to see if an array should be created
|
59
|
+
case k
|
60
|
+
when Integer
|
61
|
+
obj[k] = []
|
62
|
+
else
|
63
|
+
obj[k] = {}
|
64
|
+
end
|
65
|
+
else
|
66
|
+
raise TypeError.new("expected Hash, Array, or nil but got #{obj.class.name}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# return the value that was set
|
71
|
+
value
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Checks whether a Hash or Array contains the last part of a Dottie-style key.
|
76
|
+
|
77
|
+
def self.has_key?(obj, key)
|
78
|
+
key_parts = Dottie.key_parts(key)
|
79
|
+
key_parts.each_with_index do |k, i|
|
80
|
+
# look for the key if this is the last key part
|
81
|
+
if i == key_parts.size - 1
|
82
|
+
if obj.is_a?(Array) && k.is_a?(Integer)
|
83
|
+
return obj.size > k
|
84
|
+
elsif obj.is_a?(Hash)
|
85
|
+
return obj.has_key?(k)
|
86
|
+
else
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
else
|
90
|
+
obj = case obj
|
91
|
+
when Hash, Array
|
92
|
+
obj[k]
|
93
|
+
else
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Mimics the behavior of Hash#fetch, raising an error if a key does not exist
|
102
|
+
# and no default value or block is provided.
|
103
|
+
|
104
|
+
def self.fetch(obj, key, default = :_fetch_default_)
|
105
|
+
if Dottie.has_key?(obj, key)
|
106
|
+
Dottie.get(obj, key)
|
107
|
+
elsif block_given?
|
108
|
+
yield(key)
|
109
|
+
elsif default != :_fetch_default_
|
110
|
+
default
|
111
|
+
else
|
112
|
+
raise KeyError.new(%{key not found: "#{key}"})
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Checks whether a key looks like a key Dottie understands.
|
118
|
+
|
119
|
+
def self.dottie_key?(key)
|
120
|
+
!!(key.is_a?(String) && key =~ /[.\[]/) || key.is_a?(Array)
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Parses a Dottie key into an Array of strings and integers.
|
125
|
+
|
126
|
+
def self.key_parts(key)
|
127
|
+
if key.is_a?(String)
|
128
|
+
parts = []
|
129
|
+
s = StringScanner.new(key)
|
130
|
+
loop do
|
131
|
+
if s.scan(/\./)
|
132
|
+
next
|
133
|
+
elsif (p = s.scan(/[^\[\].]+/))
|
134
|
+
parts << p
|
135
|
+
elsif (p = s.scan(/\[-?\d+\]/))
|
136
|
+
parts << p.scan(/-?\d+/).first.to_i
|
137
|
+
elsif (p = s.scan(/\[(first|last)\]/))
|
138
|
+
parts << (p[1..-2] == 'first' ? 0 : -1)
|
139
|
+
elsif (p = s.scan(/\[.+?\]/))
|
140
|
+
parts << p[1..-2] # remove '[' and ']'
|
141
|
+
else
|
142
|
+
break
|
143
|
+
end
|
144
|
+
end
|
145
|
+
parts
|
146
|
+
elsif key.is_a?(Array)
|
147
|
+
key
|
148
|
+
else
|
149
|
+
raise TypeError.new("expected String or Array but got #{key.class.name}")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|