puppetlabs/facter-ng

View on GitHub
lib/framework/parsers/query_parser.rb

Summary

Maintainability
A
25 mins
Test Coverage
# 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