class FilterTable::Table

Attributes

criteria_string[R]
raw_data[R]
resource_instance[R]

Public Class Methods

new(resource_instance, raw_data, criteria_string) click to toggle source
# File lib/inspec/utils/filter.rb, line 92
def initialize(resource_instance, raw_data, criteria_string)
  @resource_instance = resource_instance
  @raw_data = raw_data
  @raw_data = [] if @raw_data.nil?
  @criteria_string = criteria_string
  @populated_lazy_columns = {}
end

Public Instance Methods

callback_for_lazy_field(field_name) click to toggle source
# File lib/inspec/utils/filter.rb, line 220
def callback_for_lazy_field(field_name)
  return unless is_field_lazy?(field_name)

  custom_properties_schema.values.find do |property_struct|
    property_struct.field_name == field_name
  end.opts[:lazy]
end
create_eval_context_for_row(*_) click to toggle source
# File lib/inspec/utils/filter.rb, line 146
def create_eval_context_for_row(*_)
  raise "#{self.class} must not be used on its own. It must be inherited "\
       "and the #create_eval_context_for_row method must be implemented. This is an internal "\
       "error and should not happen."
end
entries() click to toggle source
# File lib/inspec/utils/filter.rb, line 161
def entries
  row_criteria_string = resource.to_s + criteria_string + " one entry"
  raw_data.map do |row|
    create_eval_context_for_row(row, row_criteria_string)
  end
end
field?(proposed_field) click to toggle source
# File lib/inspec/utils/filter.rb, line 180
def field?(proposed_field)
  # Currently we only know about a field if it is present in a at least one row of the raw data.
  # If we have no rows in the raw data, assume all fields are acceptable (and rely on failing to match on value, nil)
  return true if raw_data.empty?

  # Most resources have Symbol keys in their raw data.  Some have Strings (looking at you, `shadow`).
  is_field = false
  is_field ||= list_fields.include?(proposed_field.to_s)
  is_field ||= list_fields.include?(proposed_field.to_sym)
  is_field ||= is_field_lazy?(proposed_field.to_s)
  is_field ||= is_field_lazy?(proposed_field.to_sym)

  is_field
end
field_populated?(field_name) click to toggle source
# File lib/inspec/utils/filter.rb, line 228
def field_populated?(field_name)
  @populated_lazy_columns[field_name]
end
get_column_values(field) click to toggle source
# File lib/inspec/utils/filter.rb, line 168
def get_column_values(field)
  raw_data.map do |row|
    row[field]
  end
end
inspect()
Alias for: to_s
is_field_lazy?(sought_field_name) click to toggle source
# File lib/inspec/utils/filter.rb, line 213
def is_field_lazy?(sought_field_name)
  custom_properties_schema.values.any? do |property_struct|
    sought_field_name == property_struct.field_name && \
      property_struct.opts[:lazy]
  end
end
list_fields() click to toggle source
# File lib/inspec/utils/filter.rb, line 174
def list_fields
  @__fields_in_raw_data ||= raw_data.reduce([]) do |fields, row|
    fields.concat(row.keys).uniq
  end
end
mark_lazy_field_populated(field_name) click to toggle source
# File lib/inspec/utils/filter.rb, line 232
def mark_lazy_field_populated(field_name)
  @populated_lazy_columns[field_name] = true
end
params() click to toggle source
# File lib/inspec/utils/filter.rb, line 156
def params
  # TODO: consider deprecating
  raw_data
end
populate_lazy_field(field_name, criterion) click to toggle source
# File lib/inspec/utils/filter.rb, line 201
def populate_lazy_field(field_name, criterion)
  return unless is_field_lazy?(field_name)
  return if field_populated?(field_name)

  raw_data.each do |row|
    next if row.key?(field_name) # skip row if pre-existing data is present

    callback_for_lazy_field(field_name).call(row, criterion, self)
  end
  mark_lazy_field_populated(field_name)
end
resource() click to toggle source
# File lib/inspec/utils/filter.rb, line 152
def resource
  resource_instance
end
to_s() click to toggle source
# File lib/inspec/utils/filter.rb, line 195
def to_s
  resource.to_s + criteria_string
