Initial Chef repository
This commit is contained in:
222
cookbooks/chef-solo-search/libraries/search/parser.rb
Normal file
222
cookbooks/chef-solo-search/libraries/search/parser.rb
Normal file
@@ -0,0 +1,222 @@
|
||||
#
|
||||
# Copyright 2011, edelight GmbH
|
||||
#
|
||||
# Authors:
|
||||
# Markus Korn <markus.korn@edelight.de>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
require 'chef/solr_query/query_transform'
|
||||
|
||||
# mock QueryTransform such that we can access the location of the lucene grammar
|
||||
class Chef
|
||||
class SolrQuery
|
||||
class QueryTransform
|
||||
def self.base_path
|
||||
class_variable_get(:@@base_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_flat_hash(hsh, prefix="")
|
||||
result = {}
|
||||
hsh.each_pair do |key, value|
|
||||
if value.kind_of?(Hash)
|
||||
result.merge!(build_flat_hash(value, "#{prefix}#{key}_"))
|
||||
else
|
||||
result[prefix+key] = value
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
module Lucene
|
||||
|
||||
class Term < Treetop::Runtime::SyntaxNode
|
||||
# compares a query value and a value, tailing '*'-wildcards are handled correctly.
|
||||
# Value can either be a string or an array, all other objects are converted
|
||||
# to a string and than checked.
|
||||
def match( value )
|
||||
if value.is_a?(Array)
|
||||
value.any?{ |x| self.match(x) }
|
||||
else
|
||||
File.fnmatch(self.text_value, value.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Field < Treetop::Runtime::SyntaxNode
|
||||
# simple field -> value matches, supporting tailing '*'-wildcards in keys
|
||||
# as well as in values
|
||||
def match( item )
|
||||
keys = self.elements[0].match(item)
|
||||
if keys.nil?
|
||||
false
|
||||
else
|
||||
keys.any?{ |key| self.elements[1].match(item[key]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# we don't support range matches
|
||||
# range of integers would be easy to implement
|
||||
# but string ranges are hard
|
||||
class FiledRange < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
# we handle '[* TO *]' as a special case since it is common in
|
||||
# cookbooks for matching the existence of keys
|
||||
class InclFieldRange
|
||||
def match(item)
|
||||
field = self.elements[0].text_value
|
||||
range_start = self.elements[1].transform
|
||||
range_end = self.elements[2].transform
|
||||
if range_start == "*" and range_end == "*"
|
||||
!!item[field]
|
||||
else
|
||||
raise "Ranges not really supported yet"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ExclFieldRange < FieldRange
|
||||
end
|
||||
|
||||
class RangeValue < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
class FieldName < Treetop::Runtime::SyntaxNode
|
||||
def match( item )
|
||||
if self.text_value.count("_") > 0
|
||||
item.merge!(build_flat_hash(item))
|
||||
end
|
||||
if self.text_value.end_with?("*")
|
||||
part = self.text_value.chomp("*")
|
||||
item.keys.collect{ |key| key.start_with?(part)? key: nil}.compact
|
||||
else
|
||||
if item.has_key?(self.text_value)
|
||||
[self.text_value,]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Body < Treetop::Runtime::SyntaxNode
|
||||
def match( item )
|
||||
self.elements[0].match( item )
|
||||
end
|
||||
end
|
||||
|
||||
class Group < Treetop::Runtime::SyntaxNode
|
||||
def match( item )
|
||||
self.elements[0].match(item)
|
||||
end
|
||||
end
|
||||
|
||||
class BinaryOp < Treetop::Runtime::SyntaxNode
|
||||
def match( item )
|
||||
self.elements[1].match(
|
||||
self.elements[0].match(item),
|
||||
self.elements[2].match(item)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class OrOperator < Treetop::Runtime::SyntaxNode
|
||||
def match( cond1, cond2 )
|
||||
cond1 or cond2
|
||||
end
|
||||
end
|
||||
|
||||
class AndOperator < Treetop::Runtime::SyntaxNode
|
||||
def match( cond1, cond2 )
|
||||
cond1 and cond2
|
||||
end
|
||||
end
|
||||
|
||||
# we don't support fuzzy string matching
|
||||
class FuzzyOp < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
class BoostOp < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
class FuzzyParam < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
class UnaryOp < Treetop::Runtime::SyntaxNode
|
||||
def match( item )
|
||||
self.elements[0].match(
|
||||
self.elements[1].match(item)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class NotOperator < Treetop::Runtime::SyntaxNode
|
||||
def match( cond )
|
||||
not cond
|
||||
end
|
||||
end
|
||||
|
||||
class RequiredOperator < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
class ProhibitedOperator < Treetop::Runtime::SyntaxNode
|
||||
end
|
||||
|
||||
class Phrase < Treetop::Runtime::SyntaxNode
|
||||
# a quoted ::Term
|
||||
def match( value )
|
||||
self.elements[0].match(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Query
|
||||
# initialize the parser by using the grammar shipped with chef
|
||||
@@grammar = File.join(Chef::SolrQuery::QueryTransform.base_path, "lucene.treetop")
|
||||
Treetop.load(@@grammar)
|
||||
@@parser = LuceneParser.new
|
||||
|
||||
def self.parse(data)
|
||||
# parse the query into a query tree
|
||||
if data.nil?
|
||||
data = "*:*"
|
||||
end
|
||||
tree = @@parser.parse(data)
|
||||
if tree.nil?
|
||||
msg = "Parse error at offset: #{@@parser.index}\n"
|
||||
msg += "Reason: #{@@parser.failure_reason}"
|
||||
raise "Query #{data} is not supported: #{msg}"
|
||||
end
|
||||
self.clean_tree(tree)
|
||||
tree
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.clean_tree(root_node)
|
||||
# remove all SyntaxNode elements from the tree, we don't need them as
|
||||
# the related ruby class already knowns what to do.
|
||||
return if root_node.elements.nil?
|
||||
root_node.elements.delete_if do |node|
|
||||
node.class.name == "Treetop::Runtime::SyntaxNode"
|
||||
end
|
||||
root_node.elements.each { |node| self.clean_tree(node) }
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user