lib/framework/parsers/query_parser.rb
# frozen_string_literal: true
module Facter
class QueryParser
@log = Log.new(self)
class << self
# Searches for facts that could resolve a user query.
# There are 4 types of facts:
# root facts
# e.g. networking
# child facts
# e.g. networking.dhcp
# composite facts
# e.g. networking.interfaces.en0.bindings.address
# regex facts (legacy)
# e.g. impaddress_end160
#
# Because a root fact will always be resolved by a collection of child facts,
# we can return one or more child facts for each parent.
#
# query - is the user input used to search for facts
# loaded_fact - is a list with all facts for the current operating system
#
# Returns a list of SearchedFact objects that resolve the users query.
def parse(query_list, loaded_fact)
matched_facts = []
@log.debug "User query is: #{query_list}"
@query_list = query_list
return no_user_query(loaded_fact) unless query_list.any?
query_list.each do |query|
@log.debug "Query is #{query}"
found_facts = search_for_facts(query, loaded_fact)
matched_facts << found_facts
end
matched_facts.flatten(1)
end
def no_user_query(loaded_facts)
searched_facts = []
loaded_facts.each do |loaded_fact|
searched_facts << SearchedFact.new(loaded_fact.name, loaded_fact.klass, [], '', loaded_fact.type)
end
searched_facts
end
def search_for_facts(query, loaded_fact_hash)
resolvable_fact_list = []
query = query.to_s
query_tokens = query.end_with?('.*') ? [query] : query.split('.')
size = query_tokens.size
size.times do |i|
query_token_range = 0..size - i - 1
resolvable_fact_list = get_facts_matching_tokens(query_tokens, query_token_range, loaded_fact_hash)
return resolvable_fact_list if resolvable_fact_list.any?
end
resolvable_fact_list << SearchedFact.new(query, nil, [], query, :nil) if resolvable_fact_list.empty?
resolvable_fact_list
end
def get_facts_matching_tokens(query_tokens, query_token_range, loaded_fact_hash)
@log.debug "Checking query tokens #{query_tokens[query_token_range].join('.')}"
resolvable_fact_list = []
loaded_fact_hash.each do |loaded_fact|
query_fact = query_tokens[query_token_range].join('.')
next unless found_fact?(loaded_fact.name, query_fact)
searched_fact = construct_loaded_fact(query_tokens, query_token_range, loaded_fact)
resolvable_fact_list << searched_fact
end
@log.debug "List of resolvable facts: #{resolvable_fact_list.inspect}"
resolvable_fact_list
end
def found_fact?(fact_name, query_fact)
fact_with_wildcard = fact_name.include?('.*') && !query_fact.include?('.')
processed_equery_fact = query_fact.gsub('\\', '\\\\\\\\')
return false if fact_with_wildcard && !query_fact.match("^#{fact_name}$")
return false unless fact_with_wildcard || fact_name.match("^#{processed_equery_fact}($|\\.)")
true
end
def construct_loaded_fact(query_tokens, query_token_range, loaded_fact)
filter_tokens = construct_filter_tokens(query_tokens, query_token_range)
user_query = @query_list.any? ? query_tokens.join('.') : ''
fact_name = loaded_fact.name.to_s
klass_name = loaded_fact.klass
type = loaded_fact.type
sf = SearchedFact.new(fact_name, klass_name, filter_tokens, user_query, type)
sf.file = loaded_fact.file
sf
end
def construct_filter_tokens(query_tokens, query_token_range)
(query_tokens - query_tokens[query_token_range]).map do |token|
token =~ /^[0-9]+$/ ? token.to_i : token
end
end
end
end
end