rkh-mixico 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/COPYING +22 -0
- data/README +173 -0
- data/ext/mixico/extconf.rb +6 -0
- data/ext/mixico/mixico.c +106 -0
- data/setup.rb +1585 -0
- metadata +57 -0
data/COPYING
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2008 why the lucky stiff
|
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 restriction,
|
6
|
+
including without limitation the rights to use, copy, modify, merge,
|
7
|
+
publish, distribute, sublicense, and/or sell copies of the Software,
|
8
|
+
and to permit persons to whom the Software is furnished to do so,
|
9
|
+
subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
15
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
16
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
17
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
18
|
+
SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
19
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
20
|
+
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
% mixico %
|
2
|
+
disable and re-enable mixins for ruby.
|
3
|
+
|
4
|
+
% quick summary %
|
5
|
+
there's no way i'm keeping this short. i'm sorry, but you're going to
|
6
|
+
have to really read this once.
|
7
|
+
|
8
|
+
% installation %
|
9
|
+
with gem:
|
10
|
+
$ gem sources -a http://gems.github.com
|
11
|
+
$ sudo gem install rkh-mixico
|
12
|
+
|
13
|
+
without:
|
14
|
+
$ ruby setup.rb config
|
15
|
+
$ ruby setup.rb setup
|
16
|
+
$ sudo ruby setup.rb install
|
17
|
+
|
18
|
+
% source code %
|
19
|
+
mixico is written in c. it is very quick (basically atomic.)
|
20
|
+
|
21
|
+
it is released under an MIT license. see COPYING.
|
22
|
+
|
23
|
+
% the long of it %
|
24
|
+
this is just a small bit of code and it has yet to prove itself, but
|
25
|
+
i believe this discovery could spawn a small following. i assure you:
|
26
|
+
it is practical.
|
27
|
+
|
28
|
+
while mining through the ftp site of the recently departed guy decoux,
|
29
|
+
an exquisite french hacker, i read of a lost module for reparenting
|
30
|
+
modules in different directions. this 'prop' extension is most heavily
|
31
|
+
elucidated in a thread beginning with [ruby-talk:20293]. the extension
|
32
|
+
itself is vanished, so we are left with a sort example and a diagram.
|
33
|
+
|
34
|
+
the diagram appears in a reply to hal fulton (who asks where this
|
35
|
+
'insert' method is that guy is using between modules):
|
36
|
+
|
37
|
+
from [ruby-talk:20296]:
|
38
|
+
|
39
|
+
>>>>> "H" == Hal E Fulton <hal9000 / hypermetrics.com> writes:
|
40
|
+
|
41
|
+
H> What is "insert" anyway? Did you mean "include"
|
42
|
+
H> or did I miss something?
|
43
|
+
|
44
|
+
No, this not "include" but really "insert" (i.e. another way)
|
45
|
+
|
46
|
+
For 2 classes A < B you have
|
47
|
+
|
48
|
+
|
49
|
+
extend put the class here
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
v
|
54
|
+
meta-A <========= meta-B
|
55
|
+
^ ^
|
56
|
+
insert put | |
|
57
|
+
the class ====> | |
|
58
|
+
here | |
|
59
|
+
A <========= B
|
60
|
+
^
|
61
|
+
|
|
62
|
+
|
|
63
|
+
include put the class here
|
64
|
+
|
65
|
+
You have method specifics to the metaclass
|
66
|
+
|
67
|
+
even if we had the code for 'prop', we wouldn't be able to build
|
68
|
+
it. these messages date back to 25 aug 2001, when ruby 1.6.4 was
|
69
|
+
current.
|
70
|
+
|
71
|
+
i began trying to recreate this intriguing work by trying to get
|
72
|
+
guy's example code to work. while i've not yet completed that work,
|
73
|
+
i stumbled upon another idea.
|
74
|
+
|
75
|
+
when a module is mixed into an object, the module itself appears
|
76
|
+
to be in the inheritance chain (Module.ancestors):
|
77
|
+
|
78
|
+
module Mixin; end
|
79
|
+
|
80
|
+
class GuineaPig; end
|
81
|
+
|
82
|
+
sylvain = GuineaPig.new
|
83
|
+
sylvain.extend Mixin
|
84
|
+
class << sylvain
|
85
|
+
p ancestors
|
86
|
+
end
|
87
|
+
|
88
|
+
the printed list for test subject 'sylvain' is:
|
89
|
+
[Mixin, GuineaPig, Object, Kernel]
|
90
|
+
|
91
|
+
this means 'sylvain' will respond to methods in the Mixin module
|
92
|
+
first, then in the GuineaPig class and so on up. however, the
|
93
|
+
Mixin module isn't REALLY in the inheritance chain. ruby creates
|
94
|
+
an object of type T_ICLASS that is a proxy class. a symbolic link
|
95
|
+
to the Mixin module.
|
96
|
+
|
97
|
+
sylvain ==> #<Mixin> ==> GuineaPig ==> Object ==> Kernel
|
98
|
+
|
99
|
+
the #<Mixin> object is useless in actual Ruby. you can't print it out.
|
100
|
+
it doesn't have any methods. you can pass it around in a variable, but
|
101
|
+
that's it.
|
102
|
+
|
103
|
+
again, it's a symbolic link to the Mixin module and its superclass is
|
104
|
+
GuineaPig. one of these objects is created every time you mixin.
|
105
|
+
|
106
|
+
on to this:
|
107
|
+
|
108
|
+
require 'mixico'
|
109
|
+
|
110
|
+
so, mixico adds two methods: disable_mixin and enable_mixin.
|
111
|
+
|
112
|
+
class << sylvain
|
113
|
+
@m = disable_mixin Mixin
|
114
|
+
p ancestors
|
115
|
+
end
|
116
|
+
|
117
|
+
which prints: [GuineaPig, Object, Kernel]
|
118
|
+
|
119
|
+
class << sylvain
|
120
|
+
enable_mixin @m
|
121
|
+
p ancestors
|
122
|
+
end
|
123
|
+
|
124
|
+
which prints: [Mixin, GuineaPig, Object, Kernel]
|
125
|
+
|
126
|
+
% how is this practical? %
|
127
|
+
|
128
|
+
my immediate concern is to stop using instance_eval. i use it in
|
129
|
+
markaby a lot. and it gets used once in shoes.
|
130
|
+
|
131
|
+
instance_eval can help give you syntax like this:
|
132
|
+
|
133
|
+
Markaby.html do
|
134
|
+
head do
|
135
|
+
title "feral cat colonies worldwide"
|
136
|
+
meta :name => "ROBOTS", :content => "ALL"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
nice, readable tags, right?
|
141
|
+
|
142
|
+
it's nice because instance_eval redirects all your methods inside
|
143
|
+
the block to a specific object. the problem is that it alters `self`
|
144
|
+
and redirects instance and class variables as well.
|
145
|
+
|
146
|
+
if you've got an instance variable you want to use inside the block,
|
147
|
+
you need to save it in a local variable before entering the block.
|
148
|
+
same with `self`.
|
149
|
+
|
150
|
+
def to_html
|
151
|
+
obj = self
|
152
|
+
Markaby.html do
|
153
|
+
head do
|
154
|
+
title obj.friendly_title
|
155
|
+
meta :name => "ROBOTS", :content => "ALL"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
this is annoying to remember. it's often the source of bugs.
|
161
|
+
|
162
|
+
mixico, on the other hand, can be used to redirect the methods
|
163
|
+
without changing `self` and swallowing up the ivars.
|
164
|
+
|
165
|
+
module Mixin
|
166
|
+
def head ...
|
167
|
+
def title ...
|
168
|
+
def meta ...
|
169
|
+
end
|
170
|
+
|
171
|
+
we you enable the mixin with mixico, it'll send the methods through
|
172
|
+
the proxy class. and when you disable it, you're back to normal.
|
173
|
+
no sign of the mixin at all.
|
data/ext/mixico/mixico.c
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
//
|
2
|
+
// mixico.c
|
3
|
+
// an extension for enabling and disabling mixins
|
4
|
+
//
|
5
|
+
// released under an MIT license
|
6
|
+
//
|
7
|
+
#include <ruby.h>
|
8
|
+
|
9
|
+
static VALUE mixin_eval, mixout_eval;
|
10
|
+
|
11
|
+
static VALUE
|
12
|
+
rb_mod_disable_mixin(VALUE module, VALUE super)
|
13
|
+
{
|
14
|
+
VALUE p, kid;
|
15
|
+
|
16
|
+
Check_Type(super, T_MODULE);
|
17
|
+
for (kid = module, p = RCLASS(module)->super; p; kid = p, p = RCLASS(p)->super) {
|
18
|
+
if (BUILTIN_TYPE(p) == T_ICLASS) {
|
19
|
+
if (RBASIC(p)->klass == super) {
|
20
|
+
RCLASS(kid)->super = RCLASS(p)->super;
|
21
|
+
rb_clear_cache_by_class(module);
|
22
|
+
return p;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
return Qnil;
|
28
|
+
}
|
29
|
+
|
30
|
+
static VALUE
|
31
|
+
rb_mod_enable_mixin(VALUE module, VALUE mixin)
|
32
|
+
{
|
33
|
+
VALUE p;
|
34
|
+
|
35
|
+
Check_Type(mixin, T_ICLASS);
|
36
|
+
Check_Type(RBASIC(mixin)->klass, T_MODULE);
|
37
|
+
for (p = module; p; p = RCLASS(p)->super) {
|
38
|
+
if (RCLASS(p)->super == RCLASS(mixin)->super) {
|
39
|
+
RCLASS(p)->super = mixin;
|
40
|
+
rb_clear_cache_by_class(module);
|
41
|
+
return RBASIC(mixin)->klass;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
return Qnil;
|
46
|
+
}
|
47
|
+
|
48
|
+
static VALUE
|
49
|
+
rb_mod_mixin_object(VALUE target, VALUE obj)
|
50
|
+
{
|
51
|
+
VALUE singleton = rb_singleton_class(obj);
|
52
|
+
NEWOBJ(iclass, struct RClass);
|
53
|
+
OBJSETUP(iclass, rb_cClass, T_ICLASS);
|
54
|
+
|
55
|
+
Check_Type(target, T_MODULE);
|
56
|
+
if (!ROBJECT(obj)->iv_tbl)
|
57
|
+
ROBJECT(obj)->iv_tbl = st_init_numtable();
|
58
|
+
|
59
|
+
iclass->iv_tbl = ROBJECT(obj)->iv_tbl;
|
60
|
+
iclass->m_tbl = RCLASS(singleton)->m_tbl;
|
61
|
+
iclass->super = RCLASS(target)->super;
|
62
|
+
RBASIC(iclass)->klass = singleton;
|
63
|
+
|
64
|
+
OBJ_INFECT(iclass, obj);
|
65
|
+
OBJ_INFECT(iclass, target);
|
66
|
+
RCLASS(target)->super = iclass;
|
67
|
+
rb_clear_cache_by_class(target);
|
68
|
+
|
69
|
+
return Qnil;
|
70
|
+
}
|
71
|
+
|
72
|
+
void Init_mixico()
|
73
|
+
{
|
74
|
+
rb_define_method(rb_cModule, "disable_mixin", rb_mod_disable_mixin, 1);
|
75
|
+
rb_define_method(rb_cModule, "enable_mixin", rb_mod_enable_mixin, 1);
|
76
|
+
rb_define_method(rb_cModule, "mixin_an_object", rb_mod_mixin_object, 1);
|
77
|
+
|
78
|
+
rb_eval_string(
|
79
|
+
"class Proc\n" \
|
80
|
+
" def includes_mixin? mod\n" \
|
81
|
+
" (class << binding.eval('self'); self end).include? mod\n" \
|
82
|
+
" end\n" \
|
83
|
+
" def mixin mod\n" \
|
84
|
+
" binding.eval('self').extend mod\n" \
|
85
|
+
" end\n" \
|
86
|
+
" def mixout mod\n" \
|
87
|
+
" (class << binding.eval('self'); self end).disable_mixin mod\n" \
|
88
|
+
" end\n" \
|
89
|
+
"end\n" \
|
90
|
+
"\n" \
|
91
|
+
"class Module\n" \
|
92
|
+
" def mix_eval mod, &blk\n" \
|
93
|
+
" if blk.includes_mixin? mod\n" \
|
94
|
+
" blk.call\n" \
|
95
|
+
" else\n" \
|
96
|
+
" blk.mixin mod\n" \
|
97
|
+
" begin\n" \
|
98
|
+
" blk.call\n" \
|
99
|
+
" ensure\n" \
|
100
|
+
" blk.mixout mod\n" \
|
101
|
+
" end\n" \
|
102
|
+
" end\n" \
|
103
|
+
" end\n" \
|
104
|
+
"end\n" \
|
105
|
+
);
|
106
|
+
}
|