module SemanticPuppet::Dependency

Public Instance Methods

add_source(source) click to toggle source

Appends a new {Source} to the current list. @param source [Source] the {Source} to add @return [void]

# File lib/semantic_puppet/dependency.rb, line 24
def add_source(source)
  sources
  @sources << source
  nil
end
clear_sources() click to toggle source

Clears the current list of {Source}s. @return [void]

# File lib/semantic_puppet/dependency.rb, line 32
def clear_sources
  sources
  @sources.clear
  nil
end
fetch_releases(name) click to toggle source

Fetches all available releases for the given module name.

@param name [String] the module name to find releases for @return [Array<ModuleRelease>] the available releases

# File lib/semantic_puppet/dependency.rb, line 77
def fetch_releases(name)
  releases = {}

  sources.each do |source|
    source.fetch(name).each do |dependency|
      releases[dependency.version] ||= dependency
    end
  end

  return releases.values
end
query(modules) click to toggle source

Fetches a graph of modules and their dependencies from the currently configured list of {Source}s.

@todo Return a specialized “Graph” object. @todo Allow for external constraints to be added to the graph. @see sources @see add_source @see clear_sources

@param modules [{ String => String }] @return [Graph] the root of a dependency graph

# File lib/semantic_puppet/dependency.rb, line 51
def query(modules)
  constraints = Hash[modules.map { |k, v| [ k, VersionRange.parse(v) ] }]

  graph = Graph.new(constraints)
  fetch_dependencies(graph)
  return graph
end
resolve(graph) click to toggle source

Given a graph result from {#query}, this method will resolve the graph of dependencies, if possible, into a flat list of the best suited modules. If the dependency graph does not have a suitable resolution, this method will raise an exception to that effect.

@param graph [Graph] the root of a dependency graph @return [Array<ModuleRelease>] the list of releases to act on

# File lib/semantic_puppet/dependency.rb, line 66
def resolve(graph)
  catch :next do
    return walk(graph, graph.dependencies.dup)
  end
  raise UnsatisfiableGraph.new(graph)
end
sources() click to toggle source

@return [Array<Source>] a frozen copy of the {Source} list

# File lib/semantic_puppet/dependency.rb, line 17
def sources
  (@sources ||= []).dup.freeze
end

Private Instance Methods

fetch_dependencies(node, cache = {}) click to toggle source

Given a {ModuleRelease}, this method will iterate through the current list of {Source}s to find the complete list of versions available for its dependencies.

@param node [GraphNode] the node to fetch details for @return [void]

# File lib/semantic_puppet/dependency.rb, line 154
def fetch_dependencies(node, cache = {})
  node.dependency_names.each do |name|
    unless cache.key?(name)
      cache[name] = fetch_releases(name)
      cache[name].each { |dep| fetch_dependencies(dep, cache) }
    end

    node << cache[name]
  end
end
preferred_releases(releases) click to toggle source

Given a list of potential releases, this method returns the most suitable releases for exploration. Only {ModuleRelease#satisfied? satisfied} releases are considered, and releases with stable versions are preferred.

@param releases [Array<ModuleRelease>] a list of potential releases @return [Array<ModuleRelease>] releases open for consideration

# File lib/semantic_puppet/dependency.rb, line 171
def preferred_releases(releases)
  satisfied = releases.select { |x| x.satisfied? }

  if satisfied.any? { |x| x.version.stable? }
    return satisfied.select { |x| x.version.stable? }
  else
    return satisfied
  end
end
walk(graph, dependencies, *considering) click to toggle source

Iterates over a changing set of dependencies in search of the best solution available. Fitness is specified as meeting all the constraints placed on it, being {ModuleRelease#satisfied? satisfied}, and having the greatest version number (with stability being preferred over prereleases).

@todo Traversal order is not presently guaranteed.

@param graph [Graph] the root of a dependency graph @param dependencies [{ String => Array<ModuleRelease> }] the dependencies @param considering [Array<GraphNode>] the set of releases being tested @return [Array<GraphNode>] the list of releases to use, if successful

# File lib/semantic_puppet/dependency.rb, line 102
def walk(graph, dependencies, *considering)
  return considering if dependencies.empty?

  # Selecting a dependency from the collection...
  name = dependencies.keys.sort.first
  deps = dependencies.delete(name)

  # ... (and stepping over it if we've seen it before) ...
  unless (deps & considering).empty?
    return walk(graph, dependencies, *considering)
  end

  # ... we'll iterate through the list of possible versions in order.
  preferred_releases(deps).reverse_each do |dep|

    # We should skip any releases that violate any module's constraints.
    unless [graph, *considering].all? { |x| x.satisfies_constraints?(dep) }
      next
    end

    # We should skip over any releases that violate graph-level constraints.
    potential_solution = considering.dup << dep
    unless graph.satisfies_graph? potential_solution
      next
    end

    catch :next do
      # After adding any new dependencies and imposing our own constraints
      # on existing dependencies, we'll mark ourselves as "under
      # consideration" and recurse.
      merged = dependencies.merge(dep.dependencies) { |_,a,b| a & b }

      # If all subsequent dependencies resolved well, the recursive call
      # will return a completed dependency list. If there were problems
      # resolving our dependencies, we'll catch `:next`, which will cause
      # us to move to the next possibility.
      return walk(graph, merged, *potential_solution)
    end
  end

  # Once we've exhausted all of our possible versions, we know that our
  # last choice was unusable, so we'll unwind the stack and make a new
  # choice.
  throw :next
end