rbpath 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 31a72727369862168d205513377ef7f776ba1657
4
+ data.tar.gz: 8a67e07605cd5e5ef79f273fe31779e2eca9d6e6
5
+ SHA512:
6
+ metadata.gz: 6be6653406797068a4afb74d5d4adad456fd98f576929c4c337149290288f079a54ad78ac3e7d567e776147abc2b22d8c5cfda107bcac4b9a6cd67f6fcb7d419
7
+ data.tar.gz: 100d805624f2ef1b0e24f4fc39361d3358ee1c77cc4c760726f701d407db3712f6a79af35847b7c303dd0942debc184aa5685093e02746fec3481228430d465b
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jrel.gemspec
4
+ gemspec
5
+
6
+ gem 'pry'
7
+ gem 'growl'
8
+ gem 'guard'
9
+ gem 'hirb'
10
+ gem 'guard-minitest'
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ notification :growl
5
+
6
+ guard :minitest do
7
+ watch(%r{^test/test_(.*)\.rb})
8
+ watch(%r{^lib/(.*/)?([^/]+)\.rb}) { |m| "test/test_#{m[2]}.rb" }
9
+ watch(%r{^test/test_helper\.rb}) { 'test' }
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alex Skryl
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,351 @@
1
+ # RbPath
2
+
3
+ RbPath is a small library for finding and retrieving data in large Ruby
4
+ collections (Arrays/Hashes) and object graphs, similar to XPath and CSS
5
+ selectors. You might use it over XPath or something similar because it's
6
+ super lightweight and may do exactly what you need without the complex
7
+ semantics of XPath or CSS selectors. It also makes operations such as regular
8
+ expression filtering much easier to use.
9
+
10
+
11
+ ## Table of contents
12
+
13
+ - [Installation](#installation)
14
+ - [Usage](#usage)
15
+ - [Queries](#queries)
16
+ - [Literals](#literals)
17
+ - [Wildcards](#wildcards)
18
+ - [Logic Expressions](#logic-expressions)
19
+ - [Regex Matching](#regex-matching)
20
+ - [Gotchas](#gotchas)
21
+ - [Working with JSON/YAML/XML](#working-with-jsonyamlxml)
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ gem 'rbpath'
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install rbpath
36
+
37
+ ## Usage
38
+
39
+ ### Direct
40
+
41
+ You can use the query engine directly through the Query class.
42
+
43
+ ```ruby
44
+ require 'rbpath'
45
+
46
+ h = {...}
47
+
48
+ RbPath::Query.new(...).query(h)
49
+ ```
50
+
51
+ ### Object Mixin
52
+
53
+ You can add the query interface to an existing instance of a Hash or Array.
54
+
55
+ ```ruby
56
+ require 'rbpath'
57
+
58
+ h = {...}
59
+ h.extend RbPath
60
+
61
+ h.query(...)
62
+ ```
63
+
64
+ ### Class Mixin
65
+
66
+ You can make your own objects queryable by using the RbPath mixin.
67
+
68
+ ```ruby
69
+ require 'rbpath'
70
+
71
+ class Person < Struct.new(:first, :middle, :last, :age, :relatives)
72
+ include RbPath
73
+
74
+ rbpath :first, :middle, :last, :age, :relatives
75
+ end
76
+
77
+ p = Person.new('john', 'michael', 'doe', 21, [relative1,...])
78
+
79
+ p.query(...)
80
+ ```
81
+
82
+ Notice that the rbpath attributes must be explicitly listed.
83
+
84
+ ## Queries
85
+
86
+ Queries are similar to XPath expressions. They are used to navigate and find
87
+ information in tree-like data structures.
88
+
89
+ ```ruby
90
+ class Employee < Struct.new(:first, :last, :position)
91
+ include RbPath
92
+ rbpath :first, :last, :position
93
+ end
94
+
95
+ data = {
96
+ illinois: {
97
+ chicago: {
98
+ inventory: {
99
+ bakery: { white: 220, whole_wheat: 150, multigrain: 72, rye: 27 },
100
+ fish: { salmon: 110, tuna: 115, flounder: 22, catfish: 90, cod: 15 },
101
+ meat: { ribeye: 23, pork_chop: 19, pork_loin: 12, beef_brisket: 30 }},
102
+ employees: [
103
+ Employee.new("John", "Sansk", "General Manager"),
104
+ Employee.new("Gene", "Pollack", "Warehouse Manager"),
105
+ Employee.new("Luke", "Sanders", "Director")],
106
+ address: '101 Big St',
107
+ services: [:pharmacy, :bakery, :groceries, :kids_corner, :pet_grooming]
108
+ },
109
+ springfield: {
110
+ inventory: {
111
+ fish: { salmon: 101, trout: 97, snapper: 172, catfish: 17, cod: 93 },
112
+ meat: { ribeye: 13, chuck_roast: 82, flank_steak: 73, beef_brisket: 30 }},
113
+ employees: [
114
+ Employee.new("Kerry", "Adams", "General Manager"),
115
+ Employee.new("Sherry", "Nerst", "Warehouse Manager"),
116
+ Employee.new("Kate", "Holmes", "Director")],
117
+ address: '220 Small St',
118
+ services: [:groceries, :kids_corner]
119
+ }
120
+ }
121
+ }
122
+
123
+ data.extend RbPath
124
+ ```
125
+
126
+ The sample data above represents a chain of grocery stores and their employees.
127
+ We will see how RbPath can extract useful information from this set of data.
128
+
129
+
130
+ ### Literals
131
+
132
+ The result of a *query* call will always be a list values that satisfy it, or
133
+ an empty list if no matching values were found. There is also an analagous
134
+ *pquery* interface which, instead of returning the values themselves, will
135
+ return the paths to the values (or an empty list). Calling *path_values* on the
136
+ result of *pquery* is the same as calling *query* directly.
137
+
138
+ To make the examples more concise, only results from the *pquery* call will be
139
+ provided in later examples.
140
+
141
+ ```ruby
142
+ # Xpath: /illinois/chicago
143
+
144
+ > data.query("illinois chicago")
145
+ => [{inventory: {...}, employees: [...], address: "...", services: [...]}]
146
+
147
+ > data.pquery("illinois chicago")
148
+ => [['illinois','chicago']]
149
+
150
+ > data.path_values( data.pquery("illinois chicago") )
151
+ => [{inventory: {...}, employees: [...], address: "...", services: [...]}]
152
+
153
+ # Xpath: /california/san_francisco
154
+
155
+ > data.query("california san_francisco")
156
+ => []
157
+
158
+ > data.pquery("california san_francisco")
159
+ => []
160
+ ```
161
+
162
+ Notice that the elements are seperated by **spaces** instead of slashes, and rbpath
163
+ queries are **absolute** by default.
164
+
165
+ Because the access semantics for Ruby collections (Arrays vs Hashes vs Objects)
166
+ are inherently different, queries into Arrays will have numerical indices
167
+ while queries into Hashes and RbPath objects will usually have string
168
+ indices, much like in XPath.
169
+
170
+ ``` ruby
171
+ # Xpath: /illinois/chicago/services[1]
172
+
173
+ > data.pquery("illinois chicago services 0")
174
+ => [['illinois','chicago','services','0']]
175
+ ```
176
+
177
+ Results to absolute queries aren't very interesting though, since they only
178
+ return a single match. Other queries can return multiple matching paths.
179
+
180
+ ### Wildcards
181
+
182
+ The star in the query below represents a **wildcard** match. It allows us to
183
+ match more than one value at a particular depth in the tree.
184
+
185
+ ```ruby
186
+ # XPath: /illinois/*/employees
187
+
188
+ > data.pquery("illinois * employees")
189
+ => [['illinois','chicago','employees'],
190
+ ['illinois','springfield','employees']]
191
+ ```
192
+
193
+ Wildcards can also be span across multiple levels of the tree, in case you
194
+ don't know how deep your value lives. These **multi-level wildcards** will reach
195
+ across 0 or more depth levels.
196
+
197
+ ```ruby
198
+ # XPath: //illinois
199
+
200
+ > data.pquery("** illinois")
201
+ => [['illinois']]
202
+
203
+ # XPath: //employees
204
+
205
+ > data.pquery("** employees")
206
+ => [['illinois','employees'],
207
+ ['illinois','chicago','employees'],
208
+ ['illinois','springfield','employees']]
209
+
210
+ ```
211
+
212
+ Notice that paths of differnt lengths may be returned in the resulting set when
213
+ using multi-level wildcards.
214
+
215
+ ### Logic Expressions
216
+
217
+ In addiction to wildcards, there is another, more restrictive, way to match
218
+ several paths at once using **AND** and **NOR** expressions.
219
+
220
+ ```ruby
221
+ # XPath: /illinois/chicago/inventory/*/salmon | /illinois/chicago/inventory/*/pork_chop
222
+
223
+ > data.pquery("illinois chicago inventory * (salmon,pork_chop)")
224
+ => [['illinois','chicago','inventory','fish','salmon'],
225
+ ['illinois','chicago','inventory','meat','pork_chop']]
226
+ ```
227
+
228
+ The above query will select all the inventory paths in the chicago store that
229
+ match 'salmon' **AND** 'pork_chop' for any of the departments. We can also
230
+ achieve the opposite effect by using a **NOR** expression and specifying a
231
+ list of values to avoid matching.
232
+
233
+ ```ruby
234
+ # XPath: /illinois/chicago/inventory/fish[not(contains(salmon)) and not(contains(tuna))]
235
+
236
+ > data.pquery("illinois chicago inventory fish [salmon,tuna]")
237
+ => [['illinois','chicago','inventory','fish','flounder'],
238
+ ['illinois','chicago','inventory','fish','catfish'],
239
+ ['illinois','chicago','inventory','fish','cod']]
240
+ ```
241
+
242
+ This query gives us all the fish in the chicago store which **do not match**
243
+ 'salmon' or 'tuna'.
244
+
245
+ ### Regex Matching
246
+
247
+ It's not always enough to be able to filter on an exact field/key name.
248
+ Sometimes you only know part of the name or you want to match all the names
249
+ that match a certain pattern. This is where regular expressions come in handy.
250
+
251
+ You can include plain old ruby regexes in your quries by splitting the query up
252
+ into multiple arguments. In this case the query will will still be processed as
253
+ though it was continuous.
254
+
255
+ ```ruby
256
+ > data.pquery("illinois chicago inventory meat", /(pork.*|beef.*)/)
257
+ => [['illinois','chicago','inventory','meat','pork_chop'],
258
+ ['illinois','chicago','inventory','meat','pork_loin'],
259
+ ['illinois','chicago','inventory','meat','beef_brisket']]
260
+
261
+ > data.pquery("illinois", /(chi.*|spr.*)/, "inventory *")
262
+ => [['illinois','chicago','inventory','bakery'],
263
+ ['illinois','chicago','inventory','fish'],
264
+ ['illinois','chicago','inventory','meat'],
265
+ ['illinois','springfield','inventory','fish'],
266
+ ['illinois','springfield','inventory','meat']]
267
+ ```
268
+
269
+ XPath 1.0 doesn't actually support regular expressions, but it does provide some
270
+ [specialized functions](http://www.w3.org/TR/xpath/#function-starts-with) for
271
+ partially matching element names such as *starts-with()* and *contains()*.
272
+
273
+ ### Gotchas
274
+
275
+ Sometimes you may need to find strings with spaces or things which are not
276
+ strings at all. Here is how you do that.
277
+
278
+ Single quote your string or use a regex matcher when your filter string
279
+ contains spaces.
280
+
281
+ ```ruby
282
+ > data.pquery("* * employees * position 'General Manager'")
283
+ => [['illinois','chicago','employees','0','position','General Manager'],
284
+ ['illinois','chicago','employees','0','position','General Manager']]
285
+
286
+ > data.pquery("* * employees 0 position", /General Manager/)
287
+ => [['illinois','chicago','employees','0','position','General Manager'],
288
+ ['illinois','chicago','employees','0','position','General Manager']]
289
+ ```
290
+
291
+ Split up queries that use non-String filters into multiple arguments, just like
292
+ we did with regular expressions.
293
+
294
+ ```ruby
295
+ > data.pquery("* * inventory * *", 30)
296
+ => [['illinois','chicago','inventory','meat','beef_brisket',30],
297
+ ['illinois','springfield','inventory','meat','beef_brisket',30]]
298
+ ```
299
+
300
+ ## Working With JSON/YAML/XML
301
+
302
+ You can use the **rq** command (which is installed with the gem) from your
303
+ shell to query JSON, YAML and XML files using the RbPath engine, but you
304
+ will not be able to match regular expressions or non-string values due to the
305
+ limitations of the query parser.
306
+
307
+ ```bash
308
+ Usage: rq [OPTIONS] QUERY
309
+ -f, --file [FILE] File to parse
310
+ -t, --type [TYPE] File format
311
+ -p, --paths Paths only
312
+ -h, --help Show usage
313
+ ```
314
+
315
+ - If you don't supply the *file* option then data will be read from STDIN.
316
+ - The *paths* option will mimic the *pquery* interface shown in the examples.
317
+
318
+ ```bash
319
+ # read from a file
320
+ $ rq -f data.json '** john **'
321
+
322
+ # read from STDIN
323
+ $ curl http://myservice.com/api/1.json | rq -t json '** john **'
324
+ ```
325
+
326
+ ### XML
327
+
328
+ The [xml-simple](http://rubygems.org/gems/xml-simple) gem is used to convert
329
+ xml files to Ruby hashes prior to processing, so it must be installed if you
330
+ want to query XML files.
331
+
332
+ ```bash
333
+ gem install xml-simple
334
+ ```
335
+
336
+ ### Pretty Printer
337
+
338
+ Installing the [hirb](http://rubygems.org/gems/hirb) gem will tabularize
339
+ certain types of output, making it much easier to read.
340
+
341
+ ```bash
342
+ gem install hirb
343
+ ```
344
+
345
+ ## Contributing
346
+
347
+ 1. Fork it
348
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
349
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
350
+ 4. Push to the branch (`git push origin my-new-feature`)
351
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO.md ADDED
@@ -0,0 +1,7 @@
1
+ # TODO
2
+ - explain leaf vs path value matching in readme
3
+ - paths should be returned with array indices as integers
4
+ - handle nil and false keys/values properly
5
+ - deep_stringify_all is too costly
6
+ - test all README examples
7
+ - add rq tests
data/bin/rq ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ GC.disable # short lived process doesn't need gc
4
+
5
+ lib = "#{File.expand_path(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)}/../../lib"
6
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
7
+
8
+ require 'optparse'
9
+ require 'rbpath'
10
+ require 'pp'
11
+
12
+ begin
13
+ require 'hirb'
14
+ rescue LoadError
15
+ end
16
+
17
+ options = {}
18
+ optparse = OptionParser.new do |opts|
19
+ opts.banner = "Usage: rq [OPTIONS] QUERY"
20
+
21
+ opts.on("-f", "--file [FILE]", "File to parse") { |opt| options[:file] = opt }
22
+ opts.on("-t", "--type [TYPE]", "File format") { |opt| options[:type] = opt }
23
+ opts.on("-p", "--paths", "Paths only") { |opt| options[:paths] = true}
24
+ opts.on("-h", "--help", "Show usage") { puts opts; exit }
25
+ end
26
+
27
+ begin
28
+ optparse.parse!(ARGV)
29
+ query = ARGV.shift.to_s
30
+ file, type = options.values_at(:file, :type)
31
+ content = (file && File.read(file)) || ARGF.read
32
+ extension = type || (file && File.extname(file)[1..-1]) || 'json'
33
+
34
+ raise OptionParser::MissingArgument unless query
35
+
36
+ data = \
37
+ case extension
38
+ when 'yml', 'yaml'
39
+ require 'yaml'
40
+ YAML.load(content)
41
+ when 'jsn', 'json'
42
+ require 'json'
43
+ JSON.parse(content)
44
+ when 'xml'
45
+ require 'xmlsimple'
46
+ XmlSimple.xml_in(content, 'ForceArray' => false)
47
+ end
48
+
49
+ rescue LoadError
50
+ puts "You are probably missing the 'xml-simple' gem, install it by running 'gem install xml-simple' and try again."
51
+ exit
52
+ rescue OptionParser::MissingArgument
53
+ puts optparse.help
54
+ exit
55
+ rescue Exception => e
56
+ puts "Error: #{e}"
57
+ exit
58
+ end
59
+
60
+ result = RbPath::Query.new(query).send(options[:paths] ? :pquery : :query, data)
61
+
62
+ if options[:paths]
63
+ defined?(Hirb)
64
+ puts(Hirb::Helpers::AutoTable.render(result))
65
+ else puts result.map(&:pretty_inspect)
66
+ end
data/lib/rbpath.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "rbpath/version"
2
+
3
+ module RbPath
4
+ autoload :Query, 'rbpath/query'
5
+ autoload :Utils, 'rbpath/utils'
6
+ autoload :ObjectMixin, 'rbpath/object_mixin'
7
+ autoload :ClassMixin, 'rbpath/class_mixin'
8
+
9
+ def self.included(klass)
10
+ klass.send(:include, RbPath::ObjectMixin)
11
+ klass.extend RbPath::ClassMixin
12
+ end
13
+
14
+ def self.extended(obj)
15
+ obj.singleton_class.send(:include, RbPath::ObjectMixin)
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module RbPath::ClassMixin
2
+
3
+ def rbpath(*fields)
4
+ @_rbpath_fields_ = fields.map(&:to_s)
5
+ end
6
+
7
+ def rbpath_fields
8
+ @_rbpath_fields_
9
+ end
10
+
11
+ end
@@ -0,0 +1,26 @@
1
+ module RbPath::ObjectMixin
2
+
3
+ def query(*query)
4
+ RbPath::Query.new(*query).query(self)
5
+ end
6
+
7
+ def pquery(*query)
8
+ RbPath::Query.new(*query).pquery(self)
9
+ end
10
+
11
+ def path_values(paths)
12
+ RbPath::Query.new(*query).values_at(self, paths)
13
+ end
14
+
15
+ # The object's class may not have the ClassMixin if a singleton object
16
+ # was extended:
17
+ #
18
+ # h = { a: 1, b: 2}
19
+ # h.extend RbPath
20
+ #
21
+ def rbpath_fields
22
+ self.class.respond_to?(:rbpath_fields) ?
23
+ self.class.rbpath_fields : nil
24
+ end
25
+
26
+ end
@@ -0,0 +1,105 @@
1
+ class RbPath::Query
2
+ include RbPath::Utils
3
+
4
+ # takes a string query or a pre-parsed query list
5
+ #
6
+ def initialize(*query)
7
+ @query = parse_query_list(query)
8
+ end
9
+
10
+ # Parsing rules:
11
+ # - query keys are seperated by spaces, keys with spaces must be single quoted
12
+ # - brackets group keys into an NOR group
13
+ # - parens group keys into a OR group
14
+ # - valid keys names consist of [chars|nums|spaces|-|_|.], anything else can
15
+ # be used as a seperator inside the parens/brackets
16
+ #
17
+ def parse_string_query(query)
18
+ query.scan(/(\([^\)]+\)|\[[^\]]+\]|'[^']+'|[^\s]+)/)
19
+ .flatten
20
+ .map { |keys| { multi: /\*\*/ === keys[0..1],
21
+ neg: /[\[\*]/ === keys[0],
22
+ keys: keys.scan(/[\w\d\s\-\_\.]+/) }}
23
+ end
24
+
25
+ def parse_query_list(query)
26
+ query.flat_map do |part|
27
+ case part
28
+ when String, Symbol
29
+ parse_string_query(part.to_s)
30
+ when Regexp
31
+ {multi: false, neg: false, keys: [], regexp: part}
32
+ else {multi: false, neg: false, keys: [part]}
33
+ end
34
+ end
35
+ end
36
+
37
+ def query(data)
38
+ data = deep_stringify_all(data)
39
+ do_query(data, @query, [[]]).map(&:flatten)
40
+ .map { |path| get_value(data, path) }
41
+ end
42
+
43
+ def pquery(data)
44
+ do_query(deep_stringify_all(data), @query, [[]]).map(&:flatten)
45
+ end
46
+
47
+ def values_at(data, paths)
48
+ paths.map {|path| get_value(deep_stringify_all(data), path) }
49
+ end
50
+
51
+ private
52
+
53
+ def do_query(data, query, valid_paths)
54
+ matcher, *rest = *query
55
+
56
+ if query.empty? || valid_paths.empty?
57
+ valid_paths
58
+ elsif matcher[:multi]
59
+ do_query(data, rest, valid_paths) +
60
+ do_query(data, query, match(data, matcher, valid_paths))
61
+ else
62
+ do_query(data, rest, match(data, matcher, valid_paths))
63
+ end
64
+ end
65
+
66
+ def match(data, matcher, valid_paths)
67
+ neg, keys, rgx = matcher.values_at(:neg, :keys, :regexp)
68
+
69
+ valid_paths.flat_map { |path| children = if rgx
70
+ all_keys(data, path).grep(rgx)
71
+ elsif neg
72
+ (all_keys(data, path) - keys)
73
+ else keys; end
74
+ [path].product(children).map(&:flatten) } \
75
+ .select { |path| get_value(data, path) }
76
+ end
77
+
78
+ def all_keys(data, path)
79
+ value = get_value(data, path)
80
+
81
+ case value
82
+ when Hash then value.keys
83
+ when Array then (0...value.size).map(&:to_s)
84
+ when RbPath
85
+ value.rbpath_fields
86
+ else [value]
87
+ end
88
+ end
89
+
90
+ def get_value(data, path)
91
+ return data if path.empty?
92
+ key, *rest = *path
93
+
94
+ case data
95
+ when Hash
96
+ get_value(data[key], rest)
97
+ when Array
98
+ get_value(data[Integer(key)], rest) rescue nil
99
+ when RbPath
100
+ get_value(data.send(key), rest) if data.respond_to?(key)
101
+ when key
102
+ rest.empty? ? data : nil
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,20 @@
1
+ module RbPath::Utils
2
+ def deep_transform_all(obj, &block)
3
+ case obj
4
+ when Hash
5
+ Hash[ obj.map { |k,v| [deep_transform_all(k, &block),
6
+ deep_transform_all(v, &block)] }]
7
+ when Array
8
+ obj.map { |i| deep_transform_all(i, &block) }
9
+ else block[obj]
10
+ end
11
+ end
12
+
13
+ def deep_stringify_all(obj)
14
+ deep_transform_all(obj) { |obj| obj.is_a?(Symbol) ? obj.to_s : obj }
15
+ end
16
+
17
+ def deep_symbolize_all(obj)
18
+ deep_transform_all(obj) { |obj| obj.is_a?(String) ? obj.to_sym : obj }
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module RbPath
2
+ VERSION = "0.2.3"
3
+ end
data/rbpath.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rbpath/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rbpath"
8
+ spec.version = RbPath::VERSION
9
+ spec.authors = ["Alex Skryl"]
10
+ spec.email = ["rut216@gmail.com"]
11
+ spec.description = %q{A lightweight library for running XPath like queries on Ruby collections and object graphs.}
12
+ spec.summary = %q{A lightweight library for running XPath like queries on Ruby collections and object graphs}
13
+ spec.homepage = "http://github.com/skryl/rbpath"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
File without changes
File without changes
@@ -0,0 +1,48 @@
1
+ require_relative '../test_helper'
2
+
3
+ module TestData
4
+ class Employee < Struct.new(:first, :last, :position)
5
+ include RbPath
6
+ rbpath :first, :last, :position
7
+ end
8
+
9
+ STORE_DATA =
10
+ {
11
+ illinois: {
12
+ employees: [
13
+ Employee.new("Kerry", "Adams", "District Manager")],
14
+ chicago: {
15
+ inventory: {
16
+ apples: { granny_smith: 150, gala: 200, cameo: 150, honeycrisp: 75 },
17
+ bread: { white: 220, whole_wheat: 150, multigrain: 72, rye: 27 },
18
+ fish: { salmon: 110, tuna: 115, flounder: 22, catfish: 90, cod: 15 },
19
+ meat: { ribeye: 23, pork_chop: 19, pork_loin: 12, beef_brisket: 30 },
20
+ nuts: { brazil: 200, pecan: 173, almond: 37, cashew: 12, chestnut: 70 },
21
+ shrimp: { arctic: 120, fresh_water: 20, atlantic: 72 } },
22
+ employees: [
23
+ Employee.new("John", "Sansk", "General Manager"),
24
+ Employee.new("Sam", "Bogert", "Checkout Manager"),
25
+ Employee.new("Gene", "Pollack", "Warehouse Manager"),
26
+ Employee.new("Shane", "Leson", "Human Resources") ],
27
+ address: '101 Big St',
28
+ services: [:pharmacy, :groceries, :salon, :kids_corner, :pet_grooming]
29
+ },
30
+ springfield: {
31
+ inventory: {
32
+ apples: { golden_delicious: 220, fuji: 110, cameo: 101, honeycrisp: 75 },
33
+ bread: { white: 220, whole_wheat: 150, multigrain: 72, rye: 27 },
34
+ fish: { salmon: 101, trout: 97, snapper: 172, catfish: 17, cod: 93 },
35
+ meat: { ribeye: 13, chuck_roast: 82, flank_steak: 73, beef_brisket: 30 },
36
+ nuts: { mixed: 211, pistachio: 75, almond: 370, cashew: 121, trail_mix: 92},
37
+ shrimp: { uncooked: 252, fresh_water: 72, atlantic: 93 } },
38
+ employees: [
39
+ Employee.new("Kerry", "Adams", "General Manager"),
40
+ Employee.new("Jack", "Lenere", "Checkout Manager"),
41
+ Employee.new("Sherry", "Nerst", "Warehouse Manager"),
42
+ Employee.new("Ken", "Beson", "Human Resources") ],
43
+ address: '220 Small St',
44
+ services: [:groceries, :kids_corner]
45
+ }
46
+ }
47
+ }
48
+ end
@@ -0,0 +1,57 @@
1
+ module MiniTest::Assertions
2
+
3
+ class MatchArray
4
+ def initialize(expected, actual)
5
+ @expected = expected
6
+ @actual = actual
7
+ end
8
+
9
+ def match()
10
+ return result, message
11
+ end
12
+
13
+ def result()
14
+ return false unless @actual.respond_to? :to_ary
15
+ @extra_items = difference_between_arrays(@actual, @expected)
16
+ @missing_items = difference_between_arrays(@expected, @actual)
17
+ @extra_items.empty? & @missing_items.empty?
18
+ end
19
+
20
+ def message()
21
+ if @actual.respond_to? :to_ary
22
+ message = "expected collection contained: #{safe_sort(@expected).inspect}\n"
23
+ message += "actual collection contained: #{safe_sort(@actual).inspect}\n"
24
+ message += "the missing elements were: #{safe_sort(@missing_items).inspect}\n" unless @missing_items.empty?
25
+ message += "the extra elements were: #{safe_sort(@extra_items).inspect}\n" unless @extra_items.empty?
26
+ else
27
+ message = "expected an array, actual collection was #{@actual.inspect}"
28
+ end
29
+
30
+ message
31
+ end
32
+
33
+ private
34
+
35
+ def safe_sort(array)
36
+ array.sort rescue array
37
+ end
38
+
39
+ def difference_between_arrays(array_1, array_2)
40
+ difference = array_1.to_ary.dup
41
+ array_2.to_ary.each do |element|
42
+ if index = difference.index(element)
43
+ difference.delete_at(index)
44
+ end
45
+ end
46
+ difference
47
+ end
48
+ end # MatchArray
49
+
50
+ def assert_match_array(expected, actual)
51
+ result, message = MatchArray.new(expected, actual).match
52
+ assert result, message
53
+ end
54
+
55
+ end # MiniTest::Assertions
56
+
57
+ Array.infect_an_assertion :assert_match_array, :must_match_array
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ Bundler.require(:default)
4
+
5
+ require 'json'
6
+ require 'yaml'
7
+ require 'set'
8
+ require 'minitest/autorun'
9
+ require 'minitest/pride'
10
+
11
+ # extend minitest to support out of order array matches
12
+ #
13
+ require_relative 'extensions/match_array'
@@ -0,0 +1,360 @@
1
+ require_relative 'test_helper'
2
+ require_relative 'data/test_data'
3
+
4
+ describe RbPath::Query do
5
+ before do
6
+ @store_data = TestData::STORE_DATA
7
+ end
8
+
9
+ describe 'query parsing' do
10
+
11
+ describe "single string queries" do
12
+
13
+ it 'should identify a single key' do
14
+ RbPath::Query.new("some_key").instance_variable_get('@query').must_equal \
15
+ [{multi: false, neg: false, keys: ['some_key']}]
16
+ end
17
+
18
+ it 'should identify a list of keys' do
19
+ RbPath::Query.new("one two three").instance_variable_get('@query').must_equal \
20
+ [ {multi: false, neg: false, keys: ['one']},
21
+ {multi: false, neg: false, keys: ['two']},
22
+ {multi: false, neg: false, keys: ['three']} ]
23
+ end
24
+
25
+ it 'should identify list of keys with quoted spaces' do
26
+ RbPath::Query.new("one 'two and a half' three").instance_variable_get('@query').must_equal \
27
+ [ {multi: false, neg: false, keys: ['one']},
28
+ {multi: false, neg: false, keys: ['two and a half']},
29
+ {multi: false, neg: false, keys: ['three']} ]
30
+ end
31
+
32
+ it 'should identify ORed keys in a list' do
33
+ RbPath::Query.new("one (two,three,four) ('twenty two','forty three')").instance_variable_get('@query').must_equal \
34
+ [ {multi: false, neg: false, keys: ['one']},
35
+ {multi: false, neg: false, keys: ['two','three','four']},
36
+ {multi: false, neg: false, keys: ['twenty two', 'forty three']} ]
37
+ end
38
+
39
+ it 'should identify NORed keys in a list' do
40
+ RbPath::Query.new("one [two,three,four] ['fifty five','sixty six']").instance_variable_get('@query').must_equal \
41
+ [ {multi: false, neg: false, keys: ['one']},
42
+ {multi: false, neg: true, keys: ['two','three','four']},
43
+ {multi: false, neg: true, keys: ['fifty five', 'sixty six']} ]
44
+ end
45
+
46
+ it 'should identify splats' do
47
+ RbPath::Query.new("one [] (two,three) * four").instance_variable_get('@query').must_equal \
48
+ [ {multi: false, neg: false, keys: ['one']},
49
+ {multi: false, neg: true, keys: []},
50
+ {multi: false, neg: false, keys: ['two', 'three']},
51
+ {multi: false, neg: true, keys: []},
52
+ {multi: false, neg: false, keys: ['four']} ]
53
+ end
54
+
55
+ it 'should identify multi-splats' do
56
+ RbPath::Query.new("one [] (two,three) ** four").instance_variable_get('@query').must_equal \
57
+ [ {multi: false, neg: false, keys: ['one']},
58
+ {multi: false, neg: true, keys: []},
59
+ {multi: false, neg: false, keys: ['two', 'three']},
60
+ {multi: true, neg: true, keys: []},
61
+ {multi: false, neg: false, keys: ['four']} ]
62
+ end
63
+
64
+ it 'should just work' do
65
+ RbPath::Query.new("one [] ('twenty two',three) * () four [five,'sixty five'] 'seventy two' *").instance_variable_get('@query').must_equal \
66
+ [ {multi: false, neg: false, keys: ['one']},
67
+ {multi: false, neg: true, keys: []},
68
+ {multi: false, neg: false, keys: ['twenty two', 'three']},
69
+ {multi: false, neg: true, keys: []},
70
+ {multi: false, neg: false, keys: []},
71
+ {multi: false, neg: false, keys: ['four']},
72
+ {multi: false, neg: true, keys: ['five', 'sixty five']},
73
+ {multi: false, neg: false, keys: ['seventy two']},
74
+ {multi: false, neg: true, keys: []} ]
75
+ end
76
+ end
77
+
78
+ describe "multipart queries" do
79
+
80
+ it 'should accept a multipart query and convert symbols to strings' do
81
+ RbPath::Query.new(:one, 'two', :three).instance_variable_get('@query').must_equal \
82
+ [ {multi: false, neg: false, keys: ['one']},
83
+ {multi: false, neg: false, keys: ['two']},
84
+ {multi: false, neg: false, keys: ['three']} ]
85
+ end
86
+
87
+ it 'should not convert other objects to strings in a multipart query' do
88
+ RbPath::Query.new(:one, 2, 'three', nil).instance_variable_get('@query').must_equal \
89
+ [ {multi: false, neg: false, keys: ['one']},
90
+ {multi: false, neg: false, keys: [2]},
91
+ {multi: false, neg: false, keys: ['three']},
92
+ {multi: false, neg: false, keys: [nil]} ]
93
+ end
94
+
95
+ it 'should seperately parse all parts of a multipart query' do
96
+ RbPath::Query.new("one (two,three)", :four, "[five,six] seven", "'eighty nine'").instance_variable_get('@query').must_equal \
97
+ [ {multi: false, neg: false, keys: ['one']},
98
+ {multi: false, neg: false, keys: ['two','three']},
99
+ {multi: false, neg: false, keys: ['four']},
100
+ {multi: false, neg: true, keys: ['five','six']},
101
+ {multi: false, neg: false, keys: ['seven']},
102
+ {multi: false, neg: false, keys: ['eighty nine']} ]
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'query engine' do
108
+
109
+ describe 'literal queries' do
110
+
111
+ it 'should work on hashes and their leaf values' do
112
+ RbPath::Query.new("illinois chicago inventory apples gala", 200).pquery(@store_data).must_match_array \
113
+ [['illinois', 'chicago', 'inventory', 'apples', 'gala', 200]]
114
+ end
115
+
116
+ it 'should work on arrays and their leaf values' do
117
+ RbPath::Query.new("illinois chicago services 0 pharmacy").pquery(@store_data).must_match_array \
118
+ [['illinois', 'chicago', 'services', '0', 'pharmacy']]
119
+ end
120
+
121
+ it 'should work on rbpath objects and their leaf values' do
122
+ RbPath::Query.new("illinois chicago employees 0 last Sansk").pquery(@store_data).must_match_array \
123
+ [['illinois', 'chicago', 'employees', '0', 'last', 'Sansk']]
124
+ end
125
+
126
+ # leaf values should not be included in the main query if they are not strings
127
+ #
128
+ it 'should not match stringified leaf values' do
129
+ RbPath::Query.new("illinois chicago inventory apples gala 200").pquery(@store_data).must_match_array []
130
+ end
131
+
132
+ end
133
+
134
+ describe 'splat queries' do
135
+
136
+ it 'should work on leaf values' do
137
+ RbPath::Query.new("illinois chicago inventory apples gala *").pquery(@store_data).must_match_array \
138
+ [['illinois', 'chicago', 'inventory', 'apples', 'gala', 200]]
139
+ end
140
+
141
+ it 'should work on hashes' do
142
+ RbPath::Query.new("* * *").pquery(@store_data).must_match_array \
143
+ [["illinois", "employees", "0"],
144
+ ["illinois", "chicago", "inventory"],
145
+ ["illinois", "chicago", "employees"],
146
+ ["illinois", "chicago", "address"],
147
+ ["illinois", "chicago", "services"],
148
+ ["illinois", "springfield", "inventory"],
149
+ ["illinois", "springfield", "employees"],
150
+ ["illinois", "springfield", "address"],
151
+ ["illinois", "springfield", "services"]]
152
+ end
153
+
154
+
155
+ it 'should work on arrays' do
156
+ RbPath::Query.new("* chicago services *").pquery(@store_data).must_match_array \
157
+ [["illinois", "chicago", "services", "0"],
158
+ ["illinois", "chicago", "services", "1"],
159
+ ["illinois", "chicago", "services", "2"],
160
+ ["illinois", "chicago", "services", "3"],
161
+ ["illinois", "chicago", "services", "4"]]
162
+ end
163
+
164
+ it 'should work on rbpath objects' do
165
+ RbPath::Query.new("* chicago employees 0 *").pquery(@store_data).must_match_array \
166
+ [["illinois", "chicago", "employees", "0", "first"],
167
+ ["illinois", "chicago", "employees", "0", "last"],
168
+ ["illinois", "chicago", "employees", "0", "position"]]
169
+ end
170
+
171
+ end
172
+
173
+ describe 'OR queries' do
174
+
175
+ it 'should work on hashes' do
176
+ RbPath::Query.new("illinois chicago (services,employees) *").pquery(@store_data).must_match_array \
177
+ [["illinois", "chicago", "services", "0"],
178
+ ["illinois", "chicago", "services", "1"],
179
+ ["illinois", "chicago", "services", "2"],
180
+ ["illinois", "chicago", "services", "3"],
181
+ ["illinois", "chicago", "services", "4"],
182
+ ["illinois", "chicago", "employees", "0"],
183
+ ["illinois", "chicago", "employees", "1"],
184
+ ["illinois", "chicago", "employees", "2"],
185
+ ["illinois", "chicago", "employees", "3"]]
186
+ end
187
+
188
+ it 'should work on arrays' do
189
+ RbPath::Query.new("illinois chicago services (0,1) *").pquery(@store_data).must_match_array \
190
+ [["illinois", "chicago", "services", "0", "pharmacy"],
191
+ ["illinois", "chicago", "services", "1", "groceries"]]
192
+ end
193
+
194
+ it 'should work on rbpath objects' do
195
+ RbPath::Query.new("illinois chicago employees * (first,last) *").pquery(@store_data).must_match_array \
196
+ [["illinois", "chicago", "employees", "0", "first", "John"],
197
+ ["illinois", "chicago", "employees", "0", "last", "Sansk"],
198
+ ["illinois", "chicago", "employees", "1", "first", "Sam"],
199
+ ["illinois", "chicago", "employees", "1", "last", "Bogert"],
200
+ ["illinois", "chicago", "employees", "2", "first", "Gene"],
201
+ ["illinois", "chicago", "employees", "2", "last", "Pollack"],
202
+ ["illinois", "chicago", "employees", "3", "first", "Shane"],
203
+ ["illinois", "chicago", "employees", "3", "last", "Leson"]]
204
+ end
205
+ end
206
+
207
+ describe 'NOR queries' do
208
+
209
+ it 'should work on hashes' do
210
+ RbPath::Query.new("illinois chicago [inventory,address] *").pquery(@store_data).must_match_array \
211
+ [["illinois", "chicago", "services", "0"],
212
+ ["illinois", "chicago", "services", "1"],
213
+ ["illinois", "chicago", "services", "2"],
214
+ ["illinois", "chicago", "services", "3"],
215
+ ["illinois", "chicago", "services", "4"],
216
+ ["illinois", "chicago", "employees", "0"],
217
+ ["illinois", "chicago", "employees", "1"],
218
+ ["illinois", "chicago", "employees", "2"],
219
+ ["illinois", "chicago", "employees", "3"]]
220
+ end
221
+
222
+ it 'should work on arrays' do
223
+ RbPath::Query.new("illinois chicago services [2,3,4] *").pquery(@store_data).must_match_array \
224
+ [["illinois", "chicago", "services", "0", "pharmacy"],
225
+ ["illinois", "chicago", "services", "1", "groceries"]]
226
+ end
227
+
228
+ it 'should work on rbpath objects' do
229
+ RbPath::Query.new("illinois chicago employees * [position] *").pquery(@store_data).must_match_array \
230
+ [["illinois", "chicago", "employees", "0", "first", "John"],
231
+ ["illinois", "chicago", "employees", "0", "last", "Sansk"],
232
+ ["illinois", "chicago", "employees", "1", "first", "Sam"],
233
+ ["illinois", "chicago", "employees", "1", "last", "Bogert"],
234
+ ["illinois", "chicago", "employees", "2", "first", "Gene"],
235
+ ["illinois", "chicago", "employees", "2", "last", "Pollack"],
236
+ ["illinois", "chicago", "employees", "3", "first", "Shane"],
237
+ ["illinois", "chicago", "employees", "3", "last", "Leson"]]
238
+ end
239
+ end
240
+
241
+ describe 'REGEX queries' do
242
+
243
+ it 'should work on hashes' do
244
+ RbPath::Query.new("illinois chicago inventory meat", /(pork.*|beef.*)/).pquery(@store_data).must_match_array \
245
+ [["illinois", "chicago", "inventory", "meat", "pork_chop"],
246
+ ["illinois", "chicago", "inventory", "meat", "pork_loin"],
247
+ ["illinois", "chicago", "inventory", "meat", "beef_brisket"]]
248
+ end
249
+
250
+ it 'should work on arrays' do
251
+ RbPath::Query.new("illinois chicago services", /[01]/, "*").pquery(@store_data).must_match_array \
252
+ [["illinois", "chicago", "services", "0", "pharmacy"],
253
+ ["illinois", "chicago", "services", "1", "groceries"]]
254
+ end
255
+
256
+ it 'should work on rbpath objects' do
257
+ RbPath::Query.new("illinois chicago employees *", /(first|last)/, /(Gene|Pollack)/).pquery(@store_data).must_match_array \
258
+ [["illinois", "chicago", "employees", "2", "first", "Gene"],
259
+ ["illinois", "chicago", "employees", "2", "last", "Pollack"]]
260
+ end
261
+ end
262
+
263
+ describe 'Multilevel wildcard queries' do
264
+ it 'should match no elements' do
265
+ RbPath::Query.new("** illinois").pquery(@store_data).must_match_array \
266
+ [["illinois"]]
267
+ end
268
+
269
+ it 'should match leaf nodes' do
270
+ RbPath::Query.new("illinois chicago services 0 pharmacy **").pquery(@store_data).must_match_array \
271
+ [['illinois','chicago','services','0','pharmacy']]
272
+ end
273
+
274
+ it 'should match with multiple multi-wildcards' do
275
+ RbPath::Query.new("** chicago ** pharmacy").pquery(@store_data).must_match_array \
276
+ [['illinois','chicago','services','0','pharmacy']]
277
+ end
278
+
279
+ it 'should work on hashes' do
280
+ RbPath::Query.new("**", /(pork.*|beef.*)/).pquery(@store_data).must_match_array \
281
+ [["illinois", "chicago", "inventory", "meat", "pork_chop"],
282
+ ["illinois", "chicago", "inventory", "meat", "pork_loin"],
283
+ ["illinois", "chicago", "inventory", "meat", "beef_brisket"],
284
+ ["illinois", "springfield", "inventory", "meat", "beef_brisket"]]
285
+ end
286
+
287
+ it 'should work on arrays' do
288
+ RbPath::Query.new("** services", /[01]/, "*").pquery(@store_data).must_match_array \
289
+ [["illinois", "chicago", "services", "0", "pharmacy"],
290
+ ["illinois", "chicago", "services", "1", "groceries"],
291
+ ["illinois", "springfield", "services", "0", "groceries"],
292
+ ["illinois", "springfield", "services", "1", "kids_corner"]]
293
+ end
294
+
295
+ it 'should work on rbpath objects' do
296
+ RbPath::Query.new("**", /(first|last)/, /(Gene|Pollack)/).pquery(@store_data).must_match_array \
297
+ [["illinois", "chicago", "employees", "2", "first", "Gene"],
298
+ ["illinois", "chicago", "employees", "2", "last", "Pollack"]]
299
+ end
300
+ end
301
+ end
302
+
303
+ describe 'classes which include the RbPath mixin' do
304
+
305
+ before do
306
+ @employee = TestData::Employee.new('Alex', 'Skryl', 'CEO')
307
+ @employee_class = TestData::Employee.dup
308
+ end
309
+
310
+ it 'should be rbpath' do
311
+ @employee.must_be_kind_of(RbPath)
312
+ end
313
+
314
+ it 'should return its rbpath fields' do
315
+ @employee.rbpath_fields.must_equal ['first', 'last', 'position']
316
+ @employee.class.rbpath_fields.must_equal ['first', 'last', 'position']
317
+ end
318
+
319
+ it 'should be able to set rbpath fields' do
320
+ @employee_class.class_eval do
321
+ rbpath :one, :two, :three
322
+ end
323
+ @employee_class.rbpath_fields.must_equal ['one', 'two', 'three']
324
+ @employee_class.new.rbpath_fields.must_equal ['one', 'two', 'three']
325
+ end
326
+
327
+ it 'should have instances that are able to query themselves' do
328
+ @employee.must_respond_to(:pquery)
329
+ @employee.pquery("first *").must_equal [['first', 'Alex']]
330
+ end
331
+
332
+ it 'should have instances that are able to fetch values' do
333
+ @employee.must_respond_to(:path_values)
334
+ @employee.path_values([['first','Alex']]).must_equal ['Alex']
335
+ end
336
+ end
337
+
338
+ describe 'singletons which extend the RbPath mixin' do
339
+
340
+ before do
341
+ @hash = {first: 'Alex', last: 'Skryl', age: '100', address: '101 blah st'}
342
+ @hash.extend RbPath
343
+ end
344
+
345
+ it 'should be able to query itself' do
346
+ @hash.must_respond_to(:query)
347
+ @hash.pquery("first *").must_equal [['first', 'Alex']]
348
+ end
349
+
350
+ it 'should have instances that are able to fetch values' do
351
+ @hash.must_respond_to(:path_values)
352
+ @hash.path_values([['first','Alex']]).must_equal ['Alex']
353
+ end
354
+
355
+ it 'should have no rbpath fields' do
356
+ @hash.rbpath_fields.must_equal nil
357
+ end
358
+ end
359
+
360
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbpath
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Alex Skryl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A lightweight library for running XPath like queries on Ruby collections
42
+ and object graphs.
43
+ email:
44
+ - rut216@gmail.com
45
+ executables:
46
+ - rq
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - .gitignore
51
+ - Gemfile
52
+ - Guardfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - TODO.md
57
+ - bin/rq
58
+ - lib/rbpath.rb
59
+ - lib/rbpath/class_mixin.rb
60
+ - lib/rbpath/object_mixin.rb
61
+ - lib/rbpath/query.rb
62
+ - lib/rbpath/utils.rb
63
+ - lib/rbpath/version.rb
64
+ - rbpath.gemspec
65
+ - test/data/data.json
66
+ - test/data/data.yaml
67
+ - test/data/test_data.rb
68
+ - test/extensions/match_array.rb
69
+ - test/test_helper.rb
70
+ - test/test_query.rb
71
+ homepage: http://github.com/skryl/rbpath
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.0.14
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: A lightweight library for running XPath like queries on Ruby collections
95
+ and object graphs
96
+ test_files:
97
+ - test/data/data.json
98
+ - test/data/data.yaml
99
+ - test/data/test_data.rb
100
+ - test/extensions/match_array.rb
101
+ - test/test_helper.rb
102
+ - test/test_query.rb