class Inspec::Metadata

Use valid? to determine if the metadata is coherent.
This allows the check CLI command to analyse the issues.
A Metadata object may be created and finalized with invalid data.
See lib/inspec/profile.rb for the runtime representation of a profile.
This class does NOT represent the runtime state of a profile during execution.
metadata like if this profile supports the current runtime and the intended target.
This includes the metadata stored in the profile’s metadata.rb file, as well as inferred
The Metadata class represents a profile’s metadata.

def self.finalize(metadata, profile_id, options, logger = nil)

def self.finalize(metadata, profile_id, options, logger = nil)
  return nil if metadata.nil?
  param = metadata.params || {}
  options ||= {}
  param["version"] = param["version"].to_s unless param["version"].nil?
  metadata.params = symbolize_keys(param)
  metadata.params[:supports] = finalize_supports(metadata.params[:supports], logger)
  finalize_name(metadata, profile_id, options[:target])
  metadata
end

def self.finalize_name(metadata, profile_id, original_target)

def self.finalize_name(metadata, profile_id, original_target)
  # profile_id always overwrites whatever already exists as the name
  unless profile_id.to_s.empty?
    metadata.params[:name] = profile_id.to_s
    return
  end
  # don't overwrite an existing name
  return unless metadata.params[:name].nil?
  # if there's a title, there is no need to set a name too
  return unless metadata.params[:title].nil?
  # create a new name based on the original target if it exists
  # Crudely slug the target to not contain slashes, to avoid breaking
  # unit tests that look for warning sequences
  return if original_target.to_s.empty?
  metadata.params[:title] = "tests from #{original_target}"
  metadata.params[:name] = metadata.params[:title].gsub(%r{[\/\\]}, ".")
end

def self.finalize_supports(supports, logger)

def self.finalize_supports(supports, logger)
  case x = supports
  when Hash   then [finalize_supports_elem(x, logger)]
  when Array  then x.map { |e| finalize_supports_elem(e, logger) }.compact
  when nil    then []
  end
end

def self.finalize_supports_elem(elem, logger)

def self.finalize_supports_elem(elem, logger)
  case x = elem
  when Hash
    x[:release] = x[:release].to_s if x[:release]
    x
  when Array
    logger.warn(
      "Failed to read supports entry that is an array. Please use "\
      "the `supports: {os-family: xyz}` syntax."
    )
    nil
  when nil then nil
  else
    Inspec.deprecate(
      :supports_syntax,
      "Do not use deprecated `supports: #{x}` syntax. Instead use:\n"\
        "supports:\n  - os-family: #{x}\n\n"
    )
    { :'os-family' => x } # rubocop:disable Style/HashSyntax
  end
end

def self.from_file(path, profile_id, logger = nil)

def self.from_file(path, profile_id, logger = nil)
  unless File.file?(path)
    logger ||= Logger.new(nil)
    logger.error "Can't find metadata file #{path}"
    return nil
  end
  from_ref(File.basename(path), File.read(path), profile_id, logger)
end

def self.from_ref(ref, content, profile_id, logger = nil)

def self.from_ref(ref, content, profile_id, logger = nil)
  # NOTE there doesn't have to exist an actual file, it may come from an
  # archive (i.e., content)
  case File.basename(ref)
  when "inspec.yml"
    from_yaml(ref, content, profile_id, logger)
  when "metadata.rb"
    from_ruby(ref, content, profile_id, logger)
  else
    logger ||= Logger.new(nil)
    logger.error "Don't know how to handle metadata in #{ref}"
    nil
  end
end

def self.from_ruby(ref, content, profile_id, logger = nil)

def self.from_ruby(ref, content, profile_id, logger = nil)
  res = Metadata.new(ref, logger)
  res.instance_eval(content, ref, 1)
  res.content = content
  finalize(res, profile_id, {}, logger)
end

def self.from_yaml(ref, content, profile_id, logger = nil)

def self.from_yaml(ref, content, profile_id, logger = nil)
  require "erb" unless defined?(Erb)
  res = Metadata.new(ref, logger)
  res.params = YAML.load(ERB.new(content).result)
  res.content = content
  finalize(res, profile_id, {}, logger)
end

def self.symbolize_keys(obj)

def self.symbolize_keys(obj)
  return obj.map { |i| symbolize_keys(i) } if obj.is_a?(Array)
  return obj unless obj.is_a?(Hash)
  obj.each_with_object({}) do |(k, v), h|
    v = symbolize_keys(v) if v.is_a?(Hash)
    v = symbolize_keys(v) if v.is_a?(Array)
    h[k.to_sym] = v
  end
