tanuki 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +5 -4
- data/app/tanuki/controller/{link.thtml → controller.link.thtml} +0 -0
- data/app/tanuki/controller/controller.page.thtml +14 -0
- data/app/tanuki/controller/controller.rb +1 -2
- data/app/tanuki/controller/controller.title.ttxt +1 -0
- data/app/tanuki/controller/controller.view.thtml +3 -0
- data/app/tanuki/fetcher/sequel/sequel.rb +34 -0
- data/app/tanuki/manager/controller/controller.rb +1 -1
- data/app/tanuki/manager/page/page.rb +1 -1
- data/app/tanuki/meta_model/{manager.ttxt → meta_model.manager.ttxt} +0 -0
- data/app/tanuki/meta_model/{manager_base.ttxt → meta_model.manager_base.ttxt} +0 -0
- data/app/tanuki/meta_model/{model.ttxt → meta_model.model.ttxt} +0 -0
- data/app/tanuki/meta_model/{model_base.ttxt → meta_model.model_base.ttxt} +0 -0
- data/app/tanuki/meta_model/meta_model.rb +1 -2
- data/app/tanuki/model/controller/controller.rb +1 -1
- data/app/tanuki/model/page/page.rb +1 -1
- data/app/tanuki/page/missing/{default.thtml → missing.page.thtml} +1 -1
- data/app/tanuki/page/missing/missing.rb +3 -2
- data/app/user/page/home/home.rb +2 -0
- data/app/user/page/home/home.title.thtml +1 -0
- data/app/user/page/home/home.view.css +88 -0
- data/app/user/page/home/home.view.thtml +22 -0
- data/bin/tanuki +2 -1
- data/config/common.rb +1 -0
- data/config/common_application.rb +3 -6
- data/config/development_application.rb +0 -3
- data/lib/tanuki.rb +8 -7
- data/lib/tanuki/application.rb +108 -81
- data/lib/tanuki/argument.rb +10 -5
- data/lib/tanuki/argument/integer_range.rb +4 -2
- data/lib/tanuki/{behavior/object_behavior.rb → base_behavior.rb} +21 -4
- data/lib/tanuki/configurator.rb +20 -8
- data/lib/tanuki/const.rb +32 -0
- data/lib/tanuki/context.rb +18 -7
- data/lib/tanuki/controller.rb +517 -0
- data/lib/tanuki/css_compressor.rb +50 -0
- data/lib/tanuki/extensions/module.rb +21 -5
- data/lib/tanuki/extensions/rack/frozen_route.rb +35 -0
- data/lib/tanuki/extensions/rack/static_dir.rb +1 -1
- data/lib/tanuki/extensions/sequel/model.rb +7 -0
- data/lib/tanuki/i18n.rb +8 -6
- data/lib/tanuki/loader.rb +166 -33
- data/lib/tanuki/meta_model.rb +176 -0
- data/lib/tanuki/{behavior/model_behavior.rb → model_behavior.rb} +7 -3
- data/lib/tanuki/model_generator.rb +49 -29
- data/lib/tanuki/template_compiler.rb +72 -41
- data/lib/tanuki/utility.rb +11 -4
- data/lib/tanuki/utility/create.rb +52 -11
- data/lib/tanuki/utility/generate.rb +16 -10
- data/lib/tanuki/utility/version.rb +1 -1
- data/lib/tanuki/version.rb +7 -2
- metadata +50 -66
- data/app/tanuki/controller/default.thtml +0 -5
- data/app/tanuki/controller/index.thtml +0 -1
- data/app/user/page/index/default.thtml +0 -121
- data/app/user/page/index/index.rb +0 -2
- data/config/test_application.rb +0 -2
- data/lib/tanuki/behavior/controller_behavior.rb +0 -366
- data/lib/tanuki/behavior/meta_model_behavior.rb +0 -160
- data/lib/tanuki/extensions/rack/builder.rb +0 -26
- data/lib/tanuki/extensions/rack/server.rb +0 -18
- data/lib/tanuki/launcher.rb +0 -21
- data/lib/tanuki/utility/server.rb +0 -23
@@ -0,0 +1,50 @@
|
|
1
|
+
module Tanuki
|
2
|
+
|
3
|
+
# Tanuki::CssCompressor takes a CSS source and makes it shorter,
|
4
|
+
# trying not to break the semantics.
|
5
|
+
class CssCompressor
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Compresses CSS source in +css+.
|
10
|
+
def compress(css)
|
11
|
+
@css = css
|
12
|
+
compress_structure
|
13
|
+
compress_colors
|
14
|
+
compress_dimensions
|
15
|
+
@css
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Compresses the CSS structure in +@css+.
|
21
|
+
def compress_structure
|
22
|
+
@css.gsub!(%r{/\*.*\*/}, '')
|
23
|
+
@css.strip!.gsub!(/\s*(\s|;|:|}|{|\)|\(|,)\s*/, '\1')
|
24
|
+
@css.gsub!(/[;]+/, ';')
|
25
|
+
@css.gsub!(';}', '}')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Compresses CSS color values in +@css+.
|
29
|
+
def compress_colors
|
30
|
+
@css.gsub! /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/ do
|
31
|
+
r = $~[1].to_i.to_s(16).rjust(2, '0')
|
32
|
+
g = $~[2].to_i.to_s(16).rjust(2, '0')
|
33
|
+
b = $~[3].to_i.to_s(16).rjust(2, '0')
|
34
|
+
"##{r}#{g}#{b}"
|
35
|
+
end
|
36
|
+
@css.gsub!(/#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3/, '#\1\2\3')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Compresses CSS dimension values in +@css+.
|
40
|
+
def compress_dimensions
|
41
|
+
@css.gsub!(/(?<=[\s:])0(?:%|cm|em|ex|in|mm|pc|pt|px)/, '0')
|
42
|
+
@css.gsub!(/(?<=[\s:])([0-9]+[a-z]*)\s+\1\s+\1\s+\1/, '\1')
|
43
|
+
@css.gsub!(/(?<=[\s:])(\d+[a-z]*)\s+(\d+[a-z]*)\s+\1\s+\2/, '\1 \2')
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end # CssCompressor
|
49
|
+
|
50
|
+
end # Tanuki
|
@@ -1,21 +1,37 @@
|
|
1
1
|
class Module
|
2
2
|
|
3
3
|
# Runs Tanuki::Loader for every missing constant in any namespace.
|
4
|
-
def
|
5
|
-
klass = "#{name + '::'
|
4
|
+
def const_missing_with_balls(sym)
|
5
|
+
klass = "#{name + '::' unless name.nil? || (name == 'Object')}#{sym}"
|
6
6
|
paths = Dir.glob(Tanuki::Loader.combined_class_path(klass))
|
7
7
|
if paths.empty?
|
8
8
|
unless Dir.glob(Tanuki::Loader.combined_class_dir(klass)).empty?
|
9
|
-
return const_set(sym,
|
9
|
+
return const_set(sym, Module.new)
|
10
10
|
end
|
11
11
|
else
|
12
12
|
paths.reverse_each {|path| require path }
|
13
13
|
return const_get(sym) if const_defined?(sym)
|
14
14
|
end
|
15
|
-
|
15
|
+
const_missing_without_balls(sym)
|
16
16
|
end
|
17
|
+
alias_method_chain :const_missing, :balls
|
17
18
|
|
18
|
-
#
|
19
|
+
# Sets the named constant to the given object, returning that object.
|
20
|
+
# Creates new constants and modules recursively if no constant with the
|
21
|
+
# given name and nesting previously existed.
|
22
|
+
def const_set_recursive(sym, obj)
|
23
|
+
sym.to_s.split('::').inject(self) do |klass, const|
|
24
|
+
if const_defined? const.to_sym
|
25
|
+
const = const_get(const)
|
26
|
+
else
|
27
|
+
const = const_set(const, Module.new)
|
28
|
+
end
|
29
|
+
const
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a reader +sym+ and a writer +sym=+
|
34
|
+
# for the instance variable @_sym.
|
19
35
|
def internal_attr_accessor(*syms)
|
20
36
|
internal_attr_reader(*syms)
|
21
37
|
internal_attr_writer(*syms)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class FrozenRoute
|
5
|
+
|
6
|
+
# Creates a new +route+ (defined by a regular expression)
|
7
|
+
# that yields the same +body+ throughout the whole life of the
|
8
|
+
# application.
|
9
|
+
def initialize(app, route, content_type, body)
|
10
|
+
@app = app
|
11
|
+
@route = route
|
12
|
+
@headers = {
|
13
|
+
'Content-Type' => content_type,
|
14
|
+
'ETag' => Digest::MD5.hexdigest(body)
|
15
|
+
}
|
16
|
+
@body = body.freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the defined +body+ if the route is matched.
|
20
|
+
def call(env)
|
21
|
+
if env['PATH_INFO'] =~ @route
|
22
|
+
if @headers['ETag'] == env['HTTP_IF_NONE_MATCH']
|
23
|
+
status = 304
|
24
|
+
body = []
|
25
|
+
else
|
26
|
+
status = 200
|
27
|
+
body = @body
|
28
|
+
end
|
29
|
+
return [status, @headers, body]
|
30
|
+
end
|
31
|
+
@app.call(env)
|
32
|
+
end
|
33
|
+
|
34
|
+
end # FrozenRoute
|
35
|
+
end # Rack
|
data/lib/tanuki/i18n.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
module Tanuki
|
2
2
|
|
3
3
|
# Tanuki::I18n is a drop-in controller for localizable applications.
|
4
|
-
class I18n
|
5
|
-
|
6
|
-
include ControllerBehavior
|
4
|
+
class I18n < Controller
|
7
5
|
|
8
6
|
# Adds language routes of root controller class when invoked.
|
9
7
|
def configure
|
@@ -15,12 +13,16 @@ module Tanuki
|
|
15
13
|
# Returns default route according to default language.
|
16
14
|
def default_route
|
17
15
|
raise 'default language is not configured' unless @_ctx.language
|
18
|
-
{
|
16
|
+
{
|
17
|
+
:route => @_ctx.language,
|
18
|
+
:args => {},
|
19
|
+
:redirect => @_ctx.i18n_redirect
|
20
|
+
}
|
19
21
|
end
|
20
22
|
|
21
23
|
# Calls default view of visual child.
|
22
|
-
def
|
23
|
-
@_visual_child.
|
24
|
+
def view
|
25
|
+
@_visual_child.view
|
24
26
|
end
|
25
27
|
|
26
28
|
# Adds child controller language to its context.
|
data/lib/tanuki/loader.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
1
3
|
module Tanuki
|
2
4
|
|
3
|
-
# Tanuki::Loader deals with framework paths resolving,
|
5
|
+
# Tanuki::Loader deals with framework paths resolving,
|
6
|
+
# and object and template loading.
|
4
7
|
class Loader
|
5
8
|
|
6
9
|
class << self
|
@@ -8,27 +11,43 @@ module Tanuki
|
|
8
11
|
# Returns the path to a source file in +root+ containing class +klass+.
|
9
12
|
def class_path(klass, root)
|
10
13
|
path = const_to_path(klass, root)
|
11
|
-
File.join(path, path.match(
|
14
|
+
File.join(path, path.match(%r{/([^/]*)$})[1] << '.rb')
|
12
15
|
end
|
13
16
|
|
14
17
|
# Returns the path to a source file containing class +klass+.
|
15
18
|
# Seatches across all common roots.
|
16
19
|
def combined_class_path(klass)
|
17
|
-
class_path(klass, @app_root ||=
|
20
|
+
class_path(klass, @app_root ||= combined_app_root_glob)
|
18
21
|
end
|
19
22
|
|
20
23
|
# Returns the path to a directory containing class +klass+.
|
21
24
|
# Seatches across all common roots.
|
22
25
|
def combined_class_dir(klass)
|
23
|
-
const_to_path(klass, @app_root ||=
|
26
|
+
const_to_path(klass, @app_root ||= combined_app_root_glob)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns an array with all common roots.
|
30
|
+
def combined_app_root(include_gen_root=true)
|
31
|
+
local_app_root = File.expand_path('../../../app', __FILE__)
|
32
|
+
app_root = [@ctx_app_root ||= @context.app_root]
|
33
|
+
app_root << @context.gen_root if include_gen_root
|
34
|
+
app_root << local_app_root if local_app_root != @ctx_app_root
|
35
|
+
app_root
|
24
36
|
end
|
25
37
|
|
26
38
|
# Returns a glob pattern of all common roots.
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
39
|
+
def combined_app_root_glob(include_gen_root=true)
|
40
|
+
"{#{combined_app_root(include_gen_root).join(',')}}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a regexp pattern of all common roots.
|
44
|
+
def combined_app_root_regexp(include_gen_root=true)
|
45
|
+
regexp_root = '^('
|
46
|
+
regexp_root << combined_app_root(include_gen_root).map do |dir|
|
47
|
+
Regexp.escape(dir)
|
48
|
+
end.join('|')
|
49
|
+
regexp_root << ')/?'
|
50
|
+
Regexp.new(regexp_root)
|
32
51
|
end
|
33
52
|
|
34
53
|
# Assigns a context to Tanuki::Loader.
|
@@ -37,18 +56,21 @@ module Tanuki
|
|
37
56
|
@context = ctx
|
38
57
|
end
|
39
58
|
|
40
|
-
# Checks if +templates+ contain a compiled template +sym+
|
59
|
+
# Checks if +templates+ contain a compiled template +sym+
|
60
|
+
# for class +klass+.
|
41
61
|
def has_template?(templates, klass, sym)
|
42
62
|
templates.include? "#{klass}##{sym}"
|
43
63
|
end
|
44
64
|
|
45
|
-
#
|
65
|
+
# Loads template +sym+.
|
46
66
|
#
|
47
67
|
# Template is recompiled from source on two conditions:
|
48
|
-
# *
|
49
|
-
#
|
50
|
-
|
51
|
-
|
68
|
+
# * Template source modification time is older than
|
69
|
+
# compiled template modification time,
|
70
|
+
# * Tanuki::TemplateCompiler source modification time is older than
|
71
|
+
# compiled template modification time.
|
72
|
+
def load_template(templates, obj, sym)
|
73
|
+
owner, st_path = *resource_owner(obj.class, sym)
|
52
74
|
if st_path
|
53
75
|
ct_path = compiled_template_path(owner, sym)
|
54
76
|
ct_file_exists = File.file?(ct_path)
|
@@ -56,16 +78,21 @@ module Tanuki
|
|
56
78
|
st_file = File.new(st_path, 'r:UTF-8')
|
57
79
|
|
58
80
|
# Find out if template refresh is required
|
59
|
-
if !ct_file_exists
|
60
|
-
|
81
|
+
if !ct_file_exists \
|
82
|
+
|| st_file.mtime > ct_file_mtime \
|
83
|
+
|| File.mtime(COMPILER_PATH) > ct_file_mtime
|
84
|
+
no_refresh = compile_template(
|
85
|
+
st_file, ct_path, ct_file_mtime, owner, sym
|
86
|
+
)
|
61
87
|
else
|
62
88
|
no_refresh = true
|
63
89
|
end
|
64
90
|
|
65
91
|
# Load template
|
66
|
-
method_name = "#{sym}_view".to_sym
|
92
|
+
method_name = (sym == 'view' ? sym : "#{sym}_view").to_sym
|
67
93
|
owner.instance_eval do
|
68
|
-
|
94
|
+
method_exists = instance_methods(false).include?(method_name)
|
95
|
+
unless method_exists && no_refresh
|
69
96
|
remove_method method_name if method_exists
|
70
97
|
load ct_path
|
71
98
|
end
|
@@ -75,23 +102,121 @@ module Tanuki
|
|
75
102
|
templates["#{owner}##{sym}"] = nil
|
76
103
|
templates["#{obj.class}##{sym}"] = nil
|
77
104
|
|
78
|
-
|
105
|
+
method_name
|
79
106
|
else
|
80
107
|
raise "undefined template `#{sym}' for #{obj.class}"
|
81
108
|
end
|
82
109
|
end
|
83
110
|
|
111
|
+
def load_template_files(ctx, template_signature)
|
112
|
+
klass_name, method = *template_signature.split('#')
|
113
|
+
klass = klass_name.constantize
|
114
|
+
method = method.to_sym
|
115
|
+
_, path = *resource_owner(klass, method, JAVASCRIPT_EXT)
|
116
|
+
ctx.javascripts[path] = false if path
|
117
|
+
ctx.resources[template_signature] = nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Writes the compiled CSS file to disk
|
121
|
+
# and refreshes with a given +interval+ in seconds.
|
122
|
+
def refresh_css(interval=5)
|
123
|
+
return if @next_reload && @next_reload > Time.new
|
124
|
+
mode = File::RDWR|File::CREAT
|
125
|
+
File.open("#{@context.public_root}/bundle.css", mode) do |f|
|
126
|
+
f.flock(File::LOCK_EX) # Avoid race condition
|
127
|
+
now = Time.new
|
128
|
+
if !@next_reload || @next_reload < now
|
129
|
+
@next_reload = now + interval
|
130
|
+
@ctx_app_root ||= @context.app_root
|
131
|
+
f.rewind
|
132
|
+
compile_css(f, true)
|
133
|
+
f.flush.truncate(f.pos)
|
134
|
+
end # if
|
135
|
+
end # open
|
136
|
+
end
|
137
|
+
|
138
|
+
# Prepares the application for production mode. This includes:
|
139
|
+
# * precompiling all templates into memory,
|
140
|
+
# * and prebuilding static file like the CSS bundle.
|
141
|
+
def prepare_for_production
|
142
|
+
root_glob = combined_app_root_glob(false)
|
143
|
+
root_re = combined_app_root_regexp(false)
|
144
|
+
|
145
|
+
# Load application modules
|
146
|
+
path_re = /#{root_re}(?<path>.*)/
|
147
|
+
Dir.glob(File.join(root_glob, "**/*")) do |file|
|
148
|
+
if File.directory? file
|
149
|
+
file.match(path_re) do |m|
|
150
|
+
mod = File.join(file, File.basename(file)) << '.rb'
|
151
|
+
if File.file? mod
|
152
|
+
require mod
|
153
|
+
else
|
154
|
+
Object.const_set_recursive(m[:path].camelize, Module.new)
|
155
|
+
end
|
156
|
+
end # match
|
157
|
+
end # if
|
158
|
+
end
|
159
|
+
|
160
|
+
# Load templates
|
161
|
+
file_re = /#{root_re}(?<path>.*)\/.*\.(?<template>.*)\./
|
162
|
+
Dir.glob(File.join(root_glob, "**/*#{TEMPLATE_EXT}")) do |file|
|
163
|
+
file.match(file_re) do |m|
|
164
|
+
ios = StringIO.new
|
165
|
+
TemplateCompiler.compile_template(
|
166
|
+
ios, File.read(file), m[:path].camelize, m[:template], false
|
167
|
+
)
|
168
|
+
m[:path].camelize.constantize.class_eval(ios.string)
|
169
|
+
end # match
|
170
|
+
end # glob
|
171
|
+
|
172
|
+
# Load CSS
|
173
|
+
css = CssCompressor.compress(compile_css(StringIO.new).string)
|
174
|
+
Application.use(Rack::FrozenRoute, %r{/bundle.css}, 'text/css', css)
|
175
|
+
Application.pull_down(Rack::StaticDir)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Runs template +sym+ with optional +args+ and +block+
|
179
|
+
# from object +obj+.
|
180
|
+
def run_template(templates, obj, sym, *args, &block)
|
181
|
+
method_name = load_template(templates, obj, sym)
|
182
|
+
obj.send(method_name, *args, &block)
|
183
|
+
end
|
184
|
+
|
84
185
|
private
|
85
186
|
|
86
187
|
# Path to Tanuki::TemplateCompiler for internal use.
|
87
|
-
COMPILER_PATH = File.expand_path(
|
188
|
+
COMPILER_PATH = File.expand_path(
|
189
|
+
'../template_compiler.rb',
|
190
|
+
__FILE__
|
191
|
+
).freeze
|
88
192
|
|
89
193
|
# Extension glob for template files.
|
90
|
-
TEMPLATE_EXT = '.t{html,txt}'
|
194
|
+
TEMPLATE_EXT = '.t{html,txt}'.freeze
|
195
|
+
|
196
|
+
# Extension glob for JavaScript files.
|
197
|
+
JAVASCRIPT_EXT = '.js'.freeze
|
198
|
+
|
199
|
+
# Extension glob for CSS files.
|
200
|
+
STYLESHEET_EXT = '.css'.freeze
|
201
|
+
|
202
|
+
# Compiles all application stylesheets into +ios+.
|
203
|
+
# Add path headers for each chunk of CSS if +mark_source+ is true.
|
204
|
+
def compile_css(ios, mark_source=false)
|
205
|
+
header = "/*** %s ***/\n"
|
206
|
+
Dir.glob("#{@ctx_app_root}/**/*#{STYLESHEET_EXT}") do |file|
|
207
|
+
if File.file? file
|
208
|
+
ios << header % file.sub("#{@ctx_app_root}/", '') if mark_source
|
209
|
+
ios << File.read(file) << "\n"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
ios
|
213
|
+
end
|
91
214
|
|
92
|
-
# Compiles template +sym+ from +owner+ class
|
93
|
-
#
|
94
|
-
#
|
215
|
+
# Compiles template +sym+ from +owner+ class
|
216
|
+
# using source in +st_file+ to +ct_path+.
|
217
|
+
# Compilation is only done if destination file modification time
|
218
|
+
# has not changed (is equal to +ct_file_mtime+)
|
219
|
+
# since file locking was initiated.
|
95
220
|
def compile_template(st_file, ct_path, ct_file_mtime, owner, sym)
|
96
221
|
no_refresh = true
|
97
222
|
|
@@ -104,7 +229,9 @@ module Tanuki
|
|
104
229
|
ct_dir = File.dirname(ct_path)
|
105
230
|
FileUtils.mkdir_p(ct_dir) unless File.directory?(ct_dir)
|
106
231
|
File.open(tmp_ct_path = ct_path + '~', 'w:UTF-8') do |ct_file|
|
107
|
-
TemplateCompiler.compile_template(
|
232
|
+
TemplateCompiler.compile_template(
|
233
|
+
ct_file, st_file.read, owner, sym
|
234
|
+
)
|
108
235
|
end
|
109
236
|
FileUtils.mv(tmp_ct_path, ct_path)
|
110
237
|
end
|
@@ -115,21 +242,27 @@ module Tanuki
|
|
115
242
|
no_refresh
|
116
243
|
end
|
117
244
|
|
118
|
-
# Returns the path to a compiled template file
|
245
|
+
# Returns the path to a compiled template file
|
246
|
+
# containing template +method_name+ for class +klass+.
|
119
247
|
def compiled_template_path(klass, method_name)
|
120
|
-
|
248
|
+
path = const_to_path(klass, @context.gen_root)
|
249
|
+
"#{path}/#{method_name.to_s << '.tpl.rb'}"
|
121
250
|
end
|
122
251
|
|
123
252
|
# Transforms a given constant +klass+ to a path with a given +root+.
|
124
253
|
def const_to_path(klass, root)
|
125
|
-
|
254
|
+
path = klass.to_s.split('::').map(&:underscore).join('/').downcase
|
255
|
+
"#{root}/#{path}"
|
126
256
|
end
|
127
257
|
|
128
|
-
# Finds the direct template +method_name+ owner
|
129
|
-
|
130
|
-
|
258
|
+
# Finds the direct template +method_name+ owner
|
259
|
+
# among ancestors of class +klass+.
|
260
|
+
def resource_owner(klass, method_name, extension=TEMPLATE_EXT)
|
131
261
|
klass.ancestors.each do |ancestor|
|
132
|
-
|
262
|
+
path = const_to_path(ancestor, @app_root ||= combined_app_root_glob)
|
263
|
+
method_file = ancestor.to_s.split('::')[-1].underscore.downcase
|
264
|
+
method_file << '.' << method_name.to_s << extension
|
265
|
+
files = Dir["#{path}/#{method_file}"]
|
133
266
|
return ancestor, files[0] unless files.empty?
|
134
267
|
end
|
135
268
|
[nil, nil]
|