tanuki 0.2.1 → 0.3.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.
@@ -1,2 +1,2 @@
1
- class <%= class_name_for :manager %> < <%= class_name_for :manager_base %>
1
+ class <%= class_name_for :manager %>
2
2
  end
@@ -1,2 +1,2 @@
1
- class <%= class_name_for :model %> < <%= class_name_for :model_base %>
1
+ class <%= class_name_for :model %>
2
2
  end
@@ -3,10 +3,10 @@ class <%= class_name_for :model_base %> < Tanuki_Base
3
3
 
4
4
  class << self
5
5
  def extract_key(data)
6
- [<% key.each do |field_name| %>data[:<%= field_name.inspect %>], <% end %>]
6
+ [<% @key.each do |qualified| %>data[<%= qualified[0].inspect %>][<%= qualified[1].inspect %>], <% end %>]
7
7
  end
8
8
 
9
- def get(ctx,*args)
9
+ def get(ctx, *args)
10
10
  end
11
11
  end
12
12
  end
data/bin/tanuki CHANGED
@@ -35,8 +35,9 @@ module Tanuki
35
35
 
36
36
  def generate(cwd)
37
37
  version unless @in_repl
38
- cwd = File.expand_path(cwd ? cwd : '.')
38
+ cwd = cwd ? File.expand_path(cwd) : Dir.pwd
39
39
  puts "Working directory is: #{cwd}\nTo specify another: `generate <path>'"
40
+ require 'active_support/inflector'
40
41
  require 'yaml'
41
42
  require 'fileutils'
42
43
  require 'tanuki/extensions/object'
@@ -48,12 +49,17 @@ module Tanuki
48
49
  require 'tanuki/loader'
49
50
  require 'tanuki/template_compiler'
50
51
  Loader.context = ctx = Context
51
- cfg = Configurator.new(Context, cwd, File.expand_path(File.join('..', '..', 'config'), __FILE__))
52
- cfg.load_config :common
52
+ default_root = File.expand_path(File.join('..', '..'), __FILE__)
53
+ cfg = Configurator.new(Context, cwd)
54
+ if cwd != default_root
55
+ cfg.config_root = File.join(default_root, 'config')
56
+ cfg.load_config :common
57
+ end
53
58
  cfg.config_root = File.join(cwd, 'config')
54
- cfg.load_config :common, true
59
+ cfg.load_config :common, cwd != default_root
55
60
  puts "\n looking for models"
56
61
  @tried = {}
62
+ @models = {}
57
63
  local_schema_root = File.expand_path(File.join('..', '..', 'schema'), __FILE__)
58
64
  generate_in(ctx.schema_root, ctx)
59
65
  generate_in(local_schema_root, ctx) if ctx.schema_root != local_schema_root
@@ -65,15 +71,34 @@ module Tanuki
65
71
 
66
72
  def generate_in(schema_root, ctx)
67
73
  Dir.entries(schema_root)[2..-1].each do |namespace_path|
68
- namespace = namespace_path.capitalize
74
+ namespace_name = namespace_path.split('_').map {|s| s.capitalize }.join
75
+ namespace = @models[namespace_name] = {}
69
76
  Dir.glob(File.join(schema_root, namespace_path, 'models', '*.yml')) do |file_path|
70
77
  model_name = File.basename(file_path, '.yml').split('_').map {|s| s.capitalize }.join
71
- if @tried.include? namespace_model_name = "#{namespace}.#{model_name}"
78
+ meta_model = namespace[model_name] = Tanuki_MetaModel.new(
79
+ namespace_name,
80
+ model_name,
81
+ YAML.load_file(file_path),
82
+ @models
83
+ )
84
+ meta_model.process!
85
+ if @tried.include? namespace_model_name = "#{namespace_name}.#{model_name}"
72
86
  next
73
87
  else
74
88
  @tried[namespace_model_name] = {:generated => [], :failed => [], :skipped => []}
75
89
  end
