friend 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Loren Segal
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ # Friend
2
+
3
+ Adds fine-grained visibility semantics to Ruby, allowing you to make methods
4
+ visible only to specific classes (and their subclasses).
5
+
6
+ ## Installing
7
+
8
+ $ [sudo] gem install friend
9
+
10
+ Or build it manually:
11
+
12
+ $ git clone git://github.com/lsegal/friend
13
+ $ cd friend
14
+ $ rake install
15
+
16
+ ## Using
17
+
18
+ To use the library, require it and call `friend` (aka. `export`) from
19
+ any module/class to export a method to a list of specific classes in the
20
+ form:
21
+
22
+ export :methodname, Class1, Class2, ...
23
+
24
+ ## Example
25
+
26
+ require 'friend'
27
+
28
+ class A; def bar; D.new.foo end end
29
+ class B; def bar; D.new.foo end end
30
+ class C; def bar; D.new.foo end end
31
+
32
+ class D
33
+ def foo; "HELLO WORLD!" end
34
+
35
+ # Export to A and B, but not C
36
+ export :foo, A, B
37
+ end
38
+
39
+ puts A.new.bar
40
+ puts B.new.bar
41
+ puts C.new.bar
42
+
43
+ # Output:
44
+ #
45
+ # HELLO WORLD!
46
+ # HELLO WORLD!
47
+ # export_features.rb:5:in `bar': `foo' is not accessible to C:Class (NoMethodError)
48
+ # from export_features.rb.rb:16:in `<main>'
49
+
50
+ More examples can be found in the `example/` directory.
51
+
52
+ ## License & Author
53
+
54
+ Friend is written by Loren Segal &copy; 2010, licensed under the BSD license.
55
+ This software can be redistributed and modified so long as it maintains the
56
+ copyright notice and `LICENSE` file. See `LICENSE` for more information.
@@ -0,0 +1,24 @@
1
+ require 'rake/testtask'
2
+
3
+ WINDOWS = (PLATFORM =~ /win32|cygwin/ ? true : false) rescue false
4
+ SUDO = WINDOWS ? '' : 'sudo'
5
+
6
+ task :default => :test
7
+ task :test => :build
8
+
9
+ Rake::TestTask.new
10
+
11
+ task :package => :build do
12
+ sh "gem build friend.gemspec"
13
+ end
14
+
15
+ desc "Install the gem locally"
16
+ task :install => :package do
17
+ sh "#{SUDO} gem install pkg/#{SPEC.name}-#{SPEC.version}.gem --local"
18
+ sh "rm -rf pkg/#{SPEC.name}-#{SPEC.version}" unless ENV['KEEP_FILES']
19
+ end
20
+
21
+ desc 'Build'
22
+ task :build do
23
+ sh "cd ext && make"
24
+ end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../lib/friend'
2
+
3
+ class A; def bar; D.new.foo end end
4
+ class B; def bar; D.new.foo end end
5
+ class C; def bar; D.new.foo end end
6
+
7
+ class D
8
+ def foo; "HELLO WORLD!" end
9
+
10
+ # Export to A and B, but not C
11
+ export :foo, A, B
12
+ end
13
+
14
+ puts A.new.bar
15
+ puts B.new.bar
16
+ puts C.new.bar
17
+
18
+ # Output:
19
+ #
20
+ # HELLO WORLD!
21
+ # HELLO WORLD!
22
+ # export_features.rb:5:in `bar': `foo' is not accessible to C:Class (NoMethodError)
23
+ # from export_features.rb.rb:16:in `<main>'
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../lib/friend'
2
+
3
+ class Car
4
+ def initialize; @engine = Engine.new end
5
+
6
+ def turn_on(key)
7
+ if key == "foo"
8
+ @engine.engage
9
+ puts "Car turned on!"
10
+ else
11
+ puts "Wrong key!"
12
+ end
13
+ end
14
+ end
15
+
16
+ class Engine
17
+ private
18
+ def engage; puts "Engine turned on!" end
19
+ friend :engage, Car
20
+ end
21
+
22
+ # We can enable the engine if we
23
+ # turn the car on with the right key
24
+ car = Car.new
25
+ car.turn_on("foo")
26
+
27
+ # But we can't enable the engine directly
28
+ car.instance_variable_get("@engine").engage
29
+
30
+ # Output:
31
+ # Engine turned on!
32
+ # Car turned on!
33
+ # export.rb:17:in `block in export': `engage' is not accessible outside
34
+ # Engine (NoMethodError)
35
+ # from friends.rb:35:in `<main>'
Binary file
@@ -0,0 +1,30 @@
1
+ #include "callsite.h"
2
+
3
+ VALUE
4
+ callsite_caller_class(VALUE self)
5
+ {
6
+ #ifdef RUBY19
7
+ rb_control_frame_t *cfp = ruby_current_thread->cfp;
8
+ /* pop 2 stack frames */
9
+ cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
10
+ cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
11
+
12
+ /* pop any block stack frames */
13
+ while (!cfp->iseq) cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
14
+
15
+ if (cfp->iseq != 0 && cfp->pc != 0) {
16
+ return cfp->iseq->klass;
17
+ }
18
+ return Qnil;
19
+ #else /* RUBY18 */
20
+ struct FRAME *frame = ruby_frame->prev->prev;
21
+ if (frame->flags == 1) frame = frame->prev;
22
+ return frame->last_class;
23
+ #endif /* RUBY19 */
24
+ }
25
+
26
+ void
27
+ Init_callsite()
28
+ {
29
+ rb_define_method(rb_mKernel, "caller_class", callsite_caller_class, 0);
30
+ }
@@ -0,0 +1,174 @@
1
+ #include <ruby.h>
2
+
3
+ #ifdef RUBY19
4
+
5
+ #define VM_FRAME_MAGIC_METHOD 0x11
6
+ #define VM_FRAME_MAGIC_BLOCK 0x21
7
+ #define VM_FRAME_MAGIC_CLASS 0x31
8
+ #define VM_FRAME_MAGIC_TOP 0x41
9
+ #define VM_FRAME_MAGIC_FINISH 0x51
10
+ #define VM_FRAME_MAGIC_CFUNC 0x61
11
+ #define VM_FRAME_MAGIC_PROC 0x71
12
+ #define VM_FRAME_MAGIC_IFUNC 0x81
13
+ #define VM_FRAME_MAGIC_EVAL 0x91
14
+ #define VM_FRAME_MAGIC_LAMBDA 0xa1
15
+ #define VM_FRAME_MAGIC_MASK_BITS 8
16
+ #define VM_FRAME_MAGIC_MASK (~(~0<<VM_FRAME_MAGIC_MASK_BITS))
17
+
18
+ #define VM_FRAME_TYPE(cfp) ((cfp)->flag & VM_FRAME_MAGIC_MASK)
19
+
20
+ /* other frame flag */
21
+ #define VM_FRAME_FLAG_PASSED 0x0100
22
+
23
+
24
+ #define RUBYVM_CFUNC_FRAME_P(cfp) \
25
+ (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_CFUNC)
26
+
27
+ #define RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp) (cfp+1)
28
+ #define RUBY_VM_NEXT_CONTROL_FRAME(cfp) (cfp-1)
29
+
30
+ typedef struct rb_iseq_struct rb_iseq_t;
31
+
32
+ struct rb_iseq_struct;
33
+
34
+ struct rb_iseq_struct {
35
+ /***************/
36
+ /* static data */
37
+ /***************/
38
+
39
+ VALUE type; /* instruction sequence type */
40
+ VALUE name; /* String: iseq name */
41
+ VALUE filename; /* file information where this sequence from */
42
+ VALUE *iseq; /* iseq (insn number and openrads) */
43
+ VALUE *iseq_encoded; /* encoded iseq */
44
+ unsigned long iseq_size;
45
+ VALUE mark_ary; /* Array: includes operands which should be GC marked */
46
+ VALUE coverage; /* coverage array */
47
+ unsigned short line_no;
48
+
49
+ /* insn info, must be freed */
50
+ void /*struct iseq_insn_info_entry*/ *insn_info_table;
51
+ unsigned long insn_info_size;
52
+
53
+ ID *local_table; /* must free */
54
+ int local_table_size;
55
+
56
+ /* method, class frame: sizeof(vars) + 1, block frame: sizeof(vars) */
57
+ int local_size;
58
+
59
+ /**
60
+ * argument information
61
+ *
62
+ * def m(a1, a2, ..., aM, # mandatory
63
+ * b1=(...), b2=(...), ..., bN=(...), # optinal
64
+ * *c, # rest
65
+ * d1, d2, ..., dO, # post
66
+ * &e) # block
67
+ * =>
68
+ *
69
+ * argc = M
70
+ * arg_rest = M+N+1 // or -1 if no rest arg
71
+ * arg_opts = N
72
+ * arg_opts_tbl = [ (N entries) ]
73
+ * arg_post_len = O // 0 if no post arguments
74
+ * arg_post_start = M+N+2
75
+ * arg_block = M+N + 1 + O + 1 // -1 if no block arg
76
+ * arg_simple = 0 if not simple arguments.
77
+ * = 1 if no opt, rest, post, block.
78
+ * = 2 if ambiguos block parameter ({|a|}).
79
+ * arg_size = argument size.
80
+ */
81
+
82
+ int argc;
83
+ int arg_simple;
84
+ int arg_rest;
85
+ int arg_block;
86
+ int arg_opts;
87
+ int arg_post_len;
88
+ int arg_post_start;
89
+ int arg_size;
90
+ VALUE *arg_opt_table;
91
+
92
+ int stack_max; /* for stack overflow check */
93
+
94
+ /* catch table */
95
+ void /*struct iseq_catch_table_entry*/ *catch_table;
96
+ int catch_table_size;
97
+
98
+ /* for child iseq */
99
+ struct rb_iseq_struct *parent_iseq;
100
+ struct rb_iseq_struct *local_iseq;
101
+
102
+ /****************/
103
+ /* dynamic data */
104
+ /****************/
105
+
106
+ VALUE self;
107
+ VALUE orig; /* non-NULL if its data have origin */
108
+
109
+ /* block inlining */
110
+ /*
111
+ * NODE *node;
112
+ * void *special_block_builder;
113
+ * void *cached_special_block_builder;
114
+ * VALUE cached_special_block;
115
+ */
116
+
117
+ /* klass/module nest information stack (cref) */
118
+ void/*NODE*/ *cref_stack;
119
+ VALUE klass;
120
+
121
+ /* misc */
122
+ ID defined_method_id; /* for define_method */
123
+
124
+ /* used at compile time */
125
+ void /*struct iseq_compile_data*/ *compile_data;
126
+ };
127
+
128
+ typedef struct {
129
+ VALUE *pc; /* cfp[0] */
130
+ VALUE *sp; /* cfp[1] */
131
+ VALUE *bp; /* cfp[2] */
132
+ rb_iseq_t *iseq; /* cfp[3] */
133
+ VALUE flag; /* cfp[4] */
134
+ VALUE self; /* cfp[5] / block[0] */
135
+ VALUE *lfp; /* cfp[6] / block[1] */
136
+ VALUE *dfp; /* cfp[7] / block[2] */
137
+ rb_iseq_t *block_iseq; /* cfp[8] / block[3] */
138
+ VALUE proc; /* cfp[9] / block[4] */
139
+ ID method_id; /* cfp[10] saved in special case */
140
+ VALUE method_class; /* cfp[11] saved in special case */
141
+ } rb_control_frame_t;
142
+
143
+ typedef struct rb_thread_struct
144
+ {
145
+ VALUE self;
146
+ void *vm;
147
+
148
+ /* execution information */
149
+ VALUE *stack; /* must free, must mark */
150
+ unsigned long stack_size;
151
+ rb_control_frame_t *cfp;
152
+
153
+ /* INCOMPLETE! */
154
+ } rb_thread_t;
155
+
156
+ extern rb_thread_t *ruby_current_thread;
157
+
158
+ #else /* RUBY18 */
159
+
160
+ extern struct FRAME {
161
+ VALUE self;
162
+ int argc;
163
+ ID last_func;
164
+ ID orig_func;
165
+ VALUE last_class;
166
+ struct FRAME *prev;
167
+ struct FRAME *tmp;
168
+ struct RNode *node;
169
+ int iter;
170
+ int flags;
171
+ unsigned long uniq;
172
+ } *ruby_frame;
173
+
174
+ #endif /* RUBY19 */
Binary file
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+ $CPPFLAGS += " -DRUBY19" if RUBY_VERSION =~ /1.9/
3
+ create_makefile('callsite')
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../ext/callsite'
2
+
3
+ module Friend
4
+ def friend_list
5
+ @__friends__ ||= {}
6
+ end
7
+
8
+ def friend(meth, *classes)
9
+ meth, expmeth = meth.to_s, "__friends__#{meth}"
10
+ (friend_list[meth] ||= []).push(*classes)
11
+ alias_method expmeth, meth
12
+ private expmeth
13
+ public meth
14
+ define_method(meth) do |*args, &block|
15
+ cclass, classes = caller_class, self.class.friend_list[meth]
16
+ if cclass == false
17
+ raise NoMethodError, "`#{meth}' is not accessible outside #{self.class}"
18
+ elsif cclass != self.class && classes && !classes.any? {|k| cclass <= k }
19
+ raise NoMethodError,
20
+ "`#{meth}' is not accessible to #{cclass.inspect}:#{cclass.class.inspect}",
21
+ caller
22
+ end
23
+ send(expmeth, *args, &block)
24
+ end
25
+ end
26
+
27
+ alias export friend
28
+ end
29
+
30
+ (RUBY_VERSION >= "1.9.1" ? BasicObject : Object).send(:extend, Friend)
@@ -0,0 +1,17 @@
1
+ require "test/unit"
2
+ require File.dirname(__FILE__) + '/../ext/callsite'
3
+
4
+ class CA; def foo; caller_class end end
5
+ class CB; def bar; CA.new.foo end end
6
+ module CD; def zoo; define_method(:bar) {|*args, &block| CA.new.foo } end end
7
+ class CC; extend CD; zoo end
8
+
9
+ class TestCallsite < Test::Unit::TestCase
10
+ def test_callsite
11
+ assert_equal CB, CB.new.bar
12
+ end
13
+
14
+ def test_callsite_from_define_method
15
+ assert_equal CC, CC.new.bar
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ require "test/unit"
2
+ require File.dirname(__FILE__) + '/../lib/friend'
3
+
4
+ class EA; def bar; ED.new.foo end end
5
+ class EB; def bar; ED.new.foo end end
6
+ class EC; def bar; ED.new.foo end end
7
+ class EE < EB; def bar; ED.new.foo end end
8
+ class ED
9
+ def foo; "foobar" end
10
+ def bar; foo end
11
+ export :foo, EA, EB
12
+ end
13
+
14
+ class TestCallsite < Test::Unit::TestCase
15
+ def test_export_A
16
+ assert_equal "foobar", EA.new.bar
17
+ end
18
+
19
+ def test_export_B
20
+ assert_equal "foobar", EB.new.bar
21
+ end
22
+
23
+ def test_export_C
24
+ assert_raises(NoMethodError) { EC.new.bar }
25
+ end
26
+
27
+ def test_export_E
28
+ assert_equal "foobar", EE.new.bar
29
+ end
30
+
31
+ def test_local_call
32
+ assert_equal "foobar", ED.new.bar
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: friend
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Loren Segal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-01 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: lsegal@soen.ca
18
+ executables: []
19
+
20
+ extensions:
21
+ - ext/extconf.rb
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - ext/callsite.bundle
26
+ - ext/callsite.c
27
+ - ext/callsite.h
28
+ - ext/callsite.o
29
+ - ext/extconf.rb
30
+ - lib/friend.rb
31
+ - example/export_features.rb
32
+ - example/friends.rb
33
+ - test/test_callsite.rb
34
+ - test/test_export.rb
35
+ - LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ has_rdoc: true
39
+ homepage: http://github.com/lsegal/friend
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - ext
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Friend adds fine grained visibility semantics to Ruby
66
+ test_files:
67
+ - test/test_callsite.rb
68
+ - test/test_export.rb