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 +22 -0
- data/README.md +56 -0
- data/Rakefile +24 -0
- data/example/export_features.rb +23 -0
- data/example/friends.rb +35 -0
- data/ext/callsite.bundle +0 -0
- data/ext/callsite.c +30 -0
- data/ext/callsite.h +174 -0
- data/ext/callsite.o +0 -0
- data/ext/extconf.rb +3 -0
- data/lib/friend.rb +30 -0
- data/test/test_callsite.rb +17 -0
- data/test/test_export.rb +34 -0
- metadata +68 -0
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.
|
data/README.md
ADDED
@@ -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 © 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.
|
data/Rakefile
ADDED
@@ -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>'
|
data/example/friends.rb
ADDED
@@ -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>'
|
data/ext/callsite.bundle
ADDED
Binary file
|
data/ext/callsite.c
ADDED
@@ -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
|
+
}
|
data/ext/callsite.h
ADDED
@@ -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 */
|
data/ext/callsite.o
ADDED
Binary file
|
data/ext/extconf.rb
ADDED
data/lib/friend.rb
ADDED
@@ -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
|
data/test/test_export.rb
ADDED
@@ -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
|