end
Also aliased as: inspect
where(conditions = {}, &block) click to toggle source

Filter the raw data based on criteria (as method params) or by evaling a block; then construct a new Table of the same class as ourselves, wrapping the filtered data, and return it.

# File lib/inspec/utils/filter.rb, line 103
def where(conditions = {}, &block)
  return self unless conditions.is_a?(Hash)
  return self if conditions.empty? && !block_given?

  # Initialize the details of the new Table.
  new_criteria_string = criteria_string
  filtered_raw_data = raw_data

  # If we were provided params, interpret them as criteria to be evaluated
  # against the raw data. Criteria are assumed to be hash keys.
  conditions.each do |raw_field_name, desired_value|
    raise(ArgumentError, "'#{decorate_symbols(raw_field_name)}' is not a recognized criterion - expected one of #{decorate_symbols(list_fields).join(", ")}'") unless field?(raw_field_name)

    populate_lazy_field(raw_field_name, desired_value) if is_field_lazy?(raw_field_name)
    new_criteria_string += " #{raw_field_name} == #{desired_value.inspect}"
    filtered_raw_data = filter_raw_data(filtered_raw_data, raw_field_name, desired_value)
  end

  # If we were given a block, make a special Struct for each row, that has an accessor
  # for each field declared using `register_custom_property`, then instance-eval the block
  # against the struct.
  if block_given?
    # Perform the filtering.
    filtered_raw_data = filtered_raw_data.find_all { |row_as_hash| create_eval_context_for_row(row_as_hash, "").instance_eval(&block) }
    # Try to interpret the block for updating the stringification.
    src = Trace.new
    # Swallow any exceptions raised here.
    # See https://github.com/chef/inspec/issues/2929
    begin
      src.instance_eval(&block)
    rescue # rubocop: disable Lint/HandleExceptions
      # Yes, robocop, ignoring all exceptions is normally
      # a bad idea.  Here, an exception just means we don't
      # understand what was in a `where` block, so we can't
      # meaningfully sytringify it.  We still have a decent
      # default stringification.
    end
    new_criteria_string += Trace.to_ruby(src)
  end

  self.class.new(resource, filtered_raw_data, new_criteria_string)
end

Private Instance Methods

decorate_symbols(thing) click to toggle source
# File lib/inspec/utils/filter.rb, line 282
def decorate_symbols(thing)
  return thing.map { |t| decorate_symbols(t) } if thing.is_a?(Array)
  return ":" + thing.to_s if thing.is_a? Symbol
  return thing + " (String)" if thing.is_a? String

  thing
end
filter_raw_data(current_raw_data, field, desired_value) click to toggle source
# File lib/inspec/utils/filter.rb, line 262
def filter_raw_data(current_raw_data, field, desired_value)
  return [] if current_raw_data.empty?

  method_ref = case desired_value
               when Float   then :matches_float
               when Integer then :matches_int
               when Regexp  then :matches_regex
               else              :matches
               end

  assume_symbolic_keyed_data = current_raw_data.first.keys.first.is_a? Symbol
  field = assume_symbolic_keyed_data ? field.to_sym : field.to_s

  current_raw_data.find_all do |row|
    next unless row.key?(field)

    send(method_ref, row[field], desired_value)
  end
end
matches(x, y) click to toggle source
# File lib/inspec/utils/filter.rb, line 258
def matches(x, y)
  y === x # rubocop:disable Style/CaseEquality
end
matches_float(x, y) click to toggle source
# File lib/inspec/utils/filter.rb, line 238
def matches_float(x, y)
  return false if x.nil?
  return false if !x.is_a?(Float) && (x =~ /\A[-+]?(\d+\.?\d*|\.\d+)\z/).nil?

  x.to_f == y
end
matches_int(x, y) click to toggle source
# File lib/inspec/utils/filter.rb, line 245
def matches_int(x, y)
  return false if x.nil?
  return false if !x.is_a?(Integer) && (x =~ /\A[-+]?\d+\z/).nil?

  x.to_i == y
end
matches_regex(x, y) click to toggle source
# File lib/inspec/utils/filter.rb, line 252
def matches_regex(x, y)
  return x == y if x.is_a?(Regexp)

  !x.to_s.match(y).nil?
end