76
- meta_model = Tanuki_MetaModel.new(namespace, model_name, YAML.load_file(file_path))
90
+ end
91
+ end
92
+
93
+ @models.each do |namespace_name, namespace |
94
+ namespace.each do |model_name, meta_model|
95
+ meta_model.process_relations!
96
+ end
97
+ end
98
+
99
+ @models.each do |namespace_name, namespace |
100
+ namespace.each do |model_name, meta_model|
101
+ namespace_model_name = "#{namespace_name}.#{model_name}"
77
102
  {
78
103
  :model => false,
79
104
  :model_base => true,
@@ -136,19 +161,9 @@ module Tanuki
136
161
 
137
162
  def server(env=nil)
138
163
  env = env ? env.to_sym : :development
139
- puts %{Calling for a Tanuki in "#{pwd = Dir.pwd}"}
164
+ puts %{Calling for a Tanuki in "#{Dir.pwd}"}
140
165
  version unless @in_repl
141
166
  require 'tanuki'
142
- allow_run = false
143
- begin
144
- @cfg = Configurator.new(Context, pwd, File.expand_path(File.join('..', '..', 'config'), __FILE__))
145
- @cfg.load_config(([:development, :production].include? env) ? :"#{env}_application" : :common_application)
146
- @cfg.config_root = File.join(pwd, 'config')
147
- @cfg.load_config :"#{env}_application", true
148
- allow_run = true
149
- rescue NameError => e
150
- puts "Tanuki wanted `#{e.name}', but you didn't have any."
151
- end
152
167
  begin
153
168
  Application.run
154
169
  false
@@ -158,7 +173,7 @@ module Tanuki
158
173
  rescue SystemCallError
159
174
  puts 'Tanuki ran away! Someone else is playing here.'
160
175
  true
161
- end if allow_run
176
+ end if Application.configure(env)
162
177
  end
163
178
 
164
179
  def start_repl
@@ -8,6 +8,7 @@ use Rack::StaticDir, 'public'
8
8
  set :server, [:thin, :mongrel, :webrick]
9
9
  set :host, '0.0.0.0'
10
10
  set :port, 3000
11
+ set :development, false
11
12
 
12
13
  # Default controllers
13
14
  set :root_page, ::User_Page_Index
@@ -15,6 +16,7 @@ set :missing_page, ::Tanuki_Page_Missing
15
16
 
16
17
  # Internationalization
17
18
  set :i18n, false
19
+ set :i18n_redirect, false
18
20
  set :language, nil
19
21
  set :language_fallback, {}
20
22
  set :languages, proc { language_fallback.keys }
@@ -9,20 +9,55 @@ module Tanuki
9
9
 
10
10
  class << self
11
11
 
12
+ # Initializes application settings using configuration for environment +env+.
13
+ # These include settings for server, context, and middleware.
14
+ # Returns true, if configuration is successful.
15
+ def configure(env)
16
+ @environment = env
17
+ begin
18
+ default_root = File.expand_path(File.join('..', '..', '..'), __FILE__)
19
+ @cfg = Configurator.new(Context, pwd = Dir.pwd)
20
+ if pwd != default_root
21
+ @cfg.config_root = File.join(default_root, 'config')
22
+ @cfg.load_config(([:development, :production].include? env) ? :"#{env}_application" : :common_application)
23
+ end
24
+ @cfg.config_root = File.join(pwd, 'config')
25
+ @cfg.load_config :"#{env}_application", pwd != default_root
26
+ return true
27
+ rescue NameError => e
28
+ if e.name =~ /\AA-Z/
29
+ raise NameError, "missing class or module for constant `#{e.name}'", e.backtrace
30
+ else
31
+ raise e
32
+ end
33
+ end
34
+ false
35
+ end
36
+
37
+ # Add utilized middleware to a given Rack::Builder instance +rack_builder+.
38
+ def configure_middleware(rack_builder)
39
+ @rack_middleware.each {|item| rack_builder.use(item[0], *item[1], &item[2]) }
40
+ end
41
+
12
42
  # Removes all occurences of a given +middleware+ from the Rack middleware pipeline.
13
43
  def discard(middleware)
14
44
  @rack_middleware.delete_if {|item| item[0] == middleware }
15
45
  end
16
46
 
47
+ # Returns current environment +Symbol+, if application is configured.
48
+ # Returns +nil+ otherwise.
49
+ def environment
50
+ @environment ||= nil
51
+ end
52
+
17
53
  # Runs the application with current settings.
18
54
  def run
19
- rack_builder = Rack::Builder.new
20
- @rack_middleware.each {|item| rack_builder.use(item[0], *item[1], &item[2]) }
55
+ configure_middleware(rack_builder = Rack::Builder.new)
21
56
  rack_builder.run(rack_app)
22
- srv = available_server
23
- puts "A wild Tanuki appears! Press Ctrl-C to set it free.",
24
- "You used #{srv.name.gsub(/.*::/, '')} at #{@context.host}:#{@context.port}."
25
- srv.run rack_builder.to_app, :Host => @context.host, :Port => @context.port
57
+ @context.running_server = available_server
58
+ puts "A#{'n' if @environment =~ /\A[aeiou]/} #{@environment} Tanuki appears! Press Ctrl-C to set it free.",
59
+ "You used #{@context.running_server.name.gsub(/.*::/, '')} at #{@context.host}:#{@context.port}."
60
+ @context.running_server.run rack_builder.to_app, :Host => @context.host, :Port => @context.port
26
61
  end
27
62
 
28
63
  # Adds a given +middleware+ with optional +args+ and +block+ to the Rack middleware pipeline.
@@ -77,7 +77,7 @@ module Tanuki
77
77
  @_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
78
78
  end
79
79
 
80
- # Returns true, if controller is active.
80
+ # Returns +true+, if controller is active.
81
81
  def active?
82
82
  @_active
83
83
  end
@@ -112,12 +112,16 @@ module Tanuki
112
112
  def configure
113
113
  end
114
114
 
115
- # Returns true, if controller is current.
115
+ # Returns +true+, if controller is current.
116
116
  def current?
117
117
  @_current
118
118
  end
119
119
 
120
120
  # If set, controller navigates to a given child route by default.
121
+ # Returned object should be either +nil+ (don't navigate), or a +Hash+ with keys:
122
+ # * +:route+ is the +Symbol+ for the route
123
+ # * +:args+ contain route arguments +Hash+
124
+ # * +:redirect+ makes a 302 redirect to this route, if true (optional)
121
125
  def default_route
122
126
  nil
123
127
  end
@@ -267,28 +271,33 @@ module Tanuki
267
271
 
268
272
  # Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
269
273
  def dispatch(ctx, klass, request_path)
270
- parts = parse_path(request_path)
274
+ route_parts = parse_path(request_path)
271
275
  curr = root_ctrl = klass.new(ctx, nil, nil, true)
272
- parts.each do |part|
276
+ route_parts.each do |route_part|
273
277
  curr.instance_variable_set :@_active, true
274
- nxt = curr[part[:route], *part[:args]]
278
+ nxt = curr[route_part[:route], *route_part[:args]]
279
+ curr.logical_child = nxt
280
+ curr = nxt
281
+ end
282
+ while route_part = curr.default_route
283
+ if route_part[:redirect]
284
+ klass = curr.child_class(route_part)
285
+ return {:type => :redirect, :location => grow_link(curr, route_part, klass.arg_defs)}
286
+ end
287
+ curr.instance_variable_set :@_active, true
288
+ nxt = curr[route_part[:route], *route_part[:args]]
275
289
  curr.logical_child = nxt
276
290
  curr = nxt
277
291
  end
278
292
  curr.instance_variable_set :@_active, true
279
293
  curr.instance_variable_set :@_current, true
280
- if route = curr.default_route
281
- klass = curr.child_class(route)
282
- {:type => :redirect, :location => grow_link(curr, route, klass.arg_defs)}
283
- else
284
- type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
294
+ type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
295
+ prev = curr
296
+ while curr = prev.visual_parent
297
+ curr.visual_child = prev
285
298
  prev = curr
286
- while curr = prev.visual_parent
287
- curr.visual_child = prev
288
- prev = curr
289
- end
290
- {:type => type, :controller => prev}
291
299
  end
300
+ {:type => type, :controller => prev}
292
301
  end
293
302
 
294
303
  # Extends the including module with Tanuki::ControllerBehavior::ClassMethods.
@@ -1,13 +1,20 @@
1
1
  module Tanuki
2
2
 
3
+ # Tanuki::MetaModelBehavior contains all methods for the meta-model.
4
+ # In is included in the meta-model class.
3
5
  module MetaModelBehavior
4
6
 
5
- def initialize(namespace, name, data)
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)
6
11
  @namespace = namespace
