tanuki 0.3.1 → 0.4.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/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]
|