tanuki 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/README.rdoc +5 -4
  2. data/app/tanuki/controller/{link.thtml → controller.link.thtml} +0 -0
  3. data/app/tanuki/controller/controller.page.thtml +14 -0
  4. data/app/tanuki/controller/controller.rb +1 -2
  5. data/app/tanuki/controller/controller.title.ttxt +1 -0
  6. data/app/tanuki/controller/controller.view.thtml +3 -0
  7. data/app/tanuki/fetcher/sequel/sequel.rb +34 -0
  8. data/app/tanuki/manager/controller/controller.rb +1 -1
  9. data/app/tanuki/manager/page/page.rb +1 -1
  10. data/app/tanuki/meta_model/{manager.ttxt → meta_model.manager.ttxt} +0 -0
  11. data/app/tanuki/meta_model/{manager_base.ttxt → meta_model.manager_base.ttxt} +0 -0
  12. data/app/tanuki/meta_model/{model.ttxt → meta_model.model.ttxt} +0 -0
  13. data/app/tanuki/meta_model/{model_base.ttxt → meta_model.model_base.ttxt} +0 -0
  14. data/app/tanuki/meta_model/meta_model.rb +1 -2
  15. data/app/tanuki/model/controller/controller.rb +1 -1
  16. data/app/tanuki/model/page/page.rb +1 -1
  17. data/app/tanuki/page/missing/{default.thtml → missing.page.thtml} +1 -1
  18. data/app/tanuki/page/missing/missing.rb +3 -2
  19. data/app/user/page/home/home.rb +2 -0
  20. data/app/user/page/home/home.title.thtml +1 -0
  21. data/app/user/page/home/home.view.css +88 -0
  22. data/app/user/page/home/home.view.thtml +22 -0
  23. data/bin/tanuki +2 -1
  24. data/config/common.rb +1 -0
  25. data/config/common_application.rb +3 -6
  26. data/config/development_application.rb +0 -3
  27. data/lib/tanuki.rb +8 -7
  28. data/lib/tanuki/application.rb +108 -81
  29. data/lib/tanuki/argument.rb +10 -5
  30. data/lib/tanuki/argument/integer_range.rb +4 -2
  31. data/lib/tanuki/{behavior/object_behavior.rb → base_behavior.rb} +21 -4
  32. data/lib/tanuki/configurator.rb +20 -8
  33. data/lib/tanuki/const.rb +32 -0
  34. data/lib/tanuki/context.rb +18 -7
  35. data/lib/tanuki/controller.rb +517 -0
  36. data/lib/tanuki/css_compressor.rb +50 -0
  37. data/lib/tanuki/extensions/module.rb +21 -5
  38. data/lib/tanuki/extensions/rack/frozen_route.rb +35 -0
  39. data/lib/tanuki/extensions/rack/static_dir.rb +1 -1
  40. data/lib/tanuki/extensions/sequel/model.rb +7 -0
  41. data/lib/tanuki/i18n.rb +8 -6
  42. data/lib/tanuki/loader.rb +166 -33
  43. data/lib/tanuki/meta_model.rb +176 -0
  44. data/lib/tanuki/{behavior/model_behavior.rb → model_behavior.rb} +7 -3
  45. data/lib/tanuki/model_generator.rb +49 -29
  46. data/lib/tanuki/template_compiler.rb +72 -41
  47. data/lib/tanuki/utility.rb +11 -4
  48. data/lib/tanuki/utility/create.rb +52 -11
  49. data/lib/tanuki/utility/generate.rb +16 -10
  50. data/lib/tanuki/utility/version.rb +1 -1
  51. data/lib/tanuki/version.rb +7 -2
  52. metadata +50 -66
  53. data/app/tanuki/controller/default.thtml +0 -5
  54. data/app/tanuki/controller/index.thtml +0 -1
  55. data/app/user/page/index/default.thtml +0 -121
  56. data/app/user/page/index/index.rb +0 -2
  57. data/config/test_application.rb +0 -2
  58. data/lib/tanuki/behavior/controller_behavior.rb +0 -366
  59. data/lib/tanuki/behavior/meta_model_behavior.rb +0 -160
  60. data/lib/tanuki/extensions/rack/builder.rb +0 -26
  61. data/lib/tanuki/extensions/rack/server.rb +0 -18
  62. data/lib/tanuki/launcher.rb +0 -21
  63. data/lib/tanuki/utility/server.rb +0 -23
