lib/frill/frill.rb
module Frill
class CyclicDependency < RuntimeError; end
def self.included base
self.dependency_graph.add base
base.extend ClassMethods
end
def self.decorators
@decorators ||= dependency_graph.to_a
@decorators.dup
end
def self.reset!
@decorators = nil
@dependency_graph = nil
end
def self.decorate object, context, options={}
frills = decorators
if subset = options[:with]
frills.select! {|d| subset.include? d}
end
frills.each do |f|
object.extend f if f.frill? object, context
end
object
end
def self.dependency_graph
@dependency_graph ||= DependencyGraph.new
end
module ClassMethods
def before decorator
Frill.dependency_graph.move_before self, decorator
end
def after decorator
Frill.dependency_graph.move_before decorator, self
end
end
class DependencyGraph
def initialize
@nodes = {}
end
def add label
nodes[label] ||= Node.new label
end
def move_before label1, label2
node1 = add label1
node2 = add label2
node1.move_before node2
CycleDetecter.detect! nodes
end
def [](label)
nodes[label]
end
def empty?
nodes.empty?
end
def include? label
nodes[label]
end
def index label
to_a.index label
end
def to_a
array = []
nodes.values.each do |node|
array += construct_array(node) unless array.include? node.label
end
array
end
private
attr_reader :nodes
def construct_array node
array = []
current_node = node.first
while current_node
array << current_node.label
current_node = current_node.next
end
array
end
class CycleDetecter
def self.detect! nodes
new(nodes).detect!
end
def initialize nodes
@nodes = nodes
@visited = {}
end
def detect!
nodes.values.each do |node|
fan_out node unless visited[node.label]
end
end
private
attr_reader :nodes, :visited
def fan_out node
visited[node.label] = true
fan :next, node
fan :previous, node
end
def fan direction, start_node
current_node = start_node.send direction
while current_node
raise Frill::CyclicDependency if visited[current_node.label]
visited[current_node.label] = true
current_node = current_node.send direction
end
end
end
class Node
attr_accessor :next, :previous
attr_reader :label
def initialize(label)
@label = label
@next = nil
@previous = nil
end
def move_before node
next_node = node.first
previous_node = self.last
previous_node.next = next_node
next_node.previous = previous_node
end
def first
first_node = self
first_node = first_node.previous while first_node.previous
first_node
end
def last
last_node = self
last_node = last_node.next while last_node.next
last_node
end
end
end
end