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.
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