7
12
  @name = name
8
13
  @data = data
14
+ @models = models
9
15
  end
10
16
 
17
+ # Returns class name for a given class type.
11
18
  def class_name_for(class_type)
12
19
  case class_type
13
20
  when :model, :model_base then "#{@namespace}_Model_#{@name}"
@@ -15,24 +22,92 @@ module Tanuki
15
22
  end
16
23
  end
17
24
 
25
+ # Returns an array of code for alias-column name pair.
18
26
  def key
19
- if @data['key'].nil?
20
- []
21
- elsif @data['key'].is_a? Array
22
- @data['key'].map {|item| qualified_name(item) }
23
- elsif @data['key'].is_a? String
24
- [qualified_name(@data['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
+ end
36
+
37
+ def process_key!
38
+ @key = @source['key'] || 'id'
39
+ @key = [@key] if @key.is_a? String
40
+ raise 'invalid key' unless @key.is_a? Array
41
+ @key.map! do |k|
42
+ parts = k.split('.').map {|p| p.to_sym }
43
+ raise "invalid key field #{k}" if parts.count > 2
44
+ if parts.count == 2
45
+ raise 'all key fields should belong to the first-source' if parts[0] != @first_source.to_s
46
+ parts
47
+ else
48
+ [@first_source, parts[0]]
49
+ end
50
+ end
51
+ end
52
+
53
+ # Extracts the model firts-source information form the YAML @data
54
+ # and performs
55
+ def process_source!
56
+ guess_table = @name.pluralize
57
+ @data ||= {}
58
+ @source = @data['source'] || guess_table
59
+ @source = {'table' => @source} if @source.is_a? String
60
+ @first_source = (@source['table'] || guess_table).downcase.to_sym
61
+ end
62
+
63
+ def process_joins!
64
+ @joins = {}
65
+ @joins[@first_source] = nil
66
+ end
67
+
68
+ # Prepares data for template generation.
69
+ # Processes foreign keys, fields, etc.
70
+ def process_relations!
71
+ joins = @source['joins'] || {}
72
+ joins = [joins] if joins.is_a? String
73
+ joins = Hash[*joins.collect {|v| [v, nil] }.flatten] if joins.is_a? Array
74
+ if joins.is_a? Hash
75
+ joins.each_pair do |table_alias, join|
76
+ table_alias = table_alias.to_sym
77
+ raise "#{table_alias} is already in use" if @joins.include? table_alias
78
+ if join
79
+ if join['on'].is_a Hash
80
+ table_name = join['table'] || table_alias
81
+ on = join['on']
82
+ else
83
+ on = join
84
+ table_name = table_alias
85
+ end
86
+ else
87
+ on = nil
88
+ table_name = table_alias
89
+ end
90
+ if on
91
+ else
92
+ on = {}
93
+ @key.each do |k|
94
+ on[[table_alias, @first_source.to_s.singularize.to_sym]] = []
95
+ # TODO choose a right priciple
96
+ end
97
+ end
98
+ end
25
99
  else
26
- raise "key for model #{@namespace}.#{@name} is invalid"
100
+ raise "`joins' should be either nil or string or array or hash"
27
101
  end
28
102
  end
29
103
 
104
+ # Returns code for alias-column name pair for field +field_name+.
30
105
  def qualified_name(field_name)
31
106
  parts = field_name.split('.')
32
107
  if parts.length == 1
33
- ":#{field_name}"
108
+ "%w{:#{@first_source} :#{field_name}}"
34
109
  elsif parts.length == 2
35
- ":#{parts[1]}.qualify(:#{parts[0]}"
110
+ "%w{:#{parts[0]} :#{parts[1]}}"
36
111
  else
37
112
  raise "field name for model #{@namespace}.#{@name} is invalid"
38
113
  end
@@ -1,17 +1,23 @@
1
1
  module Tanuki
2
2
 
3
+ # Tanuki::ModelBehavior contains basic methods for a framework model.
4
+ # In is included in the base model class.
3
5
  module ModelBehavior
4
6
 
5
- def initialize(data = {}, lazy = false)
7
+ # Creates new model with dataset row +data+.
8
+ # If the model is +lazy+, +data+ should contain only row keys.
9
+ def initialize(data={}, lazy=false)
6
10
  @_data = data
7
11
  @_loaded = !lazy
8
12
  end
9
13
 
14
+ # Returns the value of a given attribute, loading the model on demand.
10
15
  def [](attribute)
11
- ensure_loaded!
16
+ ensure_loaded! unless self.class[attribute].present_in(@_data)
12
17
  self.class[attribute].get(@_data)
13
18
  end
14
19
 
20
+ # Sets the value of a given attribute.
15
21
  def []=(attribute, value)
16
22
  @_errors ||= {}
17
23
  @_original ||= {}
@@ -24,16 +30,27 @@ module Tanuki
24
30
  end
25
31
  end
26
32
 
33
+ # Returns the modification errors hash.
34
+ def errors
35
+ @_errors ||= {}
36
+ @_errors
37
+ end
38
+
39
+ # Returns transport representation of data.
27
40
  def internals_get(attribute)
28
41
  self.class[attribute].internals_get(@_data)
29
42
  end
30
43
 
44
+ # Sets transport representation of data.
31
45
  def internals_set(attribute, internal_value)
32
46
  @_errors ||= {}
33
47
  internals_set(self.class[attribute], @_data)
34
48
  end
35
49
 
50
+ # Returns model updates hash.
51
+ # This method is used internally to generate a data source update.
36
52
  def get_updates
53
+ # TODO Rewrite this properly
37
54
  @_original ||= {}
38
55
  original_data = {}
39
56
  self.class.attributes.each_pair do |name, attrib|
@@ -46,30 +63,23 @@ module Tanuki
46
63
  updates
47
64
  end
48
65
 
49
- def get_error(attribute)
50
- @_errors ||= {}
51
- @_errors[attribute]
52
- end
53
-
54
- def invalid?(attribute)
55
- @_errors.include? attribute
56
- end
57
-
66
+ # Returns +true+ if there are any modification errors.
58
67
  def has_errors?
59
68
  @_errors ||= {}
60
69
  @_errors == {}
61
70
  end
62
71
 
63
- def errors
64
- @_errors ||= {}
65
- @_errors
66
- end
67
-
68
72
  module ClassMethods
69
73
 
70
- def create(data, ctx, lazy = false) # IDENTITY TRACKING AND LAZY LOADING
74
+ # Returns meta-information for a given attribute.
75
+ def [](attribute)
76
+ @_attributes[attribute]
77
+ end
78
+
79
+ # Creates new model, or returns existing one.
80
+ def get(data, ctx, lazy=false) # IDENTITY TRACKING AND LAZY LOADING
71
81
  entity_key = extract_key(data)
72
- key = [self, entity_key] #extract_key is generated ad hoc by model compiler!
82
+ key = [self, entity_key] # extract_key is generated ad hoc by model compiler!
73
83
  if cached = ctx.entity_cache[key]
74
84
  cached
75
85
  else
@@ -77,30 +87,29 @@ module Tanuki
77
87
  end
78
88
  end
79
89
 
90
+ # Assigns +attribute+ with definition +attr_def+ to model.
80
91
  def has_attribute(attribute, attr_def)
81
92
  @_attributes ||= superclass.instance_variable_get(:@_attributes).dup
82
93
  @_attributes[attribute] = attr_def
83
94
  end
84
95
 
85
- def [](attribute)
86
- @_attributes[attribute]
87
- end
88
-
89
- def has_reference(attribute, reference_def)
90
- @_references ||= superclass.instance_variable_get(:@_references).dup
91
- @_references[attribute] = reference_def
96
+ # Adds a relation +name+ with definition +relation_def+ to model.
97
+ def has_relation(name, relation_def)
98
+ @_relations ||= superclass.instance_variable_get(:@_relations).dup
99
+ @_relations[name] = relation_def
92
100
  end
93
101
 
94
102
  # Prepares the extended module.
95
103
  def self.extended(mod)
96
104
  mod.instance_variable_set(:@_attributes, {})
97
- mod.instance_variable_set(:@_references, {})
105
+ mod.instance_variable_set(:@_relations, {})
98
106
  end
99
107
 
100
108
  end # end ClassMethods
101
109
 
102
110
  class << self
103
111
 
112
+ # Extends the including module with Tanuki::ModelBehavior::ClassMethods.
104
113
  def included(mod)
105
114
  mod.extend ClassMethods
106
115
  end
@@ -1,7 +1,6 @@
1
1
  module Tanuki
2
2
 
3
3
  # Tanuki::Configurator is a scope for evaluating a Tanuki application configuration block.
4
- # Use Tanuki::development_application and Tanuki::production_application to create such a block.
5
4
  class Configurator
6
5
 
7
6
  # Configuration root.
@@ -12,7 +11,6 @@ module Tanuki
12
11
  def initialize(ctx, root, config_root=nil)
13
12
  @context = ctx
14
13
  set :root, root ? root : Dir.pwd
15
- @config_root = config_root ? config_root : File.join(@context.root, 'config')
16
14
  end
17
15
 
18
16
  # Loads and executes a given configuraion file with symbolic name +config+.
@@ -7,41 +7,66 @@ module Tanuki
7
7
 
8
8
  @_defined = {}
9
9
 
10
- # Creates and returns child context object.
11
- # This object's superclass is going to be current context class.
12
- def self.child
13
- child = Class.new(self)
14
- child.instance_variable_set(:@_defined, {})
15
- child
16
- end
17
-
18
- # Allowes arbitary values to be assigned to context with a +key=+ method.
19
- # A reader in context object class is created for each assigned value.
20
- def self.method_missing(sym, arg=nil)
21
- match = sym.to_s.match(/\A(?!(?:child|method_missing)=\Z)([^=]+)(=)?\Z/)
22
- raise "`#{sym}' method cannot be called for Context and its descendants" unless match
23
- defined = @_defined
24
- class << self; self; end.instance_eval do
25
- method_sym = match[1].to_sym
26
- if defined.include? method_sym
27
- undef_method method_sym
28
- else
29
- defined[method_sym] = nil
30
- end
31
- if arg.is_a? Proc
32
- define_method(method_sym, &arg)
33
- else
34
- define_method(method_sym) { arg }
10
+ class << self
11
+
12
+ # Creates and returns child context object.
13
+ # This object's superclass is going to be current context class.
14
+ def child
15
+ child = Class.new(self)
16
+ child.instance_variable_set(:@_defined, {})
17
+ child
18
+ end
19
+
20
+ # Returns a printable version of Tanuki::Context, represented as a +Hash+.
21
+ # Can be used during development for inspection purposes.
22
+ #--
23
+ # When changing this method, remember to update `#{__LINE__ + 12}' to `defined.inspect` line number.
24
+ # This is required to avoid infinite recursion.
25
+ def inspect
26
+ return to_s if caller.any? {|entry_point| entry_point =~ /\A#{__FILE__}:#{__LINE__ + 12}/}
27
+ defined = {}
28
+ ancestors.each do |ancestor|
29
+ ancestor.instance_variable_get(:@_defined).each_key do |key|
30
+ begin
31
+ defined[key] ||= send(key)
32
+ rescue ArgumentError
33
+ defined[key] ||= method(key)
34
+ end
35
+ end
36
+ break if ancestor.equal? Context
35
37
  end
36
- return arg
37
- end if match[2]
38
- super
39
- end
40
-
41
- # Disallow context instantiation
42
- def self.new
43
- raise "contexts cannot be instantiated"
44
- end
38
+ defined.inspect
39
+ end
40
+
41
+ # Allowes arbitary values to be assigned to context with a +key=+ method.
42
+ # A reader in context object class is created for each assigned value.
43
+ def method_missing(sym, arg=nil)
44
+ match = sym.to_s.match(/\A(?!(?:child|inspect|method_missing)=\Z)([^=]+)(=)?\Z/)
45
+ raise "`#{sym}' method cannot be called for Context and its descendants" unless match
46
+ defined = @_defined
47
+ class << self; self; end.instance_eval do
48
+ method_sym = match[1].to_sym
49
+ if defined.include? method_sym
50
+ undef_method method_sym
51
+ else
52
+ defined[method_sym] = nil
53
+ end
54
+ if arg.is_a? Proc
55
+ define_method(method_sym, &arg)
56
+ else
57
+ define_method(method_sym) { arg }
58
+ end
59
+ return arg
60
+ end if match[2]
61
+ super
62
+ end
63
+
64
+ # Disallow context instantiation
65
+ def new
66
+ raise "contexts cannot be instantiated"
67
+ end
68
+
69
+ end # end class << self
45
70
 
46
71
  end # end Context
47
72
 
@@ -0,0 +1,26 @@
1
+ module Rack
2
+ class Builder
3
+
4
+ # Initializes application settings using configuration for environment +env+ and +rackup+ arguments.
5
+ # Application is configured for development, if no environment is specified.
6
+ # Returns Tanuki::Application::rack_app.
7
+ #
8
+ # This should be invoked from Rackup configuration files (e.g. +config.ru+):
9
+ #
10
+ # #\ -p 3000
11
+ # require 'tanuki'
12
+ # run tanuki
13
+ def tanuki(env=nil)
14
+ puts %{Calling for a Tanuki in "#{Dir.pwd}"}
15
+ at_exit { puts 'Tanuki ran away!' }
16
+ builder = self
17
+ Tanuki::Application.instance_eval do
18
+ configure(env = env ? env.to_sym : :development)
19
+ configure_middleware(builder)
20
+ puts "A racked #{env} Tanuki appears!"
21
+ rack_app
22
+ end
23
+ end
24
+
25
+ end # end
26
+ end # end Rack
@@ -0,0 +1,18 @@
1
+ module Rack
2
+ class Server
3
+
4
+ # Wraps around Rack::Server#options to update application configuration accordingly.
5
+ def options_with_tanuki(*args, &block)
6
+ rack_server = self
7
+ rackup_options = options_without_tanuki(*args, &block)
8
+ Tanuki::Application.instance_eval do
9
+ rackup_options.each_pair {|k, v| @context.send :"#{k.downcase}=", v }
10
+ @context.running_server = rack_server.server
11
+ end
12
+ rackup_options
13
+ end
14
+
15
+ alias_method_chain :options, :tanuki
16
+
17
+ end # end
18
+ end # end Rack
@@ -1,7 +1,7 @@
1
1
  module Rack
2
2
  class StaticDir
3
3
 
4
- # Initializes a +Rack::File+ server at +root+ or +Dir.pwd+.
4
+ # Initializes a Rack::File server at +root+ or +Dir.pwd+.
5
5
  def initialize(app, root=nil)
6
6
  @app = app
7
7
  @file_server = Rack::File.new(root || Dir.pwd)
data/lib/tanuki/i18n.rb CHANGED
@@ -15,7 +15,7 @@ module Tanuki
15
15
  # Returns default route according to default language.
16
16
  def default_route
17
17
  raise 'default language is not configured' unless @_ctx.language
18
- {:route => @_ctx.language.to_s, :args => {}}
18
+ {:route => @_ctx.language, :args => {}, :redirect => @_ctx.i18n_redirect}
19
19
  end
20
20
 
21
21
  # Calls default view of visual child.
@@ -1,6 +1,6 @@
1
1
  module Tanuki
2
2
 
3
3
  # Tanuki framework version.
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
 
6
6
  end # end Tanuki
data/lib/tanuki.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  libdir = File.dirname(__FILE__)
2
2
  $:.unshift(libdir) unless $:.include?(libdir)
3
3
 
4
+ require 'active_support/all'
4
5
  require 'rack'
5
6
  require 'fileutils'
6
7
  require 'sequel'
@@ -10,6 +11,8 @@ require 'escape_utils/url/rack'
10
11
  require 'tanuki/version'
11
12
  require 'tanuki/extensions/module'
12
13
  require 'tanuki/extensions/object'
14
+ require 'tanuki/extensions/rack/builder'
15
+ require 'tanuki/extensions/rack/server'
13
16
  require 'tanuki/extensions/rack/static_dir'
14
17
  require 'tanuki/behavior/controller_behavior'
15
18
  require 'tanuki/behavior/meta_model_behavior'
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 1
9
- version: 0.2.1
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Anatoly Ressin
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-29 00:00:00 +03:00
18
+ date: 2010-09-10 00:00:00 +03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -24,7 +24,7 @@ dependencies:
24
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ">="
27
+ - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  segments:
30
30
  - 1
@@ -38,13 +38,12 @@ dependencies:
38
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
- - - ">="
41
+ - - ~>
42
42
  - !ruby/object:Gem::Version
43
43
  segments:
44
44
  - 3
45
45
  - 14
46
- - 0
47
- version: 3.14.0
46
+ version: "3.14"
48
47
  type: :runtime
49
48
  version_requirements: *id002
50
49
  - !ruby/object:Gem::Dependency
@@ -53,30 +52,56 @@ dependencies:
53
52
  requirement: &id003 !ruby/object:Gem::Requirement
54
53
  none: false
55
54
  requirements:
56
- - - ">="
55
+ - - ~>
57
56
  - !ruby/object:Gem::Version
58
57
  segments:
59
58
  - 0
60
59
  - 1
61
- - 5
62
- version: 0.1.5
60
+ version: "0.1"
63
61
  type: :runtime
64
62
  version_requirements: *id003
65
63
  - !ruby/object:Gem::Dependency
66
- name: rspec
64
+ name: activesupport
67
65
  prerelease: false
68
66
  requirement: &id004 !ruby/object:Gem::Requirement
69
67
  none: false
70
68
  requirements:
71
- - - ">="
69
+ - - ~>
72
70
  - !ruby/object:Gem::Version
73
71
  segments:
74
- - 1
75
72
  - 3
76
73
  - 0
77
- version: 1.3.0
78
- type: :development
74
+ version: "3.0"
75
+ type: :runtime
79
76
  version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: i18n
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ - 4
88
+ version: "0.4"
89
+ type: :runtime
90
+ version_requirements: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: rspec
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 1
101
+ - 3
102
+ version: "1.3"
103
+ type: :development
104
+ version_requirements: *id006
80
105
  description: Tanuki is an MVVM-inspired web framework that fancies idiomatic Ruby, DRY and extensibility by its design.
81
106
  email: tanuki@withballs.org
82
107
  executables:
@@ -121,6 +146,8 @@ files:
121
146
  - lib/tanuki/context.rb
122
147
  - lib/tanuki/extensions/module.rb
123
148
  - lib/tanuki/extensions/object.rb
149
+ - lib/tanuki/extensions/rack/builder.rb
150
+ - lib/tanuki/extensions/rack/server.rb
124
151
  - lib/tanuki/extensions/rack/static_dir.rb
125
152
  - lib/tanuki/i18n.rb
126
153
  - lib/tanuki/launcher.rb
@@ -148,7 +175,7 @@ require_paths:
148
175
  required_ruby_version: !ruby/object:Gem::Requirement
149
176
  none: false
150
177
  requirements:
151
- - - ">="
178
+ - - ~>
152
179
  - !ruby/object:Gem::Version
153
180
  segments:
154
181
  - 1