camping 2.1.467 → 2.1.523
Sign up to get free protection for your applications and to get access to all the features.
- data/{README → README.md} +45 -43
- data/Rakefile +20 -1
- data/book/{01_introduction → 01_introduction.md} +4 -4
- data/book/{02_getting_started → 02_getting_started.md} +196 -196
- data/book/{51_upgrading → 51_upgrading.md} +54 -54
- data/lib/camping-unabridged.rb +7 -7
- data/lib/camping.rb +4 -4
- data/lib/camping/mab.rb +24 -13
- data/lib/camping/reloader.rb +98 -128
- data/lib/camping/server.rb +22 -89
- data/test/app_helpers.rb +12 -1
- data/test/app_markup.rb +54 -0
- data/test/app_reloader.rb +70 -0
- data/test/app_simple.rb +12 -0
- data/test/apps/reloader.rb +7 -0
- data/test/apps/reloader/config.ru +5 -0
- data/test/apps/reloader/reload_me.rb +2 -0
- data/test/apps/reloader_indirect.rb +3 -0
- data/test/test_helper.rb +4 -5
- metadata +67 -55
@@ -1,36 +1,36 @@
|
|
1
|
-
|
1
|
+
# Appendix I: Upgrade Notes
|
2
2
|
|
3
3
|
This document includes everything needed in order to *upgrade* your
|
4
4
|
applications. If you're looking for all the new features in a version, please
|
5
5
|
have a look at the CHANGELOG in the Camping source.
|
6
6
|
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
##From 2.0 to 2.1
|
9
|
+
###Options
|
10
10
|
|
11
11
|
In Camping 2.1 there is now a built-in way to store options and settings. If
|
12
12
|
you use cookie session, it means that you'll now have to change to:
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
module Nuts
|
15
|
+
set :secret, "Very secret text, which no-one else should know!"
|
16
|
+
include Camping::Session
|
17
|
+
end
|
18
18
|
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
##From 1.5 to 2.0
|
21
|
+
###Rack
|
22
22
|
|
23
|
-
The biggest change in 2.0 is that it now uses Rack
|
23
|
+
The biggest change in 2.0 is that it now uses [Rack](http://rack.rubyforge.org/)
|
24
24
|
internally. This means that you'll now have to deploy Camping differently, but
|
25
25
|
hopefully more easily too. Now every Camping application is also a Rack
|
26
26
|
application, so simply check out the documentation to the server of your
|
27
27
|
choice.
|
28
28
|
|
29
|
-
|
29
|
+
###`require 'camping/db'`
|
30
30
|
|
31
31
|
In earlier versions of Camping, you loaded database support by:
|
32
32
|
|
33
|
-
|
33
|
+
require 'camping/db'
|
34
34
|
|
35
35
|
Actually, this loaded a very thin layer on top of ActiveRecord, and in the
|
36
36
|
future we want to experiment with other libraries. Therefore you should now
|
@@ -40,48 +40,48 @@ also means you'll have to place your migrations *after* the models.
|
|
40
40
|
We also encourage you to use <tt>Model.table_name</tt> instead of
|
41
41
|
<tt>:appname_model</tt>, just to make sure it's named correctly.
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
module Nuts::Models
|
47
|
-
## Just inherit Base:
|
48
|
-
class Page < Base; end
|
43
|
+
## Don't require anything:
|
44
|
+
# require 'camping/db'
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
create_table Page.table_name do |t|
|
54
|
-
...
|
55
|
-
end
|
56
|
-
end
|
46
|
+
module Nuts::Models
|
47
|
+
## Just inherit Base:
|
48
|
+
class Page < Base; end
|
57
49
|
|
58
|
-
|
59
|
-
|
50
|
+
## Migrations have to come *after* the models:
|
51
|
+
class CreateTheBasics < V 0.1
|
52
|
+
def self.up
|
53
|
+
create_table Page.table_name do |t|
|
54
|
+
...
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.down
|
59
|
+
drop_table Page.table_name
|
60
|
+
end
|
60
61
|
end
|
61
62
|
end
|
62
|
-
end
|
63
63
|
|
64
|
-
|
64
|
+
###Cookie Sessions
|
65
65
|
|
66
66
|
Camping 2.0 now uses a cookie-based session system, which means you now longer
|
67
67
|
need a database in order to use sessions. The disadvantage of this is that
|
68
68
|
you are restricted to only around 4k of data. See below for the changes
|
69
69
|
required, and see Camping::Session more details.
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
71
|
+
module Nuts
|
72
|
+
## Include Camping::Session as before:
|
73
|
+
include Camping::Session
|
74
|
+
|
75
|
+
## But also define a secret:
|
76
|
+
secret "Very secret text, which no-one else should know!"
|
77
|
+
end
|
78
|
+
|
79
|
+
def Nuts.create
|
80
|
+
## And remove the following line:
|
81
|
+
# Camping::Models::Session.create_schema
|
82
|
+
end
|
83
83
|
|
84
|
-
|
84
|
+
###Error handling
|
85
85
|
|
86
86
|
Camping now uses three methods in order to handle errors. These replaces the
|
87
87
|
old classes NotFound and ServerError.
|
@@ -93,18 +93,18 @@ old classes NotFound and ServerError.
|
|
93
93
|
|
94
94
|
You can override these in your application:
|
95
95
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
96
|
+
module Nuts
|
97
|
+
def r404(path)
|
98
|
+
"Sorry, but I can't find #{path}."
|
99
|
+
end
|
100
|
+
|
101
|
+
def r501(method)
|
102
|
+
"Sorry, but I can't respond to #{method}."
|
103
|
+
end
|
104
|
+
|
105
|
+
def r500(klass, method, ex)
|
106
|
+
"Sorry, but #{klass}##{method} failed with #{ex}."
|
107
|
+
end
|
107
108
|
end
|
108
|
-
end
|
109
109
|
|
110
110
|
It should be noted that this might change in the future.
|
data/lib/camping-unabridged.rb
CHANGED
@@ -201,7 +201,7 @@ module Camping
|
|
201
201
|
def R(c,*g)
|
202
202
|
p,h=/\(.+?\)/,g.grep(Hash)
|
203
203
|
g-=h
|
204
|
-
raise "bad route"
|
204
|
+
raise "bad route" if !u = c.urls.find{|x|
|
205
205
|
break x if x.scan(p).size == g.size &&
|
206
206
|
/^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
|
207
207
|
x.sub p,U.escape((a.to_param rescue a))}.gsub(/\\(.)/){$1})
|
@@ -242,7 +242,7 @@ module Camping
|
|
242
242
|
def URL c='/',*a
|
243
243
|
c = R(c, *a) if c.respond_to? :urls
|
244
244
|
c = self/c
|
245
|
-
c = @request.url[/.{8,}?(
|
245
|
+
c = @request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/
|
246
246
|
URI(c)
|
247
247
|
end
|
248
248
|
end
|
@@ -291,7 +291,7 @@ module Camping
|
|
291
291
|
def render(v, *a, &b)
|
292
292
|
if t = lookup(v)
|
293
293
|
r, @_r = @_r, o = Hash === a[-1] ? a.pop : {}
|
294
|
-
s = (t == true) ? mab{ send(v, *a, &b) } : t.render(self, o[:locals] || {}, &b)
|
294
|
+
s = (t == true) ? mab { send(v, *a, &b) } : t.render(self, o[:locals] || {}, &b)
|
295
295
|
s = render(L, o.merge(L => false)) { s } if o[L] or o[L].nil? && lookup(L) && (!r && v.to_s[0] != ?_)
|
296
296
|
s
|
297
297
|
else
|
@@ -310,7 +310,8 @@ module Camping
|
|
310
310
|
#
|
311
311
|
# You can also pass true to use the :layout HTML wrapping method
|
312
312
|
def mab(&b)
|
313
|
-
|
313
|
+
extend Mab
|
314
|
+
mab(&b)
|
314
315
|
end
|
315
316
|
|
316
317
|
# A quick means of setting this controller's status, body and headers
|
@@ -567,7 +568,7 @@ module Camping
|
|
567
568
|
@r.map { |k|
|
568
569
|
k.urls.map { |x|
|
569
570
|
return (k.method_defined?(m)) ?
|
570
|
-
[k, m, *$~[1..-1]] : [I, 'r501', m] if p =~ /^#{x}\/?$/
|
571
|
+
[k, m, *$~[1..-1].map{|x|U.unescape(x)}] : [I, 'r501', m] if p =~ /^#{x}\/?$/
|
571
572
|
}
|
572
573
|
}
|
573
574
|
[I, 'r404', p]
|
@@ -632,8 +633,7 @@ module Camping
|
|
632
633
|
# See: http://rack.rubyforge.org/doc/SPEC.html
|
633
634
|
def call(e)
|
634
635
|
X.M
|
635
|
-
|
636
|
-
k,m,*a=X.D p,e['REQUEST_METHOD'].downcase,e
|
636
|
+
k,m,*a=X.D e['PATH_INFO'],e['REQUEST_METHOD'].downcase,e
|
637
637
|
k.new(e,m).service(*a).to_a
|
638
638
|
rescue
|
639
639
|
r500(:I, k, m, $!, :env => e).to_a
|
data/lib/camping.rb
CHANGED
@@ -10,7 +10,7 @@ end;module Helpers;def R c,*g;p,h=
|
|
10
10
|
x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|x.sub p,U.escape((a.
|
11
11
|
to_param rescue a))}.gsub(/\\(.)/){$1})};h.any?? u+"?"+U.build_query(h[0]):u
|
12
12
|
end;def / p;p[0]==?/?@root+p :p end;def URL c='/',*a;c=R(c,*a) if c.respond_to?(
|
13
|
-
:urls);c=self/c;c=@request.url[/.{8,}?(
|
13
|
+
:urls);c=self/c;c=@request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/;URI c end end
|
14
14
|
module Base;attr_accessor:env,:request,:root,:input,:cookies,:state,:status,
|
15
15
|
:headers,:body;T={};L=:layout;def lookup n;T.fetch(n.to_sym){|k|t=Views.
|
16
16
|
method_defined?(k)||(t=O[:_t].keys.grep(/^#{n}\./)[0]and Template[t].new{
|
@@ -19,7 +19,7 @@ new(f,O[f[/\.(\w+)$/,1].to_sym]||{});O[:dynamic_templates]?t:T[k]=t} end
|
|
19
19
|
def render v,*a,&b;if t=lookup(v);r,@_r=@_r,o=Hash===a[-1]?a.pop: {};s=(t==true)?mab{
|
20
20
|
send v,*a,&b}: t.render(self,o[:locals]||{},&b);s=render(L,o.merge(L=>false)){s
|
21
21
|
}if o[L]or o[L].nil?&&lookup(L)&&!r&&v.to_s[0]!=?_;s;else;raise"no template: #{v}"
|
22
|
-
end;end;def mab &b;(
|
22
|
+
end;end;def mab &b;extend(Mab);mab(&b) end;def r s,b,h={};b,h=
|
23
23
|
h,b if Hash===b;@status=s;@headers.merge!(h);@body=b end;def redirect *a;r 302,
|
24
24
|
'','Location'=>URL(*a).to_s end;def r404 p;P%"#{p} not found"end;def r500 k,m,e
|
25
25
|
raise e end;def r501 m;P%"#{m.upcase} not implemented"end;def serve(p,c)
|
@@ -34,7 +34,7 @@ n(v);m}: h end;def service *a;r=catch(:halt){send(@method,*a)};@body||=r;self
|
|
34
34
|
end end;module Controllers;@r=[];class<<self;def R *u;r=@r;Class.
|
35
35
|
new{meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end;def D p,m,e;p='/'if
|
36
36
|
!p||!p[0];(a=O[:_t].find{|n,_|n==p}) and return [I,:serve,*a]
|
37
|
-
@r.map{|k|k.urls.map{|x|return(k.method_defined? m)?[k,m,*$~[1..-1]]:
|
37
|
+
@r.map{|k|k.urls.map{|x|return(k.method_defined? m)?[k,m,*$~[1..-1].map{|x|U.unescape x}]:
|
38
38
|
[I, 'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p] end;N=H.new{|_,x|x.downcase}.
|
39
39
|
merge!("N"=>'(\d+)',"X"=>'([^/]+)',"Index"=>'');def M;def M;end;constants.
|
40
40
|
map{|c|k=const_get(c);k.send:include,C,X,Base,Helpers,Models
|
@@ -44,7 +44,7 @@ Controllers;class<<self;def
|
|
44
44
|
goes m;Apps<<a=eval(S.gsub(/Camping/,m.to_s),TOPLEVEL_BINDING);caller[0]=~/:/
|
45
45
|
IO.read(a.set:__FILE__,$`)=~/^__END__/&&(b=$'.split /^@@\s*(.+?)\s*\r?\n/m).shift rescue nil
|
46
46
|
a.set :_t,H[*b||[]];end;def call e;X.M
|
47
|
-
|
47
|
+
k,m,*a=X.D e["PATH_INFO"],e['REQUEST_METHOD'].
|
48
48
|
downcase,e;k.new(e,m).service(*a).to_a;rescue;r500(:I,k,m,$!,:env=>e).to_a end
|
49
49
|
def method_missing m,c,*a;X.M;h=Hash===a[-1]?a.pop: {};e=H[Rack::MockRequest.
|
50
50
|
env_for('',h.delete(:env)||{})];k=X.const_get(c).new(e,m.to_s);h.each{|i,v|k.
|
data/lib/camping/mab.rb
CHANGED
@@ -1,23 +1,34 @@
|
|
1
1
|
class MissingLibrary < Exception #:nodoc: all
|
2
2
|
end
|
3
3
|
begin
|
4
|
-
|
4
|
+
require 'mab'
|
5
5
|
rescue LoadError => e
|
6
|
-
|
6
|
+
raise MissingLibrary, "Mab could not be loaded (is it installed?): #{e.message}"
|
7
7
|
end
|
8
8
|
|
9
|
-
$MAB_CODE = %{
|
10
|
-
|
11
|
-
|
12
|
-
# path.
|
13
|
-
class Mab < Markaby::Builder
|
14
|
-
attr_reader :builder
|
15
|
-
|
9
|
+
$MAB_CODE = %q{
|
10
|
+
module Mab
|
11
|
+
include ::Mab::Mixin::HTML5
|
16
12
|
include Views
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
|
14
|
+
alias << text!
|
15
|
+
|
16
|
+
def xhtml(*a, &b)
|
17
|
+
warn "xhtml_strict is no longer supported (or an active standard); using HTML5 instead"
|
18
|
+
html(*a, &b)
|
19
|
+
end
|
20
|
+
|
21
|
+
def xhtml_strict(*a, &b) xhtml(*a, &b) end
|
22
|
+
def xhtml_transitional(*a, &b) xhtml(*a, &b) end
|
23
|
+
def xhtml_frameset(*a, &b) xhtml(*a, &b) end
|
24
|
+
|
25
|
+
def helpers() self end
|
26
|
+
|
27
|
+
def html(*) doctype!; super end
|
28
|
+
|
29
|
+
def mab_done(tag)
|
30
|
+
h=tag.attributes
|
31
|
+
[:href,:action,:src].map{|a|h[a]&&=self/h[a]}
|
21
32
|
end
|
22
33
|
end
|
23
34
|
}
|
data/lib/camping/reloader.rb
CHANGED
@@ -32,157 +32,127 @@ module Camping
|
|
32
32
|
#
|
33
33
|
# You can also give Reloader more than one script.
|
34
34
|
class Reloader
|
35
|
-
attr_reader :
|
35
|
+
attr_reader :file
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
37
|
+
def initialize(file, &blk)
|
38
|
+
@file = file
|
39
|
+
@mtime = Time.at(0)
|
40
|
+
@requires = []
|
41
|
+
@apps = {}
|
42
|
+
@callback = blk
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
@name ||= begin
|
47
|
+
base = @file.dup
|
48
|
+
base = File.dirname(base) if base =~ /\bconfig\.ru$/
|
49
|
+
base.sub!(/\.[^.]+/, '')
|
50
|
+
File.basename(base).to_sym
|
51
51
|
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Loads the apps availble in this script. Use <tt>apps</tt> to get
|
55
|
+
# the loaded apps.
|
56
|
+
def load_apps(old_apps)
|
57
|
+
all_requires = $LOADED_FEATURES.dup
|
58
|
+
all_apps = Camping::Apps.dup
|
59
|
+
|
60
|
+
load_file
|
61
|
+
ensure
|
62
|
+
@requires = []
|
63
|
+
dirs = []
|
64
|
+
new_apps = Camping::Apps - all_apps
|
52
65
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
66
|
+
@apps = new_apps.inject({}) do |hash, app|
|
67
|
+
if file = app.options[:__FILE__]
|
68
|
+
full = File.expand_path(file)
|
69
|
+
@requires << [file, full]
|
70
|
+
dirs << full.sub(/\.[^.]+$/, '')
|
58
71
|
end
|
59
72
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
begin
|
64
|
-
load(@file)
|
65
|
-
rescue Exception => e
|
66
|
-
puts "!! Error loading #{@file}:"
|
67
|
-
puts "#{e.class}: #{e.message}"
|
68
|
-
puts e.backtrace
|
69
|
-
puts "!! Error loading #{@file}, see backtrace above"
|
70
|
-
end
|
73
|
+
key = app.name.to_sym
|
74
|
+
hash[key] = app
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
if !old_apps.include?(key)
|
77
|
+
@callback.call(app) if @callback
|
78
|
+
app.create if app.respond_to?(:create)
|
75
79
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
new_apps = (Camping::Apps - all_apps)
|
80
|
-
old_apps = @apps.dup
|
81
|
-
|
82
|
-
@apps = new_apps.inject({}) do |hash, app|
|
83
|
-
key = app.name.to_sym
|
84
|
-
hash[key] = app
|
85
|
-
|
86
|
-
if !old_apps.include?(key)
|
87
|
-
@callback.call(app) if @callback
|
88
|
-
app.create if app.respond_to?(:create)
|
89
|
-
end
|
90
|
-
hash
|
91
|
-
end
|
92
|
-
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
|
-
# Removes all the apps defined in this script.
|
97
|
-
def remove_apps
|
98
|
-
@apps.each do |name, app|
|
99
|
-
Camping::Apps.delete(app)
|
100
|
-
Object.send :remove_const, name
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# Reloads the file if needed. No harm is done by calling this multiple
|
105
|
-
# times, so feel free call just to be sure.
|
106
|
-
def reload!
|
107
|
-
return if @mtime >= mtime
|
108
|
-
remove_apps
|
109
|
-
load_apps
|
110
|
-
end
|
111
|
-
|
112
|
-
# Checks if both scripts watches the same file.
|
113
|
-
def ==(other)
|
114
|
-
@file == other.file
|
80
|
+
|
81
|
+
hash
|
115
82
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
(@requires + [@file]).compact.map do |fname|
|
121
|
-
File.mtime(fname)
|
122
|
-
end.reject{|t| t > Time.now }.max
|
83
|
+
|
84
|
+
($LOADED_FEATURES - all_requires).each do |req|
|
85
|
+
full = full_path(req)
|
86
|
+
@requires << [req, full] if dirs.any? { |x| full.index(x) == 0 }
|
123
87
|
end
|
88
|
+
|
89
|
+
@mtime = mtime
|
124
90
|
|
125
|
-
|
126
|
-
def full_path(req)
|
127
|
-
dir = $LOAD_PATH.detect { |l| File.exists?(File.join(l, req)) }
|
128
|
-
if dir
|
129
|
-
File.expand_path(req, File.expand_path(dir))
|
130
|
-
else
|
131
|
-
req
|
132
|
-
end
|
133
|
-
end
|
91
|
+
self
|
134
92
|
end
|
135
93
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
update(*scripts)
|
94
|
+
def load_file
|
95
|
+
if @file =~ /\.ru$/
|
96
|
+
@app,_ = Rack::Builder.parse_file(@file)
|
97
|
+
else
|
98
|
+
load(@file)
|
99
|
+
end
|
143
100
|
end
|
144
101
|
|
145
|
-
|
146
|
-
|
102
|
+
# Removes all the apps defined in this script.
|
103
|
+
def remove_apps
|
104
|
+
@requires.each do |(path, full)|
|
105
|
+
$LOADED_FEATURES.delete(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
@apps.each do |name, app|
|
109
|
+
Camping::Apps.delete(app)
|
110
|
+
Object.send :remove_const, name
|
111
|
+
end.dup
|
112
|
+
ensure
|
113
|
+
@apps.clear
|
147
114
|
end
|
148
115
|
|
149
|
-
#
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
old_scripts = @scripts.dup
|
154
|
-
clear
|
155
|
-
|
156
|
-
@scripts = scripts.map do |script|
|
157
|
-
file = File.expand_path(script)
|
158
|
-
old_scripts.detect { |s| s.file == file } or
|
159
|
-
Script.new(script, @callback)
|
160
|
-
end
|
161
|
-
|
116
|
+
# Reloads the file if needed. No harm is done by calling this multiple
|
117
|
+
# times, so feel free call just to be sure.
|
118
|
+
def reload
|
119
|
+
return if @mtime >= mtime rescue nil
|
162
120
|
reload!
|
163
121
|
end
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
@scripts = []
|
168
|
-
@apps = {}
|
122
|
+
|
123
|
+
def reload!
|
124
|
+
load_apps(remove_apps)
|
169
125
|
end
|
170
126
|
|
171
|
-
#
|
172
|
-
def
|
173
|
-
@
|
174
|
-
|
127
|
+
# Checks if both scripts watches the same file.
|
128
|
+
def ==(other)
|
129
|
+
@file == other.file
|
130
|
+
end
|
131
|
+
|
132
|
+
def apps
|
133
|
+
if @app
|
134
|
+
{ name => @app }
|
135
|
+
else
|
136
|
+
@apps
|
175
137
|
end
|
176
|
-
|
177
|
-
false
|
178
138
|
end
|
179
139
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
@
|
184
|
-
|
185
|
-
|
140
|
+
private
|
141
|
+
|
142
|
+
def mtime
|
143
|
+
@requires.map do |(path, full)|
|
144
|
+
File.mtime(full)
|
145
|
+
end.reject {|t| t > Time.now }.max || Time.now
|
146
|
+
end
|
147
|
+
|
148
|
+
# Figures out the full path of a required file.
|
149
|
+
def full_path(req)
|
150
|
+
return req if File.exists?(req)
|
151
|
+
dir = $LOAD_PATH.detect { |l| File.exists?(File.join(l, req)) }
|
152
|
+
if dir
|
153
|
+
File.expand_path(req, File.expand_path(dir))
|
154
|
+
else
|
155
|
+
req
|
186
156
|
end
|
187
157
|
end
|
188
158
|
end
|