tanuki 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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