Skip to content

Instantly share code, notes, and snippets.

@lsegal
Created April 1, 2010 05:41
Show Gist options
  • Select an option

  • Save lsegal/351422 to your computer and use it in GitHub Desktop.

Select an option

Save lsegal/351422 to your computer and use it in GitHub Desktop.
Eiffel-like export features syntax for fine-grained method visibility
class A; def bar; D.new.foo end end
class B; def bar; D.new.foo end end
class C; def bar; D.new.foo end end
class D
def foo; "HELLO WORLD!" end
# Export `foo` to A and B, but not C
export :foo, A, B
end
puts A.new.bar
puts B.new.bar
puts C.new.bar
# Output:
#
# HELLO WORLD!
# HELLO WORLD!
# export_features.rb:3:in `bar': `foo' is not accessible to C:Class (NoMethodError)
# from export_features.rb:14:in `<main>'
require_relative 'ext/callsite'
module Export
def export_list
@__exported__ ||= {}
end
def export(meth, *classes)
meth = meth.to_s
(export_list[meth] ||= []).push(*classes)
alias_method "__exported__#{meth}", meth
define_method(meth) do |*args, &block|
cclass, classes = caller_class, self.class.export_list[meth]
if classes && !classes.any? {|k| cclass >= k }
raise NoMethodError,
"`#{meth}' is not accessible to #{cclass.inspect}:#{cclass.class.inspect}",
caller
end
send("__exported__#{meth}", *args, &block)
end
end
end
BasicObject.send(:extend, Export)
#include "callsite.h"
extern rb_thread_t *ruby_current_thread;
VALUE
callsite_caller_class(VALUE self)
{
rb_control_frame_t *cfp = ruby_current_thread->cfp;
/* pop 2 stack frames */
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
if (cfp->iseq != 0 && cfp->pc != 0) {
return cfp->iseq->klass;
}
else if (cfp->block_iseq) {
while (!cfp->iseq) cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
return cfp->iseq->klass;
}
return Qnil;
}
void
Init_callsite()
{
rb_define_method(rb_mKernel, "caller_class", callsite_caller_class, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment