riffraff_jsonpath 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ README.markdown
2
+ lib/**/*.rb
3
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Bruce Williams
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,102 @@
1
+ # JSONPath
2
+
3
+ JSONPath support for Ruby.
4
+
5
+ For more information on JSONPath, see [Stefan Goessner's blog entry] [1], or
6
+ the [JS and PHP implementations] [2] on Google Code.
7
+
8
+ ## Installing
9
+
10
+ gem install bruce-jsonpath --source 'http://gems.github.com'
11
+
12
+ ### Dependencies
13
+
14
+ JSONPath uses [Treetop] [3] for parsing.
15
+
16
+ ## Completeness
17
+
18
+ As of 2009-07-17, this implementation passes all tests from the
19
+ [JS and PHP implementations] [2] (after modifying the script expressions
20
+ for Ruby) -- in addition to its own expanded test suite.
21
+
22
+ ## Usage
23
+
24
+ Execute JSONPath queries against a Ruby data
25
+ structure (as would be parsed from JSON using the `json` or `yajl` gems).
26
+
27
+ Only one method is needed:
28
+
29
+ JSONPath.lookup(hash_or_array, path)
30
+
31
+ ### Features
32
+
33
+ It supports hash traversal by key:
34
+
35
+ JSONPath.lookup({"a" => 1}, '$.a')
36
+ # => [1]
37
+ JSONPath.lookup({"foo" => {"bar baz" => 2}}, "$.foo['bar baz']")
38
+ # => [2]
39
+
40
+ Array traversal by index, including `start:stop:step` slices:
41
+
42
+ JSONPath.lookup([1, 2, [3, 4, 5], 6], '$[2][-2:]')
43
+ # => [4, 5]
44
+
45
+ Wildcards:
46
+
47
+ JSONPath.lookup({"a" => {"b" => 3, "c" => 2}}, "$.a.*")
48
+ # => [3, 2]
49
+
50
+ Descendant traversal (think `//` in XPath):
51
+
52
+ JSONPath.lookup({'e' => 1, 'b' => [{'e' => 3}]}, '$..e')
53
+ # => [1, 3]
54
+
55
+ Peek at the tests for more ideas.
56
+
57
+ #### Experimental Support
58
+
59
+ It has experimental support for JSONPath's script expressions, including
60
+ filters. Since JSONPath uses the underlying language in script expressions,
61
+ that means we have access to Ruby (supporting arbitrarily complex traversal).
62
+ As in other JSONPath implementations, `@` is replaced by the current node.
63
+
64
+ lists = [
65
+ [1, 2, 3, 4],
66
+ [5, 6],
67
+ [7, 8, 9, 10]
68
+ ]
69
+ JSONPath.lookup(lists, "$.*[(@.length - 1)]")
70
+ => [4, 6, 10]
71
+
72
+ And filters:
73
+
74
+ books = [
75
+ {"name" => 'Bruce', "age" => 29},
76
+ {"name" => "Braedyn", "age" => 3},
77
+ {"name" => "Jamis", "age" => 2},
78
+ ]
79
+ JSONPath.lookup(people, "$[?(@['age'] % 2 == 0)].name")
80
+ # => ['Jamis']
81
+
82
+ For more information, see the the [JSONPath introductory article] [1].
83
+
84
+ ## Contributing and Reporting Issues
85
+
86
+ The [project] [4] is hosted on [GitHub] [5], where I gladly accept pull
87
+ requests.
88
+
89
+ If you run into any problems, please either (in order of preference) post
90
+ something on the [issue tracker] [6], send me a message on GitHub, or email me.
91
+
92
+ ## Copyright
93
+
94
+ Copyright (c) 2009 Bruce Williams, based on work by Stefan Goessner.
95
+ See LICENSE.
96
+
97
+ [1]: http://goessner.net/articles/JsonPath/
98
+ [2]: http://code.google.com/p/jsonpath/
99
+ [3]: http://treetop.rubyforge.org/
100
+ [4]: http://github.com/bruce/jsonpath
101
+ [5]: http://github.com/
102
+ [6]: http://github.com/bruce/jsonpath/issues
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "riffraff_jsonpath"
8
+ gem.summary = %Q{JSONPath support for Ruby}
9
+ gem.description = %Q{The gem implements a simple parser+evaluator for JSONPath expressions in Ruby. It is a fork of bruce-jsonpath which only adds support for underscore in fields and works with warnings enabled.}
10
+ gem.email = "rff.rff@gmail.com"
11
+ gem.homepage = "http://github.com/riffraff/jsonpath"
12
+ gem.authors = ["Gabriele Renzi"]
13
+ gem.add_dependency 'treetop'
14
+ # gem is a Gem::Specification... see http:// www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/*_test.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/*_test.rb'
34
+ test.verbose = true
35
+ test.rcov_opts << '--exclude gems'
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ if File.exist?('VERSION.yml')
49
+ config = YAML.load(File.read('VERSION.yml'))
50
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
51
+ else
52
+ version = ""
53
+ end
54
+
55
+ rdoc.rdoc_dir = 'rdoc'
56
+ rdoc.title = "jsonpath #{version}"
57
+ rdoc.rdoc_files.include('README*')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
60
+
61
+ file 'lib/jsonpath/parser.rb' => 'lib/jsonpath/parser.treetop' do |t|
62
+ sh "tt 'lib/jsonpath/parser.treetop'"
63
+ end
64
+
65
+ task :treetop => 'lib/jsonpath/parser.rb'
66
+
67
+
68
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.8.2
data/jsonpath.gemspec ADDED
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{jsonpath}
8
+ s.version = "0.8.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Gabriele Renzi"]
12
+ s.date = %q{2010-01-25}
13
+ s.description = %q{The gem implements a simple parser+evaluator for JSONPath expressions in Ruby. It is a fork of bruce-jsonpath which only adds support for underscore in fields and works with warnings enabled.}
14
+ s.email = %q{rff.rff@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.markdown",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "jsonpath.gemspec",
27
+ "lib/jsonpath.rb",
28
+ "lib/jsonpath/nodes.rb",
29
+ "lib/jsonpath/parser.rb",
30
+ "lib/jsonpath/parser.treetop",
31
+ "test/parser_test.rb",
32
+ "test/reference_test.rb",
33
+ "test/test_helper.rb"
34
+ ]
35
+ s.homepage = %q{http://github.com/riffraff/jsonpath}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.5}
39
+ s.summary = %q{JSONPath support for Ruby}
40
+ s.test_files = [
41
+ "test/parser_test.rb",
42
+ "test/reference_test.rb",
43
+ "test/test_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<treetop>, [">= 0"])
52
+ else
53
+ s.add_dependency(%q<treetop>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<treetop>, [">= 0"])
57
+ end
58
+ end
59
+
data/lib/jsonpath.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'treetop'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ require 'jsonpath/parser'
6
+ require 'jsonpath/nodes'
7
+
8
+ module JSONPath
9
+
10
+ Parser = ::JSONPathGrammarParser
11
+ class ParseError < ::SyntaxError; end
12
+
13
+ def self.lookup(obj, path)
14
+ parser = Parser.new
15
+ if (result = parser.parse(path))
16
+ result.walk(obj)
17
+ else
18
+ raise ParseError, parser.failure_reason
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,213 @@
1
+ module JSONPath
2
+
3
+ module Nodes
4
+
5
+ class RootNode < Treetop::Runtime::SyntaxNode
6
+ def walk(object)
7
+ selectors.elements.inject([object]) do |reduce, selector|
8
+ selector.descend(*reduce)
9
+ end
10
+ end
11
+ end
12
+
13
+ class PathNode < Treetop::Runtime::SyntaxNode
14
+
15
+ def traversing_descendants?
16
+ respond_to?(:lower) && lower.text_value == '..'
17
+ end
18
+
19
+ def traverse(obj, &block)
20
+ if !respond_to?(:lower) || lower.text_value == '.'
21
+ obj.each(&block)
22
+ elsif lower.text_value == '..'
23
+ obj.each do |o|
24
+ recurse(o, &block)
25
+ end
26
+ end
27
+ end
28
+
29
+ def recurse(obj, &block)
30
+ block.call(obj)
31
+ children = case obj
32
+ when Hash
33
+ obj.values
34
+ when Array
35
+ obj
36
+ else
37
+ return
38
+ end
39
+ children.each do |child|
40
+ recurse(child, &block)
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ class WildcardNode < PathNode
47
+ def descend(*objects)
48
+ results = []
49
+ traverse(objects) do |obj|
50
+ values = case obj
51
+ when Hash
52
+ obj.values
53
+ when Array
54
+ obj
55
+ else
56
+ next
57
+ end
58
+ results.push(*values)
59
+ # Note: I really don't like this special case. This happens
60
+ # because when wildcarding regularly, the results are the *children*,
61
+ # but when using a .. descendant selector, you want the main parent,
62
+ # too. According to the JSONPath docs, '$..*' means "All members of
63
+ # [the] JSON structure." Should this support Array, as well?
64
+ if obj.is_a?(Hash) && traversing_descendants?
65
+ results.push(obj) unless results.include?(obj)
66
+ end
67
+ end
68
+ results
69
+ end
70
+ end
71
+
72
+ class KeyNode < PathNode
73
+
74
+ # supports finding the key from self or child elements
75
+ def find_keys(node=self, results=[], checked=[])
76
+ if node.respond_to?(:key)
77
+ results << node.key.text_value
78
+ end
79
+ if node.elements && !node.elements.empty?
80
+ node.elements.each do |element|
81
+ find_keys(element, results)
82
+ end
83
+ end
84
+ results
85
+ end
86
+
87
+ def descend(*objects)
88
+ results = []
89
+ keys = find_keys
90
+ traverse(objects) do |obj|
91
+ if obj.is_a?(Hash)
92
+ keys.each do |key|
93
+ if obj.key?(key)
94
+ results << obj[key]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ results
100
+ end
101
+ end
102
+
103
+ class IndexNode < PathNode
104
+ def descend(*objects)
105
+ offset = Integer(index.text_value)
106
+ results = []
107
+ traverse(objects) do |obj|
108
+ if obj.is_a?(Array)
109
+ if obj.size > offset
110
+ results << obj[offset]
111
+ end
112
+ end
113
+ end
114
+ results
115
+ end
116
+ end
117
+
118
+ class SliceNode < PathNode
119
+
120
+ def descend(*objects)
121
+ results = []
122
+ traverse(objects) do |obj|
123
+ if obj.is_a?(Array)
124
+ values = obj[start_offset..stop_offset(obj)]
125
+ 0.step(values.size - 1, step_size) do |n|
126
+ results << values[n]
127
+ end
128
+ end
129
+ end
130
+ results
131
+ end
132
+
133
+ def start_offset
134
+ @start_offset ||= Integer(start.text_value)
135
+ end
136
+
137
+ def stop_offset(obj)
138
+ @stop_offset ||= if respond_to?(:stop)
139
+ Integer(stop.text_value)
140
+ else
141
+ obj.size
142
+ end
143
+ end
144
+
145
+ def step_size
146
+ @step_size ||= if respond_to?(:step)
147
+ Integer(step.text_value)
148
+ else
149
+ 1
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ class CodeNode < PathNode
156
+
157
+ def code
158
+ @code ||= begin
159
+ text = template_code.text_value
160
+ text.gsub('@', '(obj)').gsub('\\(obj)', '@')
161
+ end
162
+ end
163
+
164
+ def execute(obj)
165
+ eval(code, binding)
166
+ end
167
+
168
+ end
169
+
170
+ class ExprNode < CodeNode
171
+
172
+ def descend(*objects)
173
+ results = []
174
+ traverse(objects) do |obj|
175
+ res = execute(obj)
176
+ case obj
177
+ when Hash
178
+ next unless obj.key?(res)
179
+ when Array
180
+ next unless obj.size > res
181
+ else
182
+ next
183
+ end
184
+ results << obj[res]
185
+ end
186
+ results
187
+ end
188
+
189
+ end
190
+
191
+ class FilterNode < CodeNode
192
+
193
+ class Error < ::ArgumentError; end
194
+
195
+ def descend(*objects)
196
+ results = []
197
+ traverse(objects) do |set|
198
+ next unless set.is_a?(Array) || set.is_a?(Hash)
199
+ values = set.is_a?(Array) ? set : set.values
200
+ values.each do |obj|
201
+ if execute(obj)
202
+ results << obj
203
+ end
204
+ end
205
+ end
206
+ results
207
+ end
208
+
209
+ end
210
+
211
+ end
212
+
213
+ end