query_syntax 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3080165576250038bec4e48ed2cf0da017b81fc3
4
+ data.tar.gz: 7bcff375364147ea3669ed5b66273ade248cd0a8
5
+ SHA512:
6
+ metadata.gz: 530277fd07a24d1e15b9b532fb92b8eaac72a36d5329b698064dbe78015f71f0d989bef1402a4b08e1dea7196bd9f6945e7a3bf41581b606b2e0326f162549d4
7
+ data.tar.gz: 41e176ca044ab28dd65d5d17cb38ff5eb4ab867f23bd9970b48e52d846684c95b72f5cf67a2ec2c5918c80e7ddceec52d9a6d1005becad8d273c2131e4fb0a62
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in query_syntax.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jonathan Serafini
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,110 @@
1
+ # QuerySyntax
2
+
3
+ Provides chainable objects to build Chef search queries as well as a method to share, re-use and extend queries.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'query_syntax'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install query_syntax
18
+
19
+ ## Usage
20
+
21
+ QuerySyntax::Query provides bang and non-bang methods for and, or, not, where which determine whether we are modifying the current object or whether we are returning a new object. In most cases though, QuerySyntax will return the QuerySyntax object so that we can chain at will.
22
+
23
+ The following methods are most likely to be used :
24
+
25
+ `.where!(hash)`
26
+ An AND-ed hash of criteria to search for within a single (scope). If multiple values are provided for a single key from subsequent where! or where calls, then the value is converted to an array of values which are OR-ed.
27
+
28
+ Also notice that, as long as we chain where!, we are contribution criteria to the same scope.
29
+
30
+ ```
31
+ Query.where!(a:1).to_s
32
+ > a:1
33
+
34
+ Query.where!(a:1, b:1).to_s
35
+ > (a:1 AND b:1)
36
+
37
+ Query.where!(a:1, b:1).where!(a:2,b:2).to_s
38
+ > ((a:1 OR a:2) AND (b:1 OR b:2))
39
+ ```
40
+
41
+ `.and!(hash)`
42
+ Very similar to where!, we add additional criteria which we AND to the query. Unlike where!, however, and! generates a new (scope).
43
+
44
+ If and! is specified without a hash, we decide that the entire left most portion of the statement is to be made it's own composite scope.
45
+
46
+ ```
47
+ Query.where!(a:1, b:1).where!(a:2,b:2).to_s
48
+ > ((a:1 OR a:2) AND (b:1 OR b:2))
49
+
50
+ Query.where!(a:0,b:1).where!(a:2,b:2).and!(a:2,b:2).to_s
51
+ > ((a:0 OR a:2) AND (b:1 OR b:2)) AND (a:2 AND b:2)
52
+ ```
53
+
54
+ `.or!(hash) and .not!(hash)` function exactly like .and, however with a different operator than AND.
55
+
56
+ `.push(object)` may be used to begin with a baseline and build on top of it.
57
+
58
+ # Examples
59
+ ```
60
+ # Create a baseline query
61
+ Chef::Search.add_query("base")
62
+ Chef::Search.base.
63
+ where!(chef_environment:"production").to_s
64
+ > chef_environment:production
65
+
66
+ # Create peers query
67
+ Chef::Search.add_query("peers")
68
+ Chef::Search.peers.push(Chef::Search.base).
69
+ where!(roles:"load_balancer").to_s
70
+ > chef_environment:production AND roles:load_balancer
71
+
72
+ # Update the baseline
73
+ Chef::Search.base.
74
+ not!(tags:"maintenance").to_s
75
+ > chef_environment:production NOT tags:maintenance
76
+
77
+ Chef::Search.peers.to_s
78
+ > (chef_environment:production NOT tags:maintenance) AND roles:load_balancer
79
+
80
+ # Return an updated peers query for single use, notice there's no ! symbol
81
+ Chef::Search.peers.where(roles:"shard1").to_s
82
+ > (chef_environment:production NOT tags:maintenance) AND roles:load_balancer AND roles:shard1
83
+
84
+ Chef::Search.base.to_s
85
+ # Unchanged
86
+ > chef_environment:production NOT tags:maintenance
87
+
88
+ Chef::Search.peers.to_s
89
+ # Unchanged
90
+ > (chef_environment:production NOT tags:maintenance) AND roles:load_balancer
91
+
92
+ # Perform a search
93
+ Chef::Search.peers.search.map { |item| item.node_name }
94
+ > [node,node,node]
95
+
96
+ # Perform a partial search
97
+ # notice we automatically ensure values are Arrays when they are strings
98
+ Chef::Search.peers.partial(
99
+ keys:{chef_environment:"chef_environment",hostname:["hostname"]
100
+ )
101
+ > [{chef_environment:"production", hostname:"host1"}]
102
+ ```
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it ( https://github.com/[my-github-username]/query_syntax/fork )
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,19 @@
1
+
2
+ class Chef
3
+ class Search
4
+ def self.add_query(name, index = :node)
5
+ self.class.instance_eval do
6
+ define_method "#{name}" do
7
+ instance_variable_get("@#{name}")
8
+ end
9
+
10
+ define_method "#{name}=" do |value|
11
+ instance_variable_set("@#{name}",value)
12
+ end
13
+ end
14
+
15
+ instance_variable_set("@#{name}", QuerySyntax::Query.new(index))
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,5 @@
1
+
2
+ require "query_syntax/version"
3
+ require "query_syntax/query"
4
+ require "chef/search"
5
+
@@ -0,0 +1,102 @@
1
+
2
+ require 'query_syntax/scope'
3
+
4
+ module QuerySyntax
5
+ class Query < NestedScope
6
+ def initialize(index)
7
+ super "AND"
8
+ @index = index
9
+ @ignore_failure = true
10
+ end
11
+
12
+ attr_accessor :ignore_failure
13
+
14
+ #
15
+ # Spawn! returns a clone to continue processing without mucking self
16
+ #
17
+ def spawn(operator="AND")
18
+ scope = clone
19
+ scope.scopes = scopes.clone
20
+ scope.scope!(operator)
21
+ scope
22
+ end
23
+
24
+ #
25
+ # Spawn a new scopes
26
+ #
27
+
28
+ def where(args={})
29
+ scope = spawn("AND")
30
+ scope.where!(args) if args
31
+ end
32
+
33
+ def not(args={})
34
+ scope = spawn("NOT")
35
+ scope.not!(args)
36
+ end
37
+
38
+ def and(args={})
39
+ scope = spawn("AND")
40
+ scope.and!(args)
41
+ end
42
+
43
+ def or(args={})
44
+ scope = spawn("OR")
45
+ scope.or!(args)
46
+ end
47
+
48
+ #
49
+ # Search related methods
50
+ #
51
+ def encode
52
+ URI.escape(to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
53
+ end
54
+
55
+ def search(&block)
56
+ begin
57
+ if Kernel.block_given?
58
+ Chef::Search::Query.new.search(index, encode, &block)
59
+ else
60
+ results = Array.new
61
+ search { |result| results << result }
62
+ results
63
+ end
64
+ rescue Net::HTTPServerException => e
65
+ error = Chef::JSONCompat.from_json(e.response.body)["error"].first
66
+ Chef::Log.error("Search failed with : #{error}")
67
+
68
+ if @ignore_failure
69
+ then Array.new
70
+ else raise Net::HTTPServerException
71
+ end
72
+ end
73
+ end
74
+
75
+ def partial(keys, &block)
76
+ keys = Hash[keys.map { |k,v| [k,Array(v)] }]
77
+
78
+ begin
79
+ if Kernel.block_given?
80
+ Chef::PartialSearc.new.search(index, encode, keys:keys, &block)
81
+ else
82
+ results = Array.new
83
+ partial(keys) { |result| results << result }
84
+ results
85
+ end
86
+ rescue Net::HTTPServerException => e
87
+ error = Chef::JSONCompat.from_json(e.response.body)["error"].first
88
+ Chef::Log.error("Search failed with : #{error}")
89
+
90
+ if @ignore_failure
91
+ then Array.new
92
+ else raise Net::HTTPServerException
93
+ end
94
+ end
95
+ end
96
+
97
+ def each(&block)
98
+ search(&block)
99
+ end
100
+ end
101
+ end
102
+
@@ -0,0 +1,18 @@
1
+
2
+ module QuerySyntax
3
+ #
4
+ # Scopes are conditions seperated by operators (NOT | AND | OR)
5
+ #
6
+
7
+ class Scope
8
+ def initialize(operator="AND")
9
+ @operator = operator
10
+ end
11
+
12
+ attr_accessor :operator
13
+ end
14
+ end
15
+
16
+ require 'query_syntax/scope/criteria'
17
+ require 'query_syntax/scope/nested'
18
+
@@ -0,0 +1,47 @@
1
+
2
+ require 'forwardable'
3
+ require 'query_syntax/scope'
4
+
5
+ module QuerySyntax
6
+ #
7
+ # Criteria Scopes are scopes containing key:value conditions
8
+ # ex.: where!(a:0, b:1).where!(a:2) becomes (a=0 OR a=2) AND b=1
9
+ #
10
+
11
+ class CriteriaScope < Scope
12
+ extend Forwardable
13
+
14
+ def initialize(operator, conditions={})
15
+ super operator
16
+ @conditions = conditions
17
+ where!(conditions)
18
+ end
19
+
20
+ attr_accessor :conditions
21
+
22
+ def_delegators(:@conditions,
23
+ *(Hash.instance_methods - Object.instance_methods))
24
+
25
+ def where!(args={})
26
+ args.each do |k,v|
27
+ next if v.nil?
28
+ @conditions[k] = [] unless conditions.key?(k)
29
+ @conditions[k].concat(Array(v)).uniq!
30
+ end
31
+ self
32
+ end
33
+
34
+ #
35
+ # Collapse all conditions to a string
36
+ #
37
+ def to_s
38
+ value = @conditions.map do |key,values|
39
+ query = values.map { |value| "#{key}:#{value}" }.join(" OR ")
40
+ values.count > 1 ? "(#{query})" : query
41
+ end.join(" AND ")
42
+
43
+ conditions.count > 1 ? "(#{value})" : value
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,122 @@
1
+
2
+ require 'forwardable'
3
+ require 'query_syntax/scope'
4
+ require 'query_syntax/scope/criteria'
5
+
6
+ module QuerySyntax
7
+ #
8
+ # Nested Scopes are scopes used for nesting other related scopes within ()
9
+ #
10
+
11
+ class NestedScope < Scope
12
+ extend Forwardable
13
+
14
+ def initialize(operator, *scopes)
15
+ super operator
16
+ @scopes = scopes
17
+ end
18
+
19
+ attr_accessor :scopes
20
+
21
+ #
22
+ # Return a new CriteriaScope which is added to previous scopes
23
+ #
24
+ def scope!(operator)
25
+ scope = CriteriaScope.new(operator)
26
+ scopes << scope
27
+ self
28
+ end
29
+
30
+ def nest!(operator)
31
+ @scopes = [NestedScope.new(operator, *scopes)]
32
+ self
33
+ end
34
+
35
+ def not!(args={})
36
+ if args.empty? then nest!("NOT")
37
+ else scope!("NOT").where!(args)
38
+ end
39
+ self
40
+ end
41
+
42
+ def and!(args={})
43
+ if args.empty? then nest!("AND")
44
+ else scope!("AND").where!(args)
45
+ end
46
+ self
47
+ end
48
+
49
+ def or!(args={})
50
+ if args.empty? then nest!("OR")
51
+ else scope!("OR").where!(args)
52
+ end
53
+ self
54
+ end
55
+
56
+ #
57
+ # Return the last scope so that we can continue adding to it
58
+ #
59
+ def scope
60
+ scope!(operator) if @scopes.empty?
61
+ scope!("AND") if @scopes.last.is_a?(NestedScope)
62
+ @scopes.last
63
+ end
64
+
65
+ #
66
+ # Return the last scope's criteria
67
+ #
68
+ def conditions
69
+ scope.conditions
70
+ end
71
+
72
+ #
73
+ # Add criteria to the last scope
74
+ #
75
+ def where!(args={})
76
+ scope.where!(args)
77
+ self
78
+ end
79
+
80
+ #
81
+ # Convenience
82
+ #
83
+
84
+ def empty?
85
+ compact.empty?
86
+ end
87
+
88
+ def compact
89
+ scopes.reject { |scope| scope.empty? }
90
+ end
91
+
92
+ def compact!
93
+ scopes = compact
94
+ self
95
+ end
96
+
97
+ def push(other)
98
+ @scopes << other
99
+ scope!(operator)
100
+ self
101
+ end
102
+
103
+ #
104
+ # Collapse all scopes to a string
105
+ #
106
+ def to_s
107
+ values = compact.map do |scope|
108
+ value = scope.to_s
109
+ value = if scope.is_a?(NestedScope)
110
+ scope.compact.count > 1 ? "(#{value})" : value
111
+ else value
112
+ end
113
+ [scope.operator, value]
114
+ end
115
+
116
+ values = values.flatten.compact
117
+ values.shift
118
+ values.join(" ")
119
+ end
120
+ end
121
+ end
122
+
@@ -0,0 +1,5 @@
1
+
2
+ module QuerySyntax
3
+ VERSION = "1.0.1"
4
+ end
5
+
@@ -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 'query_syntax/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "query_syntax"
8
+ spec.version = QuerySyntax::VERSION
9
+ spec.authors = ["Jonathan Serafini"]
10
+ spec.email = ["jonathan@lightspeedretail.com"]
11
+ spec.summary = "Provide chainable objects to build Chef search queries"
12
+ spec.description = "Provides a mechanism to craft Chef queries through object chains such as .and.or.where(key:value)"
13
+ spec.homepage = "https://github.com/JonathanSerafini/gem-query_syntax"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
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.6"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: query_syntax
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Serafini
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-17 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.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
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: Provides a mechanism to craft Chef queries through object chains such
42
+ as .and.or.where(key:value)
43
+ email:
44
+ - jonathan@lightspeedretail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - lib/chef/search.rb
55
+ - lib/query_syntax.rb
56
+ - lib/query_syntax/query.rb
57
+ - lib/query_syntax/scope.rb
58
+ - lib/query_syntax/scope/criteria.rb
59
+ - lib/query_syntax/scope/nested.rb
60
+ - lib/query_syntax/version.rb
61
+ - query_syntax.gemspec
62
+ homepage: https://github.com/JonathanSerafini/gem-query_syntax
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.1.11
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Provide chainable objects to build Chef search queries
86
+ test_files: []