Initial cut of the "define" API with blocks
This has taken a lot of experimenting and research to get to, and it has to resort to clever method_missing hacks because it appears to be absolutely impossible to take a binding from a block and mutate it. Original appraoch looked something like this: define :log, message: String do with # ... logic end The `with` method couldn't access the right caller's binding (using clever hacks: <http://rubychallenger.blogspot.com/2011/07/caller-binding.html>) A follow up approach: define :log, message: String do with binding # ... logic end Wouldn't work because the binding object ID inside the block would change as soon as the with method would mutata it. Thus the current approach, which is probably not going to perform worth a shit
This commit is contained in:
parent
5d005c2c7f
commit
eb4df666fc
9
Gemfile
9
Gemfile
|
@ -2,3 +2,12 @@ source 'https://rubygems.org'
|
|||
|
||||
# Specify your gem's dependencies in typedeaf.gemspec
|
||||
gemspec
|
||||
|
||||
gem 'facets'
|
||||
|
||||
group :development do
|
||||
gem 'rake'
|
||||
gem 'rspec'
|
||||
gem 'pry'
|
||||
gem 'debugger-pry', :platform => :mri
|
||||
end
|
||||
|
|
5
Rakefile
5
Rakefile
|
@ -1,2 +1,7 @@
|
|||
require "bundler/gem_tasks"
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
RSpec::Core::RakeTask.new
|
||||
|
||||
task :default => [:spec, :build]
|
||||
|
||||
|
|
|
@ -1,5 +1,71 @@
|
|||
require 'typedeaf/errors'
|
||||
require "typedeaf/version"
|
||||
|
||||
require 'continuation'
|
||||
require 'facets/binding'
|
||||
|
||||
module Typedeaf
|
||||
# Your code goes here...
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def __typedeaf_varstack__
|
||||
if @__typedeaf_varstack__.nil?
|
||||
@__typedeaf_varstack__ = []
|
||||
end
|
||||
return @__typedeaf_varstack__
|
||||
end
|
||||
|
||||
def method_missing(sym, *args)
|
||||
# We only want to peek at the stack if we have no args (i.e. not trying
|
||||
# to make a method call
|
||||
if args.empty? && !(__typedeaf_varstack__.empty?)
|
||||
params, values = __typedeaf_varstack__.last
|
||||
# The top of our stack contains something that we want
|
||||
return values[sym] if params[sym]
|
||||
end
|
||||
|
||||
return super
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def define(method_sym, params={}, &block)
|
||||
if block.nil?
|
||||
raise MissingMethodException,
|
||||
"You must provide a block for the #{method_sym} body"
|
||||
end
|
||||
|
||||
define_method(method_sym) do |*args|
|
||||
param_indices = {}
|
||||
params.each.with_index do |(key, value), index|
|
||||
param_indices[key] = args[index]
|
||||
end
|
||||
__typedeaf_varstack__ << [params, param_indices]
|
||||
begin
|
||||
instance_exec(*args, &block)
|
||||
ensure
|
||||
__typedeaf_varstack__.pop
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
# Install the Typedeaf methods onto Class to be used everywhere
|
||||
def self.global_install
|
||||
Class.class_eval do
|
||||
include Typedeaf
|
||||
|
||||
alias_method :old_inherited, :inherited
|
||||
def inherited(subclass)
|
||||
subclass.class_eval do
|
||||
include Typedeaf
|
||||
end
|
||||
return old_inherited(subclass)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
module Typedeaf
|
||||
class MissingMethodException < StandardError; end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
require 'rubygems'
|
||||
require 'typedeaf'
|
||||
|
||||
unless RUBY_PLATFORM == 'java'
|
||||
require 'debugger/pry'
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Typedeaf do
|
||||
subject(:klass) do
|
||||
Class.new do
|
||||
include Typedeaf
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a new class' do
|
||||
it { should be_kind_of Class }
|
||||
it { should respond_to :define }
|
||||
end
|
||||
|
||||
context 'defining typedeaf instance methods' do
|
||||
subject(:instance) { klass.new }
|
||||
|
||||
context 'defining a method with an invalid block' do
|
||||
it 'should raise a MissingMethodException' do
|
||||
expect {
|
||||
klass.class_eval do
|
||||
define :log
|
||||
end
|
||||
}.to raise_error(Typedeaf::MissingMethodException)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#context 'defining a method with no arguments' do
|
||||
# before :each do
|
||||
# klass.class_eval do
|
||||
# tdef :log do
|
||||
# 'hello rspec'
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
# it { should respond_to :log }
|
||||
|
||||
# it 'should return the right value when invoked' do
|
||||
# expect(instance.log).to eql('hello rspec')
|
||||
# end
|
||||
#end
|
||||
|
||||
|
||||
context 'defining a method with arguments' do
|
||||
before :each do
|
||||
klass.class_eval do
|
||||
define :log, message: String do
|
||||
"hello #{message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it { should respond_to :log }
|
||||
it 'should use the arguments to generate a result' do
|
||||
puts "instance: #{instance.object_id}"
|
||||
expect(instance.log('world')).to eql('hello world')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue