Class: Ruber::ComponentManager::PluginSorter

Inherits:
Object
  • Object
show all
Defined in:
lib/ruber/component_manager.rb

Overview

Helper class used to resolve dependencies among plugins. Most likely you don’t need to use it, but simply call Ruber::ComponentManager.sort_plugins.

Instance Method Summary (collapse)

Constructor Details

- (PluginSorter) initialize(pdfs, ignored = [])

Creates a new PluginSorter. pdfs is an array of the plugin descriptions to sort. ignored is an array containing dependencies to be ignored(maybe because they’re already loaded). ignored can be either an array of symbols, where each symbol is the name of a feature, or an array of +PluginSpecification+s.

Note: pdfs should contain dependencies in terms of actual plugins, not of features.



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ruber/component_manager.rb', line 67

def initialize pdfs, ignored = []
  @pdfs = {}
  @plugins = {}
  pdfs.each do |i|
    @pdfs[i.name] = i
    @plugins[i.name] = i.deps
  end
  @ignored = ignored.map{|i| i.is_a?(OpenStruct) ? i.name : i}
  @ready = []
  @deps = {}
end

Instance Method Details

- (Object) find_dep(plug, stack = []) (private)

Finds the dependencies of the plugin plug. To do this, it calls itself recursively for each of the direct dependencies of the plugin. The dependencies found are stored in the @deps hash.

To avoid an endless loop or a SystemStackError in case of circular dependencies, each time the method is called, it is also passed a second argument, an array containing the names of the plugins whose dependencies have lead to that call. If circular dependencies are found, the entry in @deps corresponding to the plugin is set to nil, and an array containing the pairs of plugins with circular dependencies is returned. If no circular dependencies exist, the returned array is empty.


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ruber/component_manager.rb', line 148

def find_dep plug, stack = []
  direct_deps = @plugins[plug] || []
  circ = []
  deps = []
  circ << plug if stack.include? plug
  direct_deps.each{|d| circ << plug << d if stack.include? d}
  if circ.empty?
    deps = []
    res = direct_deps.each do |d|
      circ += find_dep d, stack + [plug] unless @deps.has_key? d
      deps += @deps[d] + [d] if @deps[d] 
    end
  end
  @deps[plug] = circ.empty? ? deps : nil
  circ
end

- (Object) find_unknown_deps (private)

Checks whether all the dependencies among the plugins are satisifed either by another plugin or by a plugin in the ignore list. Returns a hash which is empty if all the dependencies were satisifed and otherwise has for keys the names of plugins whose dependencies couldn’t be found and for values arrays containing the names of the missing dependencies for that plugin.



124
125
126
127
128
129
130
131
132
# File 'lib/ruber/component_manager.rb', line 124

def find_unknown_deps
  known = @plugins.keys
  res = Hash.new{|h, k| h[k] = []}
  known.each do |i|
    missing_deps = @plugins[i] - known
    missing_deps.each{|d| res[d] << i}
  end
  res
end

- (Object) sort_plugins

Sorts the plugins associated with the object, according with their dependencies and returns an array containing the plugin descriptions sorted in dependence order, from the dependence to the dependent.

If some of the plugins have dependency which doesn’t correspond neither to another plugin nor to one of the plugins to ignore, Ruber::ComponentManager::UnresolvedDep will be raised.

If there’s a circular dependency among the plugins, Ruber::ComponentManager::CircularDep will be raised.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/ruber/component_manager.rb', line 91

def sort_plugins
  @plugins.each_value do |v|
    v.reject!{|d| @ignored.include? d}
  end
  unknown = find_unknown_deps
  raise ComponentManager::UnresolvedDep.new unknown unless unknown.empty?
  circular = @plugins.keys.inject([]){ |res, plug|  res + find_dep( plug ) }
  raise ComponentManager::CircularDep.new(circular.uniq) unless circular.empty?
  deps = @deps.reject{|k, v| v.nil? }
  res = []
  old_size = deps.size
  until deps.empty?
    ready = deps.select{|k, v| v.empty?}.map{|i| i[0]}.sort_by{|i| i.to_s}
    res += ready
    ready.each do |i|
      deps.each{|d| d[1].delete i}
      deps.delete i
    end
    raise "Circular deps (this shouldn't happen)" unless old_size > deps.size
    old_size = deps.size
  end
  res.map{|i| @pdfs[i]}
end