@@ -1,2 +0,0 @@
1
- class User::Page::Index < Tanuki::Controller
2
- end
@@ -1,2 +0,0 @@
1
- load_config :development_application
2
- set :root_page, ::Test::Page::Index
@@ -1,366 +0,0 @@
1
- module Tanuki
2
-
3
- # Tanuki::ControllerBehavior contains basic methods for a framework controller.
4
- # It is included in the base controller class.
5
- module ControllerBehavior
6
-
7
- include Enumerable
8
-
9
- internal_attr_reader :model, :logical_parent, :link
10
- internal_attr_accessor :logical_child, :visual_child
11
-
12
- # Creates new controller with context +ctx+, +logical_parent+ controller, +route_part+ definitions and a +model+.
13
- def initialize(ctx, logical_parent, route_part, model=nil)
14
- @_configured = false
15
- @_ctx = ctx
16
- @_model = model
17
- @_args = {}
18
- if @_logical_parent = logical_parent
19
-
20
- # Register controller arguments, as declared with Tanuki::ControllerBehavior#has_arg.
21
- @_route = route_part[:route]
22
- self.class.arg_defs.each_pair do |arg_name, arg_def|
23
- route_part[:args][arg_def[:index]] = @_args[arg_name] = arg_def[:arg].to_value(route_part[:args][arg_def[:index]])
24
- end
25
-
26
- @_link = self.class.grow_link(@_logical_parent, {:route => @_route, :args => @_args}, self.class.arg_defs)
27
- initialize_route(*route_part[:args])
28
- else
29
- @_link = '/'
30
- @_route = nil
31
- initialize_route
32
- end
33
- end
34
-
35
- # Invoked with route +args+ when current route is initialized.
36
- def initialize_route(*args)
37
- end
38
-
39
- # Returns controller context. Used internally by templates.
40
- def _ctx(ctx)
41
- @_ctx
42
- end
43
-
44
- # Initializes and retrieves child controller on +route+. Searches static, dynamic, and ghost routes (in that order).
45
- def [](route, *args)
46
- byname = (args.length == 1 and args[0].is_a? Hash)
47
- ensure_configured!
48
- key = [route, args.dup]
49
- if cached = @_cache[key]
50
-
51
- # Return form cache
52
- return cached
53
-
54
- elsif child_def = @_child_defs[route]
55
-
56
- # Search static routes
57
- klass = child_def[:class]
58
- args = klass.extract_args(args[0]) if byname
59
- child = klass.new(process_child_context(@_ctx, route), self, {:route => route, :args => args}, child_def[:model])
60
-
61
- else
62
-
63
- # Search dynamic routes
64
- found = false
65
- s = route.to_s
66
- @_child_collection_defs.each do |collection_def|
67
- if md = collection_def[:parse].match(s)
68
- a_route = md['route'].to_sym
69
- child_def = collection_def[:fetcher].fetch(a_route, collection_def[:format])
70
- if child_def
71
- klass = child_def[:class]
72
- args = klass.extract_args(args[0]) if byname
73
- embedded_args = klass.extract_args(md)
74
- args.each_index {|i| embedded_args[i] = args[i] if args[i] }
75
- child = klass.new(process_child_context(@_ctx, a_route), self,
76
- {:route => a_route, :args => embedded_args}, child_def[:model])
77
- found = true
78
- break child
79
- end # if
80
- end # each
81
-
82
- end
83
-
84
- # If still not found, search ghost routes
85
- child = missing_route(route, *args) unless found
86
-
87
- end
88
- @_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
89
- end
90
-
91
- # Returns +true+, if controller is active.
92
- def active?
93
- @_active
94
- end
95
-
96
- # Retrieves child controller class on +route+. Searches static, dynamic, and ghost routes (in that order).
97
- def child_class(route)
98
- ensure_configured!
99
- args = []
100
- key = [route, args]
101
- if cached = @_cache[key]
102
-
103
- # Return from cache
104
- return cached.class
105
-
106
- elsif child_def = @_child_defs[route]
107
-
108
- # Return from static routes
109
- return child_def[:class]
110
-
111
- else
112
-
113
- # Search dynamic routes
114
- s = route.to_s
115
- @_child_collection_defs.each do |collection_def|
116
- if md = collection_def[:parse].match(s)
117
- a_route = md['route'].to_sym
118
- child_def = collection_def[:fetcher].fetch(a_route, collection_def[:format])
119
- return child_def[:class] if child_def
120
- end
121
- end
122
-
123
- # If still not found, search ghost routes
124
- return (@_cache[key] = missing_route(route, *args)).class
125
-
126
- end
127
- end
128
-
129
- # Invoked when controller needs to be configured.
130
- def configure
131
- end
132
-
133
- # Returns +true+, if controller is current.
134
- def current?
135
- @_current
136
- end
137
-
138
- # If set, controller navigates to a given child route by default.
139
- # Returned object should be either +nil+ (don't navigate), or a +Hash+ with keys:
140
- # * +:route+ is the +Symbol+ for the route
141
- # * +:args+ contain route arguments +Hash+
142
- # * +:redirect+ makes a 302 redirect to this route, if true (optional)
143
- def default_route
144
- nil
145
- end
146
-
147
- # Calls +block+ once for each visible child controller on static or dynamic routes, passing it as a parameter.
148
- def each(&block)
149
- return Enumerator.new(self) unless block_given?
150
- ensure_configured!
151
- @_child_defs.each_pair do |route, child|
152
- if route.is_a? Regexp
153
- cd = @_child_collection_defs[child]
154
- cd[:fetcher].fetch_all(cd[:format]) do |child_def|
155
- key = [child_def[:route], []]
156
- unless child = @_cache[key]
157
- child = child_def[:class].new(process_child_context(@_ctx, route), self,
158
- {:route => child_def[:route], :args => {}}, child_def[:model])
159
- @_cache[key] = child
160
- end
161
- block.call child
162
- end
163
- else
164
- yield self[route] unless child[:hidden]
165
- end
166
- end
167
- self
168
- end
169
-
170
- # Invoked when controller configuration needs to be ensured.
171
- def ensure_configured!
172
- unless @_configured
173
- @_child_defs = {}
174
- @_child_collection_defs = []
175
- @_cache={}
176
- @_length = 0
177
- configure
178
- @_configured = true
179
- end
180
- nil
181
- end
182
-
183
- # Returns the link to the current controller, switching the active controller on the respective path level to +self+.
184
- def forward_link
185
- uri_parts = @_ctx.env['PATH_INFO'].split(/(?<!\$)\//)
186
- link_parts = link.split(/(?<!\$)\//)
187
- link_parts.each_index {|i| uri_parts[i] = link_parts[i] }
188
- uri_parts.join('/') << ((qs = @_ctx.env['QUERY_STRING']).empty? ? '' : "?#{qs}")
189
- end
190
-
191
- # Returns the number of visible child controllers on static and dynamic routes.
192
- def length
193
- if @_child_collection_defs.length > 0
194
- if @_length_is_valid
195
- @_length
196
- else
197
- @_child_collection_defs.each {|cd| @_length += cd[:fetcher].length }
198
- @_length_is_valid = true
199
- end
200
- else
201
- @_length
202
- end
203
- end
204
-
205
- # Invoked when child controller context needs to be processed before initializing.
206
- def process_child_context(ctx, route)
207
- ctx
208
- end
209
-
210
- alias_method :size, :length
211
-
212
- # Returns controller string representation. Defaults to route name.
213
- def to_s
214
- @_route.to_s
215
- end
216
-
217
- # Invoked when visual parent needs to be determined. Defaults to logical parent.
218
- def visual_parent
219
- @_logical_parent
220
- end
221
-
222
- private
223
-
224
- # Defines a child of class +klass+ on +route+ with +model+, optionally +hidden+.
225
- def has_child(klass, route, model=nil, hidden=false)
226
- @_child_defs[route] = {:class => klass, :model => model, :hidden => hidden}
227
- @_length += 1 unless hidden
228
- self
229
- end
230
-
231
- # Defines a child collection of type +parse_regexp+, formatted back by +format_string+.
232
- def has_child_collection(parse_regexp, format_string, child_def_fetcher)
233
- @_child_defs[parse_regexp] = @_child_collection_defs.size
234
- @_child_collection_defs << {:parse => parse_regexp, :format => format_string, :fetcher => child_def_fetcher}
235
- @_length_is_valid = false
236
- end
237
-
238
- # Invoked for +route+ with +args+ when a route is missing. This hook can be used to make ghost routes.
239
- def missing_route(route, *args)
240
- @_ctx.missing_page.new(@_ctx, self, {:route => route, :args => []})
241
- end
242
-
243
- # Tanuki::ControllerBehavior mixed-in class methods.
244
- module ClassMethods
245
-
246
- # Returns own or superclass argument definitions.
247
- def arg_defs
248
- @_arg_defs ||= superclass.arg_defs.dup
249
- end
250
-
251
- # Escapes characters +chrs+ and encodes a given string +s+ for use in links.
252
- def escape(s, chrs)
253
- s ? Rack::Utils.escape(s.to_s.gsub(/[\$#{chrs}]/, '$\0')) : nil
254
- end
255
-
256
- # Extracts arguments, initializing default values beforehand. Searches +md+ hash for default value overrides.
257
- def extract_args(md)
258
- res = []
259
- arg_defs.each_pair do |name, arg|
260
- res[arg[:index]] = md[name]
261
- end
262
- res
263
- end
264
-
265
- # Builds link from controller +ctrl+ to a given route.
266
- def grow_link(ctrl, route_part, arg_defs)
267
- own_link = escape(route_part[:route], '\/:') << route_part[:args].map do |k, v|
268
- arg_defs[k][:arg].default == v ? '' : ":#{escape(k, '\/:-')}-#{escape(v, '\/:')}"
269
- end.join
270
- "#{ctrl.link == '/' ? '' : ctrl.link}/#{own_link}"
271
- end
272
-
273
- # Defines an argument with a +name+, derived from type +obj+ with additional +args+.
274
- def has_arg(name, obj, *args)
275
- # TODO Ensure thread safety
276
- arg_defs[name] = {:arg => Argument.to_argument(obj, *args), :index => @_arg_defs.size}
277
- end
278
-
279
- # Prepares the extended module.
280
- def self.extended(mod)
281
- mod.instance_variable_set(:@_arg_defs, {})
282
- end
283
-
284
- end # ClassMethods
285
-
286
- extend ClassMethods
287
-
288
- class << self
289
-
290
- # Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
291
- def dispatch(ctx, klass, request_path)
292
- route_parts = parse_path(request_path)
293
-
294
- # Set logical children for active controllers
295
- curr = root_ctrl = klass.new(ctx, nil, nil, true)
296
- route_parts.each do |route_part|
297
- curr.instance_variable_set :@_active, true
298
- nxt = curr[route_part[:route], *route_part[:args]]
299
- curr.logical_child = nxt
300
- curr = nxt
301
- end
302
-
303
- # Set links for active controllers and default routes
304
- while route_part = curr.default_route
305
-
306
- # Do a redirect, if some controller in the chain asks for it
307
- if route_part[:redirect]
308
- klass = curr.child_class(route_part)
309
- return {:type => :redirect, :location => grow_link(curr, route_part, klass.arg_defs)}
310
- end
311
-
312
- # Add default route as logical child
313
- curr.instance_variable_set :@_active, true
314
- nxt = curr[route_part[:route], *route_part[:args]]
315
- curr.logical_child = nxt
316
- curr = nxt
317
-
318
- end
319
-
320
- # Find out dispatch result type from current controller
321
- curr.instance_variable_set :@_active, true
322
- curr.instance_variable_set :@_current, true
323
- type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
324
-
325
- # Set visual children for active controllers
326
- prev = curr
327
- while curr = prev.visual_parent
328
- curr.visual_child = prev
329
- prev = curr
330
- end
331
-
332
- {:type => type, :controller => prev}
333
- end
334
-
335
- # Extends the including module with Tanuki::ControllerBehavior::ClassMethods.
336
- def included(mod)
337
- mod.extend ClassMethods
338
- end
339
-
340
- private
341
-
342
- # Parses +path+ to return route name and arguments.
343
- def parse_path(path)
344
- path[1..-1].split(/(?<!\$)\//).map do |s|
345
- arr = s.gsub('$/', '/').split(/(?<!\$):/)
346
- route_part = {:route => unescape(arr[0]).to_sym}
347
- args = {}
348
- arr[1..-1].each do |argval|
349
- varr = argval.split(/(?<!\$)-/)
350
- args[unescape(varr[0])] = unescape(varr[1..-1].join) # TODO Predict argument
351
- end
352
- route_part[:args] = extract_args(args)
353
- route_part
354
- end # do
355
- end
356
-
357
- # Unescapes a given link part for internal use.
358
- def unescape(s)
359
- s ? s.gsub(/\$([\/\$:-])/, '\1') : nil
360
- end
361
-
362
- end # class << self
363
-
364
- end # ControllerBehavior
365
-
366
- end # Tanuki
@@ -1,160 +0,0 @@
1
- module Tanuki
2
-
3
- # Tanuki::MetaModelBehavior contains all methods for the meta-model.
4
- # In is included in the meta-model class.
5
- module MetaModelBehavior
6
-
7
- # Creates new meta-model +name+ in +namespace+.
8
- # Model schema is passed as +data+.
9
- # Stucture +models+ contains all models being generated.
10
- def initialize(namespace, name, data, models)
11
- @namespace = namespace
12
- @name = name
13
- @data = data
14
- @models = models
15
- end
16
-
17
- # Returns class name for a given class type.
18
- def class_name_for(class_type)
19
- case class_type
20
- when :model, :model_base then "#{@namespace}_Model_#{@name}"
21
- when :manager, :manager_base then "#{@namespace}_Manager_#{@name}"
22
- end
23
- end
24
-
25
- # Returns an array of code for alias-column name pair.
26
- def key
27
- @key.inspect
28
- end
29
-
30
- # Prepares data for template generation.
31
- # Processes own keys, fields, etc.
32
- def process!
33
- process_source!
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: ...
43
- end
44
-
45
- def process_key!
46
- if @source.include? 'key' && @source['key'].nil?
47
- @key = []
48
- else
49
- @key = @source['key'] || 'id'
50
- end
51
- @key = [@key] if @key.is_a? String
52
- raise 'invalid key' unless @key.is_a? Array
53
- @key.map! do |k|
54
- parts = k.split('.').map {|p| p.to_sym }
55
- raise "invalid key field #{k}" if parts.count > 2
56
- if parts.count == 2
57
- raise 'all key fields should belong to the first-source' if parts[0] != @first_source.to_s
58
- parts
59
- else
60
- [@first_source, parts[0]]
61
- end
62
- end
63
- end
64
-
65
- # Prepares data for building a Sequel +order+ clause.
66
- def process_order!
67
- # TODO: ...
68
- end
69
-
70
- # Extracts the model firts-source information form the YAML @data
71
- # and performs
72
- def process_source!
73
- guess_table = @name.pluralize
74
- @data ||= {}
75
- @source = @data['source'] || guess_table
76
- @source = {'table' => @source} if @source.is_a? String
77
- @first_source = (@source['table'] || guess_table).downcase.to_sym
78
- end
79
-
80
- def process_joins!
81
- @joins = {}
82
- @joins[@first_source] = nil
83
- joins = @source['joins'] || {}
84
- joins = [joins] if joins.is_a? String
85
- joins = Hash[*joins.collect {|v| [v, nil] }.flatten] if joins.is_a? Array
86
- if joins.is_a? Hash
87
- joins.each_pair do |table_alias, join|
88
- table_alias = table_alias.to_sym
89
- raise "#{table_alias} is already in use" if @joins.include? table_alias
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
94
- else
95
- on = join
96
- table_name = table_alias
97
- join_type = :inner
98
- end
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]
122
- else
123
- on = {}
124
- @key.each do |k|
125
- on[[table_alias, (@first_source.to_s.singularize << '_' << k[1].to_s).to_sym]] = k
126
- end
127
- end
128
- @joins[table_alias] = {
129
- :type => join_type,
130
- :table => table_name,
131
- :alias => table_alias,
132
- :on => on
133
- }
134
- end
135
- else
136
- raise "`joins' should be either nil or string or array or hash"
137
- end
138
- end
139
-
140
- # Prepares data for template generation.
141
- # Processes foreign keys, fields, etc.
142
- def process_relations!
143
-
144
- end
145
-
146
- # Returns code for alias-column name pair for field +field_name+.
147
- def qualified_name(field_name)
148
- parts = field_name.split('.')
149
- if parts.length == 1
150
- "%w{:#{@first_source} :#{field_name}}"
151
- elsif parts.length == 2
152
- "%w{:#{parts[0]} :#{parts[1]}}"
153
- else
154
- raise "field name for model #{@namespace}.#{@name} is invalid"
155
- end
156
- end
157
-
158
- end # MetaModelBehavior
159
-
160
- end # Tanuki