friend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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