end

def dependencies

def dependencies
  params[:depends] || []
end

def gem_dependencies

def gem_dependencies
  params[:gem_dependencies] || []
end

def initialize(ref, logger = nil)

def initialize(ref, logger = nil)
  @ref = ref
  @logger = logger || Logger.new(nil)
  @content = ""
  @params = {}
  @missing_methods = []
end

def inspec_requirement

def inspec_requirement
  # using Gem::Requirement here to allow nil values which
  # translate to [">= 0"]
  Gem::Requirement.create(params[:inspec_version])
end

def method_missing(sth, *args)

def method_missing(sth, *args)
  @logger.warn "#{ref} doesn't support: #{sth} #{args}"
  @missing_methods.push(sth)
end

def supports(sth, version = nil)

def supports(sth, version = nil)
  # Ignore supports with metadata.rb. This file is legacy and the way it
  # it handles `supports` deprecated. A deprecation warning will be printed
  # already.
end

def supports_platform?(backend)

def supports_platform?(backend)
  require "inspec/resources/platform" # break circularity in load
  backend.platform.supported?(params[:supports])
end

def supports_runtime?

def supports_runtime?
  running = Gem::Version.new(Inspec::VERSION)
  inspec_requirement.satisfied_by?(running)
end

def unsupported

def unsupported
  @missing_methods
end

def valid # rubocop:disable Metrics/AbcSize

rubocop:disable Metrics/AbcSize
return all warn and errors
def valid # rubocop:disable Metrics/AbcSize
  errors = []
  warnings = []
  %w{name version}.each do |field|
    next unless params[field.to_sym].nil?
    errors.push("Missing profile #{field} in #{ref}")
  end
  if %r{[\/\\]} =~ params[:name]
    errors.push("The profile name (#{params[:name]}) contains a slash" \
                  " which is not permitted. Please remove all slashes from `inspec.yml`.")
  end
  # if version is set, ensure it is correct
  if !params[:version].nil? && !valid_version?(params[:version])
    errors.push("Version needs to be in SemVer format")
  end
  if params[:entitlement_id] && params[:entitlement_id].strip.empty?
    errors.push("Entitlement ID should not be blank.")
  end
  unless supports_runtime?
    warnings.push("The current inspec version #{Inspec::VERSION} cannot satisfy profile inspec_version constraint #{params[:inspec_version]}")
  end
  %w{title summary maintainer copyright license}.each do |field|
    next unless params[field.to_sym].nil?
    warnings.push("Missing profile #{field} in #{ref}")
  end
  # if license is set, ensure it is in SPDX format or marked as proprietary
  if !params[:license].nil? && !valid_license?(params[:license])
    warnings.push("License '#{params[:license]}' needs to be in SPDX format or marked as 'Proprietary'. See https://spdx.org/licenses/.")
  end
  # If gem_dependencies is set, it must be an array of hashes with keys name and optional version
  unless params[:gem_dependencies].nil?
    list = params[:gem_dependencies]
    if list.is_a?(Array) && list.all? { |e| e.is_a? Hash }
      list.each do |entry|
        errors.push("gem_dependencies entries must all have a 'name' field") unless entry.key?(:name)
        if entry[:version]
          orig = entry[:version]
          begin
            # Split on commas as we may have a complex dep
            orig.split(",").map { |c| Gem::Requirement.parse(c) }
          rescue Gem::Requirement::BadRequirementError
            errors.push "Unparseable gem dependency '#{orig}' for #{entry[:name]}"
          rescue Inspec::GemDependencyInstallError => e
            errors.push e.message
          end
        end
        extra = (entry.keys - %i{name version})
        unless extra.empty?
          warnings.push "Unknown gem_dependencies key(s) #{extra.join(",")} seen for entry '#{entry[:name]}'"
        end
      end
    else
      errors.push("gem_dependencies must be a List of Hashes")
    end
  end
  [errors, warnings]
end

def valid?

returns true or false
def valid?
  errors, _warnings = valid
  errors.empty? && unsupported.empty?
end

def valid_license?(value)

def valid_license?(value)
  value =~ /^Proprietary[,;]?\b/ || Spdx.valid_license?(value)
end

def valid_version?(value)

def valid_version?(value)
  Semverse::Version.new(value)
  true
rescue Semverse::InvalidVersionFormat
  false
end