tanuki 0.3.0 → 0.3.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/LICENSE +1 -1
- data/README.rdoc +10 -2
- data/app/tanuki/attribute/attribute.rb +2 -2
- data/app/tanuki/base/base.rb +2 -2
- data/app/tanuki/controller/controller.rb +2 -2
- data/app/tanuki/controller/default.thtml +1 -1
- data/app/tanuki/controller/index.thtml +1 -1
- data/app/tanuki/controller/link.thtml +1 -1
- data/app/tanuki/manager/controller/controller.rb +2 -0
- data/app/tanuki/manager/page/page.rb +2 -0
- data/app/tanuki/meta_model/manager.ttxt +1 -1
- data/app/tanuki/meta_model/manager_base.ttxt +2 -2
- data/app/tanuki/meta_model/meta_model.rb +2 -2
- data/app/tanuki/meta_model/model.ttxt +1 -1
- data/app/tanuki/meta_model/model_base.ttxt +2 -2
- data/app/tanuki/model/controller/controller.rb +2 -0
- data/app/tanuki/model/model.rb +2 -2
- data/app/tanuki/model/page/page.rb +2 -0
- data/app/tanuki/page/missing/default.thtml +1 -1
- data/app/tanuki/page/missing/missing.rb +2 -2
- data/app/user/page/index/default.thtml +1 -1
- data/app/user/page/index/index.rb +2 -2
- data/bin/tanuki +3 -201
- data/config/common.rb +1 -1
- data/config/common_application.rb +3 -3
- data/config/development_application.rb +1 -1
- data/config/production_application.rb +1 -1
- data/config/test_application.rb +2 -0
- data/lib/tanuki/application.rb +27 -7
- data/lib/tanuki/argument/base.rb +3 -3
- data/lib/tanuki/argument/integer.rb +3 -3
- data/lib/tanuki/argument/integer_range.rb +3 -3
- data/lib/tanuki/argument/string.rb +3 -3
- data/lib/tanuki/argument.rb +3 -3
- data/lib/tanuki/behavior/controller_behavior.rb +40 -8
- data/lib/tanuki/behavior/meta_model_behavior.rb +62 -20
- data/lib/tanuki/behavior/model_behavior.rb +4 -4
- data/lib/tanuki/behavior/object_behavior.rb +2 -2
- data/lib/tanuki/configurator.rb +2 -2
- data/lib/tanuki/context.rb +8 -3
- data/lib/tanuki/extensions/module.rb +16 -1
- data/lib/tanuki/extensions/rack/builder.rb +2 -2
- data/lib/tanuki/extensions/rack/server.rb +2 -2
- data/lib/tanuki/extensions/rack/static_dir.rb +2 -2
- data/lib/tanuki/i18n.rb +2 -2
- data/lib/tanuki/launcher.rb +2 -2
- data/lib/tanuki/loader.rb +24 -4
- data/lib/tanuki/model_generator.rb +114 -0
- data/lib/tanuki/template_compiler.rb +89 -13
- data/lib/tanuki/utility/create.rb +38 -0
- data/lib/tanuki/utility/generate.rb +52 -0
- data/lib/tanuki/utility/help.rb +16 -0
- data/lib/tanuki/utility/server.rb +23 -0
- data/lib/tanuki/utility/version.rb +15 -0
- data/lib/tanuki/utility.rb +65 -0
- data/lib/tanuki/version.rb +2 -2
- data/lib/tanuki.rb +1 -2
- data/schema/tanuki/l10n/en/controller.yml +1 -1
- data/schema/tanuki/l10n/en/page.yml +1 -1
- data/schema/tanuki/l10n/ru/controller.yml +1 -1
- data/schema/tanuki/l10n/ru/page.yml +1 -1
- data/schema/tanuki/models/controller.yml +1 -1
- data/schema/tanuki/models/page.yml +1 -1
- metadata +16 -5
- data/lib/tanuki/extensions/object.rb +0 -12
@@ -1,7 +1,7 @@
|
|
1
1
|
module Tanuki
|
2
2
|
|
3
3
|
# Tanuki::ControllerBehavior contains basic methods for a framework controller.
|
4
|
-
#
|
4
|
+
# It is included in the base controller class.
|
5
5
|
module ControllerBehavior
|
6
6
|
|
7
7
|
include Enumerable
|
@@ -16,10 +16,13 @@ module Tanuki
|
|
16
16
|
@_model = model
|
17
17
|
@_args = {}
|
18
18
|
if @_logical_parent = logical_parent
|
19
|
+
|
20
|
+
# Register controller arguments, as declared with Tanuki::ControllerBehavior#has_arg.
|
19
21
|
@_route = route_part[:route]
|
20
22
|
self.class.arg_defs.each_pair do |arg_name, arg_def|
|
21
23
|
route_part[:args][arg_def[:index]] = @_args[arg_name] = arg_def[:arg].to_value(route_part[:args][arg_def[:index]])
|
22
24
|
end
|
25
|
+
|
23
26
|
@_link = self.class.grow_link(@_logical_parent, {:route => @_route, :args => @_args}, self.class.arg_defs)
|
24
27
|
initialize_route(*route_part[:args])
|
25
28
|
else
|
@@ -44,14 +47,19 @@ module Tanuki
|
|
44
47
|
ensure_configured!
|
45
48
|
key = [route, args.dup]
|
46
49
|
if cached = @_cache[key]
|
50
|
+
|
47
51
|
# Return form cache
|
48
52
|
return cached
|
53
|
+
|
49
54
|
elsif child_def = @_child_defs[route]
|
55
|
+
|
50
56
|
# Search static routes
|
51
57
|
klass = child_def[:class]
|
52
58
|
args = klass.extract_args(args[0]) if byname
|
53
59
|
child = klass.new(process_child_context(@_ctx, route), self, {:route => route, :args => args}, child_def[:model])
|
60
|
+
|
54
61
|
else
|
62
|
+
|
55
63
|
# Search dynamic routes
|
56
64
|
found = false
|
57
65
|
s = route.to_s
|
@@ -68,11 +76,14 @@ module Tanuki
|
|
68
76
|
{:route => a_route, :args => embedded_args}, child_def[:model])
|
69
77
|
found = true
|
70
78
|
break child
|
71
|
-
end
|
72
|
-
end
|
79
|
+
end # if
|
80
|
+
end # each
|
81
|
+
|
73
82
|
end
|
83
|
+
|
74
84
|
# If still not found, search ghost routes
|
75
85
|
child = missing_route(route, *args) unless found
|
86
|
+
|
76
87
|
end
|
77
88
|
@_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
|
78
89
|
end
|
@@ -88,12 +99,17 @@ module Tanuki
|
|
88
99
|
args = []
|
89
100
|
key = [route, args]
|
90
101
|
if cached = @_cache[key]
|
102
|
+
|
91
103
|
# Return from cache
|
92
104
|
return cached.class
|
105
|
+
|
93
106
|
elsif child_def = @_child_defs[route]
|
107
|
+
|
94
108
|
# Return from static routes
|
95
109
|
return child_def[:class]
|
110
|
+
|
96
111
|
else
|
112
|
+
|
97
113
|
# Search dynamic routes
|
98
114
|
s = route.to_s
|
99
115
|
@_child_collection_defs.each do |collection_def|
|
@@ -103,8 +119,10 @@ module Tanuki
|
|
103
119
|
return child_def[:class] if child_def
|
104
120
|
end
|
105
121
|
end
|
122
|
+
|
106
123
|
# If still not found, search ghost routes
|
107
124
|
return (@_cache[key] = missing_route(route, *args)).class
|
125
|
+
|
108
126
|
end
|
109
127
|
end
|
110
128
|
|
@@ -263,7 +281,7 @@ module Tanuki
|
|
263
281
|
mod.instance_variable_set(:@_arg_defs, {})
|
264
282
|
end
|
265
283
|
|
266
|
-
end #
|
284
|
+
end # ClassMethods
|
267
285
|
|
268
286
|
extend ClassMethods
|
269
287
|
|
@@ -272,6 +290,8 @@ module Tanuki
|
|
272
290
|
# Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
|
273
291
|
def dispatch(ctx, klass, request_path)
|
274
292
|
route_parts = parse_path(request_path)
|
293
|
+
|
294
|
+
# Set logical children for active controllers
|
275
295
|
curr = root_ctrl = klass.new(ctx, nil, nil, true)
|
276
296
|
route_parts.each do |route_part|
|
277
297
|
curr.instance_variable_set :@_active, true
|
@@ -279,24 +299,36 @@ module Tanuki
|
|
279
299
|
curr.logical_child = nxt
|
280
300
|
curr = nxt
|
281
301
|
end
|
302
|
+
|
303
|
+
# Set links for active controllers and default routes
|
282
304
|
while route_part = curr.default_route
|
305
|
+
|
306
|
+
# Do a redirect, if some controller in the chain asks for it
|
283
307
|
if route_part[:redirect]
|
284
308
|
klass = curr.child_class(route_part)
|
285
309
|
return {:type => :redirect, :location => grow_link(curr, route_part, klass.arg_defs)}
|
286
310
|
end
|
311
|
+
|
312
|
+
# Add default route as logical child
|
287
313
|
curr.instance_variable_set :@_active, true
|
288
314
|
nxt = curr[route_part[:route], *route_part[:args]]
|
289
315
|
curr.logical_child = nxt
|
290
316
|
curr = nxt
|
317
|
+
|
291
318
|
end
|
319
|
+
|
320
|
+
# Find out dispatch result type from current controller
|
292
321
|
curr.instance_variable_set :@_active, true
|
293
322
|
curr.instance_variable_set :@_current, true
|
294
323
|
type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
|
324
|
+
|
325
|
+
# Set visual children for active controllers
|
295
326
|
prev = curr
|
296
327
|
while curr = prev.visual_parent
|
297
328
|
curr.visual_child = prev
|
298
329
|
prev = curr
|
299
330
|
end
|
331
|
+
|
300
332
|
{:type => type, :controller => prev}
|
301
333
|
end
|
302
334
|
|
@@ -319,7 +351,7 @@ module Tanuki
|
|
319
351
|
end
|
320
352
|
route_part[:args] = extract_args(args)
|
321
353
|
route_part
|
322
|
-
end
|
354
|
+
end # do
|
323
355
|
end
|
324
356
|
|
325
357
|
# Unescapes a given link part for internal use.
|
@@ -327,8 +359,8 @@ module Tanuki
|
|
327
359
|
s ? s.gsub(/\$([\/\$:-])/, '\1') : nil
|
328
360
|
end
|
329
361
|
|
330
|
-
end #
|
362
|
+
end # class << self
|
331
363
|
|
332
|
-
end #
|
364
|
+
end # ControllerBehavior
|
333
365
|
|
334
|
-
end #
|
366
|
+
end # Tanuki
|
@@ -32,10 +32,22 @@ module Tanuki
|
|
32
32
|
def process!
|
33
33
|
process_source!
|
34
34
|
process_key!
|
35
|
+
process_joins!
|
36
|
+
process_filter!
|
37
|
+
process_order!
|
38
|
+
end
|
39
|
+
|
40
|
+
# Prepares data for building a Sequel +where+ clause.
|
41
|
+
def process_filter!
|
42
|
+
# TODO: ...
|
35
43
|
end
|
36
44
|
|
37
45
|
def process_key!
|
38
|
-
@key
|
46
|
+
if @source.include? 'key' && @source['key'].nil?
|
47
|
+
@key = []
|
48
|
+
else
|
49
|
+
@key = @source['key'] || 'id'
|
50
|
+
end
|
39
51
|
@key = [@key] if @key.is_a? String
|
40
52
|
raise 'invalid key' unless @key.is_a? Array
|
41
53
|
@key.map! do |k|
|
@@ -50,6 +62,11 @@ module Tanuki
|
|
50
62
|
end
|
51
63
|
end
|
52
64
|
|
65
|
+
# Prepares data for building a Sequel +order+ clause.
|
66
|
+
def process_order!
|
67
|
+
# TODO: ...
|
68
|
+
end
|
69
|
+
|
53
70
|
# Extracts the model firts-source information form the YAML @data
|
54
71
|
# and performs
|
55
72
|
def process_source!
|
@@ -63,11 +80,6 @@ module Tanuki
|
|
63
80
|
def process_joins!
|
64
81
|
@joins = {}
|
65
82
|
@joins[@first_source] = nil
|
66
|
-
end
|
67
|
-
|
68
|
-
# Prepares data for template generation.
|
69
|
-
# Processes foreign keys, fields, etc.
|
70
|
-
def process_relations!
|
71
83
|
joins = @source['joins'] || {}
|
72
84
|
joins = [joins] if joins.is_a? String
|
73
85
|
joins = Hash[*joins.collect {|v| [v, nil] }.flatten] if joins.is_a? Array
|
@@ -75,32 +87,62 @@ module Tanuki
|
|
75
87
|
joins.each_pair do |table_alias, join|
|
76
88
|
table_alias = table_alias.to_sym
|
77
89
|
raise "#{table_alias} is already in use" if @joins.include? table_alias
|
78
|
-
if join
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
else
|
83
|
-
on = join
|
84
|
-
table_name = table_alias
|
85
|
-
end
|
90
|
+
if join && (join['on'].is_a? Hash)
|
91
|
+
table_name = join['table'] || table_alias
|
92
|
+
on = join['on']
|
93
|
+
join_type = (join['type'] || 'inner').to_sym
|
86
94
|
else
|
87
|
-
|
88
|
-
|
95
|
+
on = join
|
96
|
+
table_name = table_alias
|
97
|
+
join_type = :inner
|
89
98
|
end
|
90
99
|
if on
|
100
|
+
on = Hash[*on.map do |lhs, rhs|
|
101
|
+
[[lhs,table_alias],[rhs,@first_source]].map do |side,table_alias|
|
102
|
+
if side.is_a? String
|
103
|
+
if m = side.match(/^\(('|")(.*)\1\)$/)
|
104
|
+
m[2]
|
105
|
+
else
|
106
|
+
parts = side.split('.').map {|x| x.to_sym }
|
107
|
+
case parts.count
|
108
|
+
when 1
|
109
|
+
[table_alias, parts[0]]
|
110
|
+
when 2
|
111
|
+
raise "Unknown alias #{parts[0]}" unless @joins.include? parts[0]
|
112
|
+
parts
|
113
|
+
else
|
114
|
+
raise "Invalid column specification #{lhs}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
else
|
118
|
+
side
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end.flatten]
|
91
122
|
else
|
92
123
|
on = {}
|
93
124
|
@key.each do |k|
|
94
|
-
on[[table_alias, @first_source.to_s.singularize.to_sym]] =
|
95
|
-
# TODO choose a right priciple
|
125
|
+
on[[table_alias, (@first_source.to_s.singularize << '_' << k[1].to_s).to_sym]] = k
|
96
126
|
end
|
97
127
|
end
|
128
|
+
@joins[table_alias] = {
|
129
|
+
:type => join_type,
|
130
|
+
:table => table_name,
|
131
|
+
:alias => table_alias,
|
132
|
+
:on => on
|
133
|
+
}
|
98
134
|
end
|
99
135
|
else
|
100
136
|
raise "`joins' should be either nil or string or array or hash"
|
101
137
|
end
|
102
138
|
end
|
103
139
|
|
140
|
+
# Prepares data for template generation.
|
141
|
+
# Processes foreign keys, fields, etc.
|
142
|
+
def process_relations!
|
143
|
+
|
144
|
+
end
|
145
|
+
|
104
146
|
# Returns code for alias-column name pair for field +field_name+.
|
105
147
|
def qualified_name(field_name)
|
106
148
|
parts = field_name.split('.')
|
@@ -113,6 +155,6 @@ module Tanuki
|
|
113
155
|
end
|
114
156
|
end
|
115
157
|
|
116
|
-
end #
|
158
|
+
end # MetaModelBehavior
|
117
159
|
|
118
|
-
end #
|
160
|
+
end # Tanuki
|
@@ -105,7 +105,7 @@ module Tanuki
|
|
105
105
|
mod.instance_variable_set(:@_relations, {})
|
106
106
|
end
|
107
107
|
|
108
|
-
end #
|
108
|
+
end # ClassMethods
|
109
109
|
|
110
110
|
class << self
|
111
111
|
|
@@ -114,8 +114,8 @@ module Tanuki
|
|
114
114
|
mod.extend ClassMethods
|
115
115
|
end
|
116
116
|
|
117
|
-
end #
|
117
|
+
end # class << self
|
118
118
|
|
119
|
-
end #
|
119
|
+
end # ModelBehaviour
|
120
120
|
|
121
|
-
end #
|
121
|
+
end # Tanuki
|
data/lib/tanuki/configurator.rb
CHANGED
data/lib/tanuki/context.rb
CHANGED
@@ -46,16 +46,21 @@ module Tanuki
|
|
46
46
|
defined = @_defined
|
47
47
|
class << self; self; end.instance_eval do
|
48
48
|
method_sym = match[1].to_sym
|
49
|
+
|
50
|
+
# Search cache, if method exists; add as necessary
|
49
51
|
if defined.include? method_sym
|
50
52
|
undef_method method_sym
|
51
53
|
else
|
52
54
|
defined[method_sym] = nil
|
53
55
|
end
|
56
|
+
|
57
|
+
# Register value in context
|
54
58
|
if arg.is_a? Proc
|
55
59
|
define_method(method_sym, &arg)
|
56
60
|
else
|
57
61
|
define_method(method_sym) { arg }
|
58
62
|
end
|
63
|
+
|
59
64
|
return arg
|
60
65
|
end if match[2]
|
61
66
|
super
|
@@ -66,8 +71,8 @@ module Tanuki
|
|
66
71
|
raise "contexts cannot be instantiated"
|
67
72
|
end
|
68
73
|
|
69
|
-
end #
|
74
|
+
end # class << self
|
70
75
|
|
71
|
-
end #
|
76
|
+
end # Context
|
72
77
|
|
73
|
-
end #
|
78
|
+
end # Tanuki
|
@@ -1,5 +1,20 @@
|
|
1
1
|
class Module
|
2
2
|
|
3
|
+
# Runs Tanuki::Loader for every missing constant in any namespace.
|
4
|
+
def const_missing(sym)
|
5
|
+
klass = "#{name + '::' if name != 'Object'}#{sym}"
|
6
|
+
paths = Dir.glob(Tanuki::Loader.combined_class_path(klass))
|
7
|
+
if paths.empty?
|
8
|
+
unless Dir.glob(Tanuki::Loader.combined_class_dir(klass)).empty?
|
9
|
+
return const_set(sym, Class.new)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
paths.reverse_each {|path| require path }
|
13
|
+
return const_get(sym) if const_defined?(sym)
|
14
|
+
end
|
15
|
+
raise NameError, "uninitialized constant #{name}::#{sym}"
|
16
|
+
end
|
17
|
+
|
3
18
|
# Creates a reader +sym+ and a writer +sym=+ for the instance variable @_sym.
|
4
19
|
def internal_attr_accessor(*syms)
|
5
20
|
internal_attr_reader(*syms)
|
@@ -24,4 +39,4 @@ class Module
|
|
24
39
|
end
|
25
40
|
end
|
26
41
|
|
27
|
-
end #
|
42
|
+
end # Module
|
data/lib/tanuki/i18n.rb
CHANGED
data/lib/tanuki/launcher.rb
CHANGED
data/lib/tanuki/loader.rb
CHANGED
@@ -17,6 +17,12 @@ module Tanuki
|
|
17
17
|
class_path(klass, @app_root ||= combined_app_root)
|
18
18
|
end
|
19
19
|
|
20
|
+
# Returns the path to a directory containing class +klass+.
|
21
|
+
# Seatches across all common roots.
|
22
|
+
def combined_class_dir(klass)
|
23
|
+
const_to_path(klass, @app_root ||= combined_app_root)
|
24
|
+
end
|
25
|
+
|
20
26
|
# Returns a glob pattern of all common roots.
|
21
27
|
def combined_app_root
|
22
28
|
local_app_root = File.expand_path(File.join('..', '..', '..', 'app'), __FILE__)
|
@@ -48,11 +54,15 @@ module Tanuki
|
|
48
54
|
ct_file_exists = File.file?(ct_path)
|
49
55
|
ct_file_mtime = ct_file_exists ? File.mtime(ct_path) : nil
|
50
56
|
st_file = File.new(st_path, 'r:UTF-8')
|
57
|
+
|
58
|
+
# Find out if template refresh is required
|
51
59
|
if !ct_file_exists || st_file.mtime > ct_file_mtime || File.mtime(COMPILER_PATH) > ct_file_mtime
|
52
60
|
no_refresh = compile_template(st_file, ct_path, ct_file_mtime, owner, sym)
|
53
61
|
else
|
54
62
|
no_refresh = true
|
55
63
|
end
|
64
|
+
|
65
|
+
# Load template
|
56
66
|
method_name = "#{sym}_view".to_sym
|
57
67
|
owner.instance_eval do
|
58
68
|
unless (method_exists = instance_methods(false).include? method_name) && no_refresh
|
@@ -60,8 +70,11 @@ module Tanuki
|
|
60
70
|
load ct_path
|
61
71
|
end
|
62
72
|
end
|
73
|
+
|
74
|
+
# Register template in cache
|
63
75
|
templates["#{owner}##{sym}"] = nil
|
64
76
|
templates["#{obj.class}##{sym}"] = nil
|
77
|
+
|
65
78
|
obj.send(method_name, *args, &block)
|
66
79
|
else
|
67
80
|
raise "undefined template `#{sym}' for #{obj.class}"
|
@@ -81,7 +94,11 @@ module Tanuki
|
|
81
94
|
# (is equal to +ct_file_mtime+) since file locking was initiated.
|
82
95
|
def compile_template(st_file, ct_path, ct_file_mtime, owner, sym)
|
83
96
|
no_refresh = true
|
97
|
+
|
98
|
+
# Lock template source to avoid race condition
|
84
99
|
st_file.flock(File::LOCK_EX)
|
100
|
+
|
101
|
+
# Compile, if template still needs compiling on lock release
|
85
102
|
if !File.file?(ct_path) || File.mtime(ct_path) == ct_file_mtime
|
86
103
|
no_refresh = false
|
87
104
|
ct_dir = File.dirname(ct_path)
|
@@ -91,7 +108,10 @@ module Tanuki
|
|
91
108
|
end
|
92
109
|
FileUtils.mv(tmp_ct_path, ct_path)
|
93
110
|
end
|
111
|
+
|
112
|
+
# Release lock
|
94
113
|
st_file.flock(File::LOCK_UN)
|
114
|
+
|
95
115
|
no_refresh
|
96
116
|
end
|
97
117
|
|
@@ -102,7 +122,7 @@ module Tanuki
|
|
102
122
|
|
103
123
|
# Transforms a given constant +klass+ to a path with a given +root+.
|
104
124
|
def const_to_path(klass, root)
|
105
|
-
File.join(root, klass.to_s.split('
|
125
|
+
File.join(root, klass.to_s.split('::').map {|item| item.gsub(/(?!^)([A-Z])/, '_\1') }.join(File::SEPARATOR).downcase)
|
106
126
|
end
|
107
127
|
|
108
128
|
# Finds the direct template +method_name+ owner among ancestors of class +klass+.
|
@@ -115,8 +135,8 @@ module Tanuki
|
|
115
135
|
[nil, nil]
|
116
136
|
end
|
117
137
|
|
118
|
-
end #
|
138
|
+
end # class << self
|
119
139
|
|
120
|
-
end #
|
140
|
+
end # Path
|
121
141
|
|
122
|
-
end #
|
142
|
+
end # Tanuki
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Tanuki
|
2
|
+
|
3
|
+
# Tanuki::ModelGenerator is used for, well, generating models
|
4
|
+
class ModelGenerator
|
5
|
+
|
6
|
+
# A collection of all model definitions represented as hash of hashes where
|
7
|
+
# the first nesting level represents namespaces
|
8
|
+
# and the second one contains named model bodies +Hash+.
|
9
|
+
attr_reader :models
|
10
|
+
|
11
|
+
# +Hash+ for collecting the template render-time info for models.
|
12
|
+
# Keys in this hash are full qualified names of models (+namespace_model_name+).
|
13
|
+
# Values are hashes in the form of +{:generated => [], :failed => [], :skipped => []}+
|
14
|
+
# where all subentries correspond to template generation statuses.
|
15
|
+
attr_reader :tried
|
16
|
+
|
17
|
+
# Creates a model generator configured by given context +ctx+.
|
18
|
+
def initialize(ctx)
|
19
|
+
@ctx = ctx
|
20
|
+
@tried = {}
|
21
|
+
@models = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Loads all models into memory from a given +schema_root+ and prepares
|
25
|
+
# their own properties to be rendered in class templates.
|
26
|
+
def read_models(schema_root)
|
27
|
+
Dir.entries(schema_root).reject {|path| path =~ /\A\.{1,2}\Z/ }.each do |namespace_path|
|
28
|
+
namespace_name = namespace_path.split('_').map {|s| s.capitalize }.join
|
29
|
+
namespace = @models[namespace_name] = {}
|
30
|
+
Dir.glob(File.join(schema_root, namespace_path, 'models', '*.yml')) do |file_path|
|
31
|
+
model_name = File.basename(file_path, '.yml').split('_').map {|s| s.capitalize }.join
|
32
|
+
meta_model = namespace[model_name] = Tanuki::MetaModel.new(
|
33
|
+
namespace_name,
|
34
|
+
model_name,
|
35
|
+
YAML.load_file(file_path),
|
36
|
+
@models
|
37
|
+
)
|
38
|
+
meta_model.process!
|
39
|
+
if @tried.include? namespace_model_name = "#{namespace_name}.#{model_name}"
|
40
|
+
next
|
41
|
+
else
|
42
|
+
@tried[namespace_model_name] = {:generated => [], :failed => [], :skipped => []}
|
43
|
+
@tried["#{namespace_model_name} (base)"] = {:generated => [], :failed => [], :skipped => []}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generates all models that were read from a given +schema_root+.
|
50
|
+
# Generation paths are determined by context given in the constructor.
|
51
|
+
def generate(schema_root)
|
52
|
+
read_models schema_root
|
53
|
+
process_models
|
54
|
+
generate_classes
|
55
|
+
end
|
56
|
+
|
57
|
+
# Renders a model class by applying +class_type+ template
|
58
|
+
# to a given +meta_model+. Classes are splitted in two parts:
|
59
|
+
# * user-extendable part (+base=false+), which resides in +ctx.app_root+,
|
60
|
+
# * other part with framework-specific code (+base=true+), which resides in +ctx.gen_root+.
|
61
|
+
# +namespace_model_name+ is used as a label for error report collection
|
62
|
+
# in Tanuki::ModelGenerator#tried hash.
|
63
|
+
def generate_class(meta_model, namespace_model_name, class_type, base)
|
64
|
+
class_name = meta_model.class_name_for class_type
|
65
|
+
namespace_model_name += ' (base)' if base
|
66
|
+
path = Tanuki::Loader.class_path(class_name, base ? @ctx.gen_root : @ctx.app_root)
|
67
|
+
if base || !(File.exists? path)
|
68
|
+
begin
|
69
|
+
dirname = File.dirname(path)
|
70
|
+
FileUtils.mkdir_p dirname unless File.directory? dirname
|
71
|
+
File.open path, 'w' do |file|
|
72
|
+
writer = proc {|out| file.print out.to_s }
|
73
|
+
Loader.run_template({}, meta_model, class_type).call(writer, @ctx)
|
74
|
+
end
|
75
|
+
@tried[namespace_model_name][:generated] << class_name
|
76
|
+
rescue
|
77
|
+
@tried[namespace_model_name][:failed] << class_name
|
78
|
+
end
|
79
|
+
else
|
80
|
+
@tried[namespace_model_name][:skipped] << class_name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Iterates over all loaded model definitions and renders
|
85
|
+
# all available class templates for them.
|
86
|
+
def generate_classes
|
87
|
+
@models.each do |namespace_name, namespace |
|
88
|
+
namespace.each do |model_name, meta_model|
|
89
|
+
namespace_model_name = "#{namespace_name}.#{model_name}"
|
90
|
+
{
|
91
|
+
:model => false,
|
92
|
+
:model_base => true,
|
93
|
+
:manager => false,
|
94
|
+
:manager_base => true
|
95
|
+
}.each do |class_type, base|
|
96
|
+
generate_class meta_model, namespace_model_name, class_type, base
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Iterates over all loaded model definitions, giving them a chance to meet each other
|
103
|
+
# and make any cross-model assumptions (like names for foreign keys).
|
104
|
+
def process_models
|
105
|
+
@models.each do |namespace_name, namespace |
|
106
|
+
namespace.each do |model_name, meta_model|
|
107
|
+
meta_model.process_relations!
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end # ModelGenerator
|
113
|
+
|
114
|
+
end # Tanuki
|