Skip to content

Commit

Permalink
A refactor, for readability.
Browse files Browse the repository at this point in the history
  • Loading branch information
james-dowling committed Oct 13, 2010
1 parent ea5952f commit 960c447
Showing 1 changed file with 135 additions and 80 deletions.
215 changes: 135 additions & 80 deletions lumberjack.rb
@@ -1,44 +1,43 @@
class Lumberjack

def self.construct(initial_scope = [], &block)
builder = new(initial_scope)
builder.__process(block)
builder.__process(block)
end

@@methods_to_keep = /^__/, /class/, /instance_eval/, /method_missing/,
/instance_variable_(g|s)et/
/instance_variable_(g|s)et/

instance_methods.each do |m|
undef_method m unless @@methods_to_keep.find { |r| r.match m }
end

def initialize(initial_scope)
@initial_scope = initial_scope
end

def __process(block)
prepare_scope
instance_eval(&block) if block
tree
rescue
puts @scope.inspect
raise $!
end

def /(ignore_me) # syntatic sugar for scope resolution
self
end

def load_tree_file(filename)
File.open filename, 'r' do |f|
eval f.read, binding, __FILE__, __LINE__
end
end

def shared_branch(branch_name, &block)
instance_variable_set "@#{branch_name}", lambda(&block)
end

def graft_branch(branch_name)
branch = instance_variable_get("@#{branch_name}")
raise "Attemption to graft branch #{branch_name} which is undefined" unless branch
Expand All @@ -50,93 +49,149 @@ def prune(method, value)
twig.respond_to?(method) && twig.send(method) == value
end
end

def method_missing(*args, &block)
# if we only have one arg, and no block, then we're trying to build a
# module scope, i.e. a/b/c/d would resolve to A::B::C::D, so let's start
# recording the bits...
if args.length == 1 and block.nil?
(@bits ||= []) << args[0]
self # return us coz we respond to / which does nothing but look good!
if still_modifying_scope?(args, block)
push_to_scope_stack_and_return_self(args)
else
assign_to_current_scope(*args, &block)
end
end

private

def assign_to_current_scope(*args, &block)
case current_scope_type
when :instance
assign_to_instance_with(*args, &block)
when :array
assign_to_array_with(*args, &block)
end
end

def assign_to_array_with(*args, &block)
klass = args.shift
if within_a_scope?
module_scope = @scope_stack.collect { |bit| classify bit.to_s }.join('::')
instance = eval("#{module_scope}::#{classify klass.to_s}").new(*args)
@scope_stack = nil
else
#
# now we've changed this here, we're assuming any scope that responds
# to << must be a collection, so we treat it as such - i can't think
# of any scenarios where this may bunk up, but best to remind myself
# for later just in case...
#
if !current_scope.respond_to?(:<<) # we're working inside an Instance
accessor = args.shift # grab the accessor name
if accessor.to_s[-1].chr == '!' # hacky hack instance something
instance = eval(classify(accessor.to_s[0...-1])).new(*args)
current_scope.send("#{accessor.to_s[0...-1]}=", instance)
if block # we got a block, change scope to set accessors
append_scope_with instance
instance_eval(&block)
jump_out_of_scope
end
elsif block and args.empty? # we're making an accessor into an array of Instances
if current_scope.send("#{accessor}").nil?
current_scope.send("#{accessor}=", [])
end
collection = current_scope.send("#{accessor}")
append_scope_with collection
instance_eval(&block)
jump_out_of_scope
else # it's just a plain old assignment to the accessor
if args.length == 1
current_scope.send("#{accessor}=", *args)
else # it looks like someone is trying to pass an array... so
#
# THIS BIT IS FUCKED, take this crap out, it makes the API
# way too confusing and inconsistent
#
current_scope.send("#{accessor}=", args)
end
end
else # scope is an Array, so create an Instance
klass = args.shift
# :w

if @bits and @bits.any?
module_scope = @bits.collect { |bit| classify bit.to_s }.join('::')
instance = eval("#{module_scope}::#{classify klass.to_s}").new(*args)
@bits = nil
else
instance = eval(classify(klass.to_s)).new(*args)
end
current_scope << instance # add this instance to the scoped Array
if block # we got a block, change scope to set accessors
append_scope_with instance
instance_eval(&block)
jump_out_of_scope
end
end
instance = eval(classify(klass.to_s)).new(*args)
end
current_scope << instance # add this instance to the scoped Array
assign_accessors_within_scope(instance, &block) if block
end

def assign_accessors_within_scope(instance, &block)
evaluate_block_within_context(instance, &block)
end

def assign_array_of_subvalues_to_accessor(accessor, &block)
evaluate_block_within_context(current_accessor(accessor), &block)
end

def evaluate_block_within_context(accessor, &block)
append_scope_with accessor
instance_eval(&block)
jump_out_of_scope
end

def within_a_scope?
@scope_stack and @scope_stack.any?
end

def assign_to_instance_with(*args, &block)
accessor = args.shift
case instance_assignment_behaviour_for(accessor, args, block)
when :assign_subvalues_to_instance
assign_subvalues_to_instance(accessor, args, &block)
when :assign_array_of_subvalues_to_accessor
assign_array_of_subvalues_to_accessor(accessor, &block)
when :assign_directly_to_accessor
current_scope.send("#{accessor}=", *args)
when :assign_array_directly_to_accessor
current_scope.send("#{accessor}=", args)
else
raise "unknown assignment behaviour '#{assignment_behaviour_for(accessor)}' for accessor '#{acccessor}'"
end
end

def current_accessor(accessor)
if current_accessor_undefined?(accessor)
set_current_accessor_as_empty_array(accessor)
end
current_scope.send("#{accessor}")
end

def set_current_accessor_as_empty_array(accessor)
current_scope.send("#{accessor}=", [])
end

def current_accessor_undefined?(accessor)
current_scope.send("#{accessor}").nil?
end

def assign_subvalues_to_instance(accessor, args, &block)
instance = eval(classify(accessor.to_s[0...-1])).new(args)
current_scope.send("#{accessor.to_s[0...-1]}=", instance)
set_accessors_within_scope(instance, &block) if block
end

def set_accessors_within_scope(instance, &block)
append_scope_with instance
instance_eval(&block)
jump_out_of_scope
end

def instance_assignment_behaviour_for(accessor, args, block)
if accessor.to_s[-1].chr == '!' #accessor is an actual instance
:assign_subvalues_to_instance
elsif block and args.empty? #accessor is to refer to an array
:assign_array_of_subvalues_to_accessor
elsif args.length == 1
:assign_directly_to_accessor
else
:assign_array_directly_to_accessor
end
end


def current_scope_type
# we're assuming any scope that responds to << must be a collection,
current_scope.respond_to?(:<<) ? :array : :instance
end

def push_to_scope_stack_and_return_self(args)
(@scope_stack ||= []) << args[0]
self
end

def still_modifying_scope?(args, block)
# if we only have one arg, and no block, then we're trying to build a
# module scope, i.e. a/b/c/d would resolve to A::B::C::D,
args.length == 1 and block.nil?
end

private

def prepare_scope
@scope = [@initial_scope]
end

def append_scope_with(new_scope)
scope.push new_scope
end

def jump_out_of_scope
scope.pop
end

def current_scope
scope.last
end

def tree
scope.first
end

def scope
@scope
end
Expand All @@ -145,5 +200,5 @@ def classify(str)
camels = str.split('_')
camels.collect { |c| c.capitalize }.join
end
end

end

0 comments on commit 960c447

Please sign in to comment.