hash-query 1.0.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.
@@ -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,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hquery.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mike Evans
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.
@@ -0,0 +1,29 @@
1
+ # Hquery
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'hquery'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install hquery
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hash_query/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Mike Evans']
6
+ gem.email = ['mike@urlgonomics.com']
7
+ gem.description = %q{Provides a css-like selector system for querying values out of deeply nested hashes}
8
+ gem.summary = %q{See above}
9
+ gem.homepage = 'https://github.com/mje113/hash-query'
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = 'hash-query'
15
+ gem.require_paths = ['lib']
16
+ gem.version = HashQuery::VERSION
17
+ end
@@ -0,0 +1,132 @@
1
+ require 'hash_query/version'
2
+ require 'pry'
3
+
4
+ module HashQuery
5
+
6
+ module Extension
7
+
8
+ # Public: Scans the hash data strucure looking for keys that match the
9
+ # psuedo css selector.
10
+ #
11
+ # selectors - String of pseudo css selectors to navigate hash structure
12
+ #
13
+ # Examples
14
+ #
15
+ # entity = {
16
+ # :a => 1,
17
+ # :b => {
18
+ # :c => {
19
+ # :d => {
20
+ # :e => 2,
21
+ # :f => 3
22
+ # }
23
+ # },
24
+ # :cc => [
25
+ # 3,
26
+ # {
27
+ # :g => 6,
28
+ # :h => 7,
29
+ # :i => [ 8, 9, 10, 11 ]
30
+ # },
31
+ # 4,
32
+ # 5
33
+ # ]
34
+ # }
35
+ # }.to_entity
36
+ # entity.query('a') # => 1
37
+ # entity.query('a b c d e') # => 2
38
+ # entity.query('c d e') # => 2
39
+ # entity.query('a>string')
40
+ # entity.query('a:string')
41
+ #
42
+ # Returns a value or collection of values that are found.
43
+ def query(selectors)
44
+ @_query ||= HashQuery::Query.new(self)
45
+ @_query.query(selectors)
46
+ end
47
+
48
+ end
49
+
50
+ class Query
51
+
52
+ def initialize(hash)
53
+ @hash = hash
54
+ end
55
+
56
+ def query(selectors)
57
+ selectors = Selector.parse(selectors)
58
+ found = descend(selectors, @hash, [])
59
+
60
+ if found.empty?
61
+ nil
62
+ elsif found.size == 1
63
+ found.first
64
+ else
65
+ found
66
+ end
67
+ end
68
+
69
+ def hash_y?(node)
70
+ node.class.ancestors.include?(Hash)
71
+ end
72
+
73
+ def array_y?(node)
74
+ node.class.ancestors.include?(Array)
75
+ end
76
+
77
+ def descend(selectors, node, found)
78
+ return if node.nil? || (!hash_y?(node) && !array_y?(node))
79
+ descend_array(selectors, node, found) if array_y?(node)
80
+ descend_hash(selectors, node, found) if hash_y?(node)
81
+ return found
82
+ end
83
+
84
+ def descend_array(selectors, node, found)
85
+ node.each do |a|
86
+ descend(selectors, a, found)
87
+ end
88
+ end
89
+
90
+ def descend_hash(selectors, node, found)
91
+ selectors = selectors.dup
92
+
93
+ if selectors.first.match?(node)
94
+ selector = selectors.shift
95
+ if selectors.size == 0
96
+ found << selector.match!(node)
97
+ else
98
+ descend(selectors, selector.match!(node), found)
99
+ end
100
+ else
101
+ node.select { |k, node| hash_y?(node) || array_y?(node) }.each do |k, node|
102
+ descend(selectors, node, found)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ class Selector
109
+
110
+ def self.parse(selectors)
111
+ selectors.split(/ /).map { |matcher| new(matcher) }
112
+ end
113
+
114
+ def initialize(matcher)
115
+ @key, @type = matcher.split(':')
116
+ @type = Object.module_eval("::#{@type.capitalize}", __FILE__, __LINE__) if @type
117
+ end
118
+
119
+ def match?(node)
120
+ node.has_key?(@key) && (!@type || node[@key].is_a?(@type))
121
+ end
122
+
123
+ def match!(node)
124
+ node[@key]
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ class Hash
131
+ include HashQuery::Extension
132
+ end
@@ -0,0 +1,3 @@
1
+ module HashQuery
2
+ VERSION = '1.0.1'
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+ require 'hash_query'
3
+
4
+
@@ -0,0 +1,126 @@
1
+ require 'helper'
2
+
3
+ class TestHquery < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @movie = {
7
+ 'title' => 'Goodfellas',
8
+ 'rating' => 'R',
9
+ 'encodings' => ['a', 'b', 'c'],
10
+ 'media' => [
11
+ {
12
+ 'type' => 'hd',
13
+ 'encoding' => 'h264'
14
+ },
15
+ {
16
+ 'type' => 'sd',
17
+ 'encoding' => 'mpeg2'
18
+ }
19
+ ],
20
+ 'actors' => [
21
+ {
22
+ 'name' => 'Robert De Niro',
23
+ 'role' => 'James Conway',
24
+ 'awards' => {
25
+ 'academy' => 2,
26
+ 'emmies' => 5
27
+ }
28
+ },
29
+ {
30
+ 'name' => 'Ray Liotta',
31
+ 'role' => 'Henry Hill'
32
+ }
33
+ ],
34
+ 'director' => {
35
+ 'name' => 'Martin Scorsese',
36
+ 'award_count' => 23
37
+ }
38
+ }
39
+
40
+ @hquery = {
41
+ 'a' => 1,
42
+ 'b' => {
43
+ 'c' => {
44
+ 'd' => {
45
+ 'e' => 2,
46
+ 'f' => 3
47
+ }
48
+ },
49
+ 'cc' => [
50
+ 3,
51
+ {
52
+ 'g' => 6,
53
+ 'h' => 7,
54
+ 'i' => [ 8, 9, 10, 11 ]
55
+ },
56
+ 4,
57
+ 5
58
+ ]
59
+ }
60
+ }
61
+ end
62
+
63
+ def test_included_in_hash
64
+ h = {}
65
+ assert h.respond_to?(:query)
66
+ assert h.respond_to?(:query)
67
+ end
68
+
69
+ def test_finds_nothing
70
+ assert_nil @hquery.query('z')
71
+ end
72
+
73
+ def test_finding_deep_array_value
74
+ assert_equal [8,9,10,11], @hquery.query('i')
75
+ end
76
+
77
+ def test_can_be_querried_shallow
78
+ assert_equal 'Goodfellas', @movie.query('title')
79
+ assert_equal 'R', @movie.query('rating')
80
+ end
81
+
82
+ def test_can_be_querried_one_level_deep
83
+ assert_equal 'Martin Scorsese', @movie.query('director name')
84
+ end
85
+
86
+ def test_can_be_querried_shallow_for_a_deep_value
87
+ assert_equal 23, @movie.query('award_count')
88
+ end
89
+
90
+ def test_can_be_querried_two_levels_deep
91
+ assert_equal 2, @movie.query('actors awards academy')
92
+ assert_equal 5, @movie.query('actors awards emmies')
93
+ end
94
+
95
+ def test_can_be_querried_to_find_an_array
96
+ assert_equal ['a', 'b', 'c'], @movie.query('encodings')
97
+ end
98
+
99
+ def test_can_be_querried_for_multiple_values
100
+ assert_equal ['Robert De Niro', 'Ray Liotta'], @movie.query('actors name')
101
+ end
102
+
103
+ def test_can_query_for_type
104
+ assert_equal 'Goodfellas', @movie.query('title:string')
105
+ end
106
+
107
+ def test_can_deep_query_for_type
108
+ assert_equal 2, @movie.query('academy:fixnum')
109
+ assert_equal 2, @movie.query('actors awards academy:fixnum')
110
+ end
111
+
112
+ def test_will_return_nil_if_type_is_not_matched
113
+ assert_equal nil, @movie.query('title:fixnum')
114
+ end
115
+
116
+ def test_can_query_for_complex_type
117
+ assert_equal ['a', 'b', 'c'], @movie.query('encodings:array')
118
+ end
119
+
120
+ def test_raises_exception_if_type_not_found
121
+ assert_raises NameError do
122
+ @movie.query('title:bogus')
123
+ end
124
+ end
125
+
126
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash-query
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mike Evans
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-30 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides a css-like selector system for querying values out of deeply
15
+ nested hashes
16
+ email:
17
+ - mike@urlgonomics.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - LICENSE
25
+ - README.md
26
+ - Rakefile
27
+ - hash_query.gemspec
28
+ - lib/hash_query.rb
29
+ - lib/hash_query/version.rb
30
+ - test/helper.rb
31
+ - test/test_hash_query.rb
32
+ homepage: https://github.com/mje113/hash-query
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project:
52
+ rubygems_version: 1.8.17
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: See above
56
+ test_files:
57
+ - test/helper.rb
58
+ - test/test_hash_query.rb