autocode 1.0.0 → 1.0.1
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/lib/autocode.rb +96 -41
- data/lib/rack/reload.rb +17 -0
- data/test/auto_create.rb +49 -9
- data/test/auto_eval.rb +2 -1
- data/test/auto_load.rb +27 -9
- data/test/helpers.rb +2 -14
- data/test/introspection.rb +15 -0
- data/test/least_surprise.rb +61 -0
- metadata +7 -3
data/lib/autocode.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
module AutoCode
|
2
2
|
|
3
|
+
def self.monitor
|
4
|
+
require 'monitor'
|
5
|
+
@monitor ||= Monitor.new
|
6
|
+
end
|
7
|
+
|
3
8
|
# always make sure we have a camel-cased symbol
|
4
9
|
def AutoCode.normalize( cname )
|
5
10
|
return cname unless cname.is_a? Symbol or cname.is_a? String
|
6
|
-
camel_case( cname )
|
11
|
+
camel_case( cname ).intern
|
7
12
|
end
|
8
13
|
|
9
14
|
def AutoCode.camel_case( cname )
|
10
|
-
cname.to_s.
|
15
|
+
cname.to_s.split('_').map do |word|
|
16
|
+
"#{word.slice(/^\w/).upcase}#{word.slice(/^\w(\w+)/, 1)}"
|
17
|
+
end.join
|
11
18
|
end
|
12
19
|
|
13
20
|
def AutoCode.snake_case( cname )
|
@@ -19,23 +26,57 @@ module AutoCode
|
|
19
26
|
def self.included( mod )
|
20
27
|
|
21
28
|
mod.instance_eval do
|
22
|
-
|
23
|
-
# First make sure we haven't already done this once
|
24
|
-
return unless @autocode.nil?
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
AutoCode.monitor.enter
|
31
|
+
begin
|
32
|
+
# First make sure we haven't already done this once
|
33
|
+
return unless @autocode.nil?
|
34
|
+
|
35
|
+
# Initialize bookkeeping variables needed by AutoCode
|
36
|
+
@autocode = {
|
37
|
+
:constructors => Hash.new { |h,k| h[k] = [] },
|
38
|
+
:initializers => Hash.new { |h,k| h[k] = [] },
|
39
|
+
:loaded => []
|
40
|
+
}
|
41
|
+
ensure
|
42
|
+
AutoCode.monitor.exit
|
43
|
+
end
|
44
|
+
|
45
|
+
def auto_constructor( key = true, &block)
|
46
|
+
@autocode[:constructors][ AutoCode.normalize( key ) ] << block
|
47
|
+
end
|
32
48
|
|
33
|
-
# Adds an auto_create block for the given key using the given exemplar if provided
|
34
49
|
def auto_create( key = true, options = {}, &block )
|
35
|
-
|
36
|
-
exemplar =
|
37
|
-
exemplar.
|
38
|
-
|
50
|
+
auto_constructor( key ) do | cname |
|
51
|
+
exemplar = options[:exemplar] || Module.new
|
52
|
+
new_constant = exemplar.clone
|
53
|
+
new_constant.send(:include, AutoCode)
|
54
|
+
ret = const_set( cname, new_constant )
|
55
|
+
new_constant.module_eval( &block ) if block
|
56
|
+
ret
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Convenience method for auto_creating classes.
|
61
|
+
def auto_create_class( key = true, parent = Object, &block )
|
62
|
+
auto_constructor( key ) do | cname |
|
63
|
+
parent = const_get(parent) unless parent.is_a? Class
|
64
|
+
new_constant = Class.new( parent )
|
65
|
+
new_constant.send(:include, AutoCode)
|
66
|
+
ret = const_set( cname, new_constant )
|
67
|
+
new_constant.module_eval( &block ) if block
|
68
|
+
ret
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Convenience method for auto_creating modules.
|
73
|
+
def auto_create_module( key = true, &block )
|
74
|
+
auto_constructor( key ) do | cname |
|
75
|
+
new_constant = Module.new
|
76
|
+
new_constant.send(:include, AutoCode)
|
77
|
+
ret = const_set( cname, new_constant )
|
78
|
+
new_constant.module_eval( &block ) if block
|
79
|
+
ret
|
39
80
|
end
|
40
81
|
end
|
41
82
|
|
@@ -43,54 +84,68 @@ module AutoCode
|
|
43
84
|
def auto_load( key = true, options = {} )
|
44
85
|
@autocode[:constructors][ AutoCode.normalize( key ) ] << lambda do | cname |
|
45
86
|
filename = AutoCode.snake_case( cname ) << '.rb'
|
46
|
-
|
47
|
-
|
87
|
+
options[:directories] ||= '.'
|
88
|
+
path = if target = options[:target]
|
89
|
+
target.call(cname)
|
48
90
|
else
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
Kernel.load( path ) unless path.nil?
|
91
|
+
options[:directories].
|
92
|
+
map { |dir| File.join( dir.to_s, filename ) }.
|
93
|
+
find { |path| File.exist?( path ) }
|
53
94
|
end
|
95
|
+
Kernel.load( path ) unless path.nil?
|
54
96
|
end
|
55
97
|
end
|
56
98
|
|
57
99
|
# Adds an arbitrary initializer block for the given key
|
58
100
|
def auto_eval( key, &block )
|
59
|
-
|
60
|
-
|
101
|
+
if key.is_a?( Symbol) && const_defined?( AutoCode.normalize( key ) )
|
102
|
+
const_get( key ).module_eval( &block)
|
103
|
+
else
|
104
|
+
@autocode[:initializers][ AutoCode.normalize( key ) ] << lambda do | mod |
|
105
|
+
mod.module_eval( &block )
|
106
|
+
end
|
61
107
|
end
|
62
108
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
auto_create( key,{ :exemplar => Class.new( superclass ) }, &block )
|
67
|
-
end
|
68
|
-
|
69
|
-
# Convenience method for auto_create.
|
70
|
-
def auto_create_module( key = true, &block )
|
71
|
-
auto_create( key,{ :exemplar => Module.new }, &block )
|
109
|
+
|
110
|
+
def auto_const?(cname)
|
111
|
+
true unless @autocode[:constructors][ AutoCode.normalize( cname ) ].empty?
|
72
112
|
end
|
73
113
|
|
74
114
|
# Returns the list of constants that would be reloaded upon a call to reload.
|
75
115
|
def reloadable ; @autocode[:loaded] ; end
|
76
116
|
|
77
117
|
# Reloads (via #remove_const) all the constants that were loaded via auto_code.
|
78
|
-
def reload
|
118
|
+
def reload
|
119
|
+
Autocode.monitor.synchronize do
|
120
|
+
@autocode[:loaded].each { |name| remove_const( name ) } ; @autocode[:loaded] = []
|
121
|
+
end
|
122
|
+
end
|
79
123
|
|
80
124
|
private
|
81
125
|
|
82
126
|
old = method( :const_missing )
|
83
127
|
(class << self ; self ; end ).instance_eval do
|
84
128
|
define_method( :const_missing ) do | cname |
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
129
|
+
|
130
|
+
Autocode.monitor.synchronize do
|
131
|
+
# check to see if some other thread loaded the constant before
|
132
|
+
# we entered the lock.
|
133
|
+
return const_get( cname ) if const_defined?( cname )
|
134
|
+
actions = []
|
135
|
+
@autocode[:constructors].each do |matcher,action|
|
136
|
+
actions.concat action if (matcher == true || matcher == cname || matcher === cname.to_s )
|
137
|
+
end
|
138
|
+
actions.reverse!.find { | c | c.call( cname ) and const_defined?( cname ) }
|
139
|
+
return old.call( cname ) unless const_defined?( cname )
|
140
|
+
initializers = @autocode[:initializers][true] + @autocode[:initializers][cname]
|
141
|
+
mod = const_get( cname ); initializers.each { |init| init.call( mod ) }
|
142
|
+
@autocode[:loaded] << cname
|
143
|
+
mod
|
144
|
+
end
|
91
145
|
end
|
92
146
|
end
|
93
147
|
end
|
94
148
|
end
|
95
149
|
end
|
96
|
-
Autocode = AutoCode
|
150
|
+
Autocode = AutoCode
|
151
|
+
|
data/lib/rack/reload.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
module Rack
|
4
|
+
class Reload
|
5
|
+
|
6
|
+
def initialize(app, *reloadable)
|
7
|
+
@app = app
|
8
|
+
@reloadable = reloadable
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
@reloadable.each { |mod| mod.reload }
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/test/auto_create.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
2
|
|
3
3
|
describe "auto_create" do
|
4
4
|
|
@@ -7,22 +7,62 @@ describe "auto_create" do
|
|
7
7
|
module A
|
8
8
|
include AutoCode
|
9
9
|
auto_create_module :B do
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
def self.smurf; "blue" ; end
|
12
|
+
|
13
|
+
auto_create_module true do
|
14
|
+
def self.pixie; "brown" ; end
|
15
|
+
end
|
16
|
+
|
12
17
|
end
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
16
|
-
|
17
|
-
A::B.
|
21
|
+
it "allows you create and initialize a given const name" do
|
22
|
+
A::B.smurf.should == "blue"
|
18
23
|
end
|
19
24
|
|
20
|
-
|
21
|
-
A::B::C.
|
25
|
+
it "allows you create and initialize const using a wildcard" do
|
26
|
+
A::B::C.pixie.should == "brown"
|
22
27
|
end
|
23
28
|
|
24
|
-
|
25
|
-
|
29
|
+
it "allows you to refer to auto-constructed constants using symbols" do
|
30
|
+
module K
|
31
|
+
include AutoCode
|
32
|
+
auto_create_class true, :C
|
33
|
+
auto_create_class :C
|
34
|
+
end
|
35
|
+
K::C
|
36
|
+
K::D.superclass.should == K::C
|
37
|
+
end
|
38
|
+
|
39
|
+
it "lets you use Module.name in the init block" do
|
40
|
+
module L
|
41
|
+
include AutoCode
|
42
|
+
auto_create_class true do
|
43
|
+
auto_create_class self.name.split("::").last.upcase.reverse do
|
44
|
+
def self.foo; "bar" ; end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
L::WONK::KNOW.foo.should == "bar"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raises a NameError if a const doesn't match" do
|
52
|
+
lambda{ A::C }.should raise_error( NameError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "can take a Regexp as a key" do
|
56
|
+
module M
|
57
|
+
include AutoCode
|
58
|
+
auto_create_module(/V\d+/) do
|
59
|
+
def self.thing
|
60
|
+
name.split("::").last.reverse
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
M::V2.thing.should == "2V"
|
65
|
+
M::V3.thing.should == "3V"
|
26
66
|
end
|
27
67
|
|
28
68
|
end
|
data/test/auto_eval.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
2
|
|
3
3
|
describe "auto_eval" do
|
4
4
|
|
@@ -27,5 +27,6 @@ describe "auto_eval" do
|
|
27
27
|
specify "allows you to define nested auto_eval declarations" do
|
28
28
|
A::B::C::D.should == true
|
29
29
|
end
|
30
|
+
|
30
31
|
|
31
32
|
end
|
data/test/auto_load.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
2
|
require 'extensions/io'
|
3
3
|
|
4
4
|
|
@@ -15,12 +15,6 @@ describe "auto_load" do
|
|
15
15
|
end
|
16
16
|
EOF
|
17
17
|
File.write( @path, content )
|
18
|
-
module A
|
19
|
-
include AutoCode
|
20
|
-
auto_create_class :B
|
21
|
-
auto_load :B, :directories => ['tmp']
|
22
|
-
end
|
23
|
-
|
24
18
|
end
|
25
19
|
|
26
20
|
after do
|
@@ -29,15 +23,39 @@ describe "auto_load" do
|
|
29
23
|
end
|
30
24
|
|
31
25
|
specify "allows you to load a file to define a const" do
|
26
|
+
module A
|
27
|
+
include AutoCode
|
28
|
+
auto_create_class :B
|
29
|
+
auto_load :B, :directories => ['tmp']
|
30
|
+
end
|
32
31
|
A::B.class.should == Module
|
33
32
|
end
|
34
33
|
|
35
34
|
specify "should implement LIFO semantics" do
|
35
|
+
module A
|
36
|
+
include AutoCode
|
37
|
+
auto_create_class :B
|
38
|
+
auto_load :B, :directories => ['tmp']
|
39
|
+
end
|
36
40
|
A::B.class.should == Module
|
37
41
|
end
|
38
42
|
|
39
43
|
specify "raises a NameError if a const doesn't match" do
|
40
|
-
|
44
|
+
module A
|
45
|
+
include AutoCode
|
46
|
+
auto_create_class :B
|
47
|
+
auto_load :B, :directories => ['tmp']
|
48
|
+
end
|
49
|
+
lambda{ A::C }.should raise_error( NameError )
|
50
|
+
end
|
51
|
+
|
52
|
+
it "can use a lambda to specify the target file" do
|
53
|
+
module A
|
54
|
+
include AutoCode
|
55
|
+
auto_create_class :B
|
56
|
+
auto_load :B, :target => lambda {|cname| "tmp/#{cname.to_s.downcase}.rb" }
|
57
|
+
end
|
58
|
+
A::B.class.should == Module
|
41
59
|
end
|
42
60
|
|
43
|
-
end
|
61
|
+
end
|
data/test/helpers.rb
CHANGED
@@ -1,15 +1,3 @@
|
|
1
|
-
|
2
|
-
%w{ bacon }.each { |dep| require dep }
|
3
|
-
# Bacon.extend Bacon::TestUnitOutput
|
4
|
-
Bacon.summary_on_exit
|
1
|
+
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
5
2
|
|
6
|
-
|
7
|
-
private
|
8
|
-
def specification(name, &block) Bacon::Context.new(name, &block) end
|
9
|
-
end
|
10
|
-
|
11
|
-
Bacon::Context.instance_eval do
|
12
|
-
alias_method :specify, :it
|
13
|
-
end
|
14
|
-
|
15
|
-
require "#{File.dirname(__FILE__)}/../lib/autocode"
|
3
|
+
require 'autocode'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
|
+
|
3
|
+
describe "A module that has included AutoCode" do
|
4
|
+
|
5
|
+
it "can tell you whether a subconstant has an auto-constructor" do
|
6
|
+
module A
|
7
|
+
include AutoCode
|
8
|
+
auto_create_module :B
|
9
|
+
end
|
10
|
+
|
11
|
+
A.auto_const?(:B).should be_true
|
12
|
+
A.auto_const?(:C).should_not be_true
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/helpers"
|
2
|
+
|
3
|
+
describe "An auto_created module" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Object.instance_eval { remove_const(:A) if const_defined?(:A) }
|
7
|
+
module A
|
8
|
+
module Foo; end
|
9
|
+
include AutoCode
|
10
|
+
auto_create_class :B do
|
11
|
+
def kobold
|
12
|
+
"koboldy"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "automatically includes AutoCode" do
|
19
|
+
A::B.included_modules.should include( AutoCode )
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can be initialized by later auto_eval declarations" do
|
23
|
+
A.auto_eval(:B) { def smurf; "smurfy" ; end }
|
24
|
+
A::B.new.kobold.should == "koboldy"
|
25
|
+
A::B.new.smurf.should == "smurfy"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can be referenced in auto_* declarations without getting born" do
|
29
|
+
A.auto_create_class :C, :B
|
30
|
+
A.const_defined?(:B).should == false
|
31
|
+
A::C.new.kobold.should == "koboldy"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "auto_eval" do
|
37
|
+
|
38
|
+
it "does a module_eval for constants that are already defined" do
|
39
|
+
A.auto_eval :Foo do
|
40
|
+
def self.yell; "Brick!" ; end
|
41
|
+
end
|
42
|
+
A::Foo.yell.should == "Brick!"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
from_waves = lambda do
|
49
|
+
app.auto_create_module( :Views ) do
|
50
|
+
include AutoCode
|
51
|
+
auto_create_class :Default, Waves::Views::Base
|
52
|
+
auto_load :Default, :directories => [ :views ]
|
53
|
+
auto_create_class true, :Default
|
54
|
+
auto_load true, :directories => [ :views ]
|
55
|
+
end
|
56
|
+
app.auto_eval :Views do
|
57
|
+
auto_eval :Default do
|
58
|
+
include ViewMethods
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autocode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Yoder
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date:
|
14
|
+
date: 2009-08-30 00:00:00 -07:00
|
15
15
|
default_executable:
|
16
16
|
dependencies: []
|
17
17
|
|
@@ -25,10 +25,14 @@ extra_rdoc_files: []
|
|
25
25
|
|
26
26
|
files:
|
27
27
|
- lib/autocode.rb
|
28
|
+
- lib/rack
|
29
|
+
- lib/rack/reload.rb
|
28
30
|
- test/auto_create.rb
|
29
31
|
- test/auto_eval.rb
|
30
32
|
- test/auto_load.rb
|
31
33
|
- test/helpers.rb
|
34
|
+
- test/introspection.rb
|
35
|
+
- test/least_surprise.rb
|
32
36
|
- test/normalize.rb
|
33
37
|
has_rdoc: true
|
34
38
|
homepage: http://dev.zeraweb.com/
|
@@ -52,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
56
|
requirements: []
|
53
57
|
|
54
58
|
rubyforge_project: autocode
|
55
|
-
rubygems_version: 1.
|
59
|
+
rubygems_version: 1.3.1
|
56
60
|
signing_key:
|
57
61
|
specification_version: 2
|
58
62
|
summary: Utility for auto-including, reloading, and generating classes and modules.
|