manveru-innate 2009.02.06 → 2009.02.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,10 +3,10 @@ require 'innate/options/dsl'
3
3
  module Innate
4
4
  @options = Options.new(:innate)
5
5
 
6
- def self.options; @options end
6
+ # attr_accessor is faster
7
+ class << self; attr_accessor :options; end
7
8
 
8
9
  # This has to speak for itself.
9
-
10
10
  options.dsl do
11
11
  o "IP address or hostname that Ramaze will respond to - 0.0.0.0 for all",
12
12
  :host, "0.0.0.0", :short => :H
@@ -32,9 +32,15 @@ module Innate
32
32
  o "Keep state in Thread or Fiber, fall back to Thread if Fiber not available",
33
33
  :state, :Fiber
34
34
 
35
+ o "Indicates which default middleware to use, (:dev|:live)",
36
+ :mode, :dev
37
+
38
+ trigger(:mode){|v| Innate.setup_middleware(true) }
39
+
35
40
  sub :log do
36
41
  o "Array of parameters for Logger.new, default to stderr for CGI",
37
42
  :params, [$stderr]
43
+
38
44
  o "Use ANSI colors for logging, nil does auto-detection by checking for #tty?",
39
45
  :color, nil
40
46
  end
@@ -47,6 +53,7 @@ module Innate
47
53
  sub :env do
48
54
  o "Hostname of this machine",
49
55
  :host, ENV['HOSTNAME'] # FIXME: cross-platform
56
+
50
57
  o "Username executing the application",
51
58
  :user, ENV['USER'] # FIXME: cross-platform
52
59
  end
@@ -54,23 +61,38 @@ module Innate
54
61
  sub :app do
55
62
  o "Unique identifier for this application",
56
63
  :name, 'pristine'
64
+
57
65
  o "Root directory containing the application",
58
66
  :root, File.dirname($0)
67
+
59
68
  o "Root directory for view templates, relative to app subdir",
60
69
  :view, '/view'
70
+
61
71
  o "Root directory for layout templates, relative to app subdir",
62
72
  :layout, '/layout'
73
+
74
+ o "Prefix of this application",
75
+ :prefix, '/'
76
+
77
+ o "Root directory for static public files",
78
+ :public, '/public'
79
+
80
+ trigger(:public){|v| Innate.middleware_recompile }
63
81
  end
64
82
 
65
83
  sub :session do
66
84
  o "Key for the session cookie",
67
85
  :key, 'innate.sid'
86
+
68
87
  o "Domain the cookie relates to, unspecified if false",
69
88
  :domain, false
89
+
70
90
  o "Path the cookie relates to",
71
91
  :path, '/'
92
+
72
93
  o "Use secure cookie",
73
94
  :secure, false
95
+
74
96
  o "Time of cookie expiration",
75
97
  :expires, Time.at(2147483647)
76
98
  end
@@ -86,6 +108,9 @@ module Innate
86
108
  sub :action do
87
109
  o "wish => Action#method",
88
110
  :wish, {'json' => :as_json, 'html' => :as_html, 'yaml' => :as_yaml}
111
+
112
+ o "Create actions that have no method associated",
113
+ :needs_method, false
89
114
  end
90
115
  end
91
116
  end
@@ -71,21 +71,28 @@ module Innate
71
71
 
72
72
  # Store an option in the Options instance.
73
73
  #
74
- # +doc+ should be a String describing the purpose of this option
75
- # +key+ should be a Symbol used to access
76
- # +value+ may be any object
77
- # +other+ optional Hash that may contain meta-data and should not have :doc
78
- # or :value keys
79
- def o(doc, key, value, other = {})
80
- @hash[key.to_sym] = other.merge(:doc => doc, :value => value)
74
+ # @param [#to_s] doc describing the purpose of this option
75
+ # @param [#to_sym] key used to access
76
+ # @param [Object] value may be anything
77
+ # @param [Hash] other optional Hash containing meta-data
78
+ # :doc, :value keys will be ignored
79
+ def o(doc, key, value, other = {}, &block)
80
+ trigger = block || other[:trigger]
81
+ convert = {:doc => doc.to_s, :value => value, :trigger => trigger}
82
+ @hash[key.to_sym] = other.merge(convert)
81
83
  end
82
84
 
83
85
  # To avoid lookup on the parent, we can set a default to the internal Hash.
84
- # Parameters as in #o, but without the +key+.
86
+ # Parameters as in {Options#o}, but without the +key+.
85
87
  def default(doc, value, other = {})
86
88
  @hash.default = other.merge(:doc => doc, :value => value)
87
89
  end
88
90
 
91
+ # Add a block that will be called when a new value is set.
92
+ def trigger(key, &block)
93
+ @hash[key.to_sym][:trigger] = block
94
+ end
95
+
89
96
  # Try to retrieve the corresponding Hash for the passed keys, will try to
90
97
  # retrieve the key from a parent if no match is found on the current
91
98
  # instance. If multiple keys are passed it will try to find a matching
@@ -114,10 +121,10 @@ module Innate
114
121
  # Assign new :value to the value hash on the current instance.
115
122
  #
116
123
  # TODO: allow arbitrary assignments
117
-
118
124
  def []=(key, value)
119
125
  if ns = @hash[key.to_sym]
120
126
  ns[:value] = value
127
+ ns[:trigger].call(value) if ns[:trigger].respond_to?(:call)
121
128
  else
122
129
  raise(ArgumentError, "No key for %p exists" % [key])
123
130
  end
@@ -84,7 +84,7 @@ module Innate
84
84
  # which you pass the keys.
85
85
  # Valid keys are objects that respond to :to_s
86
86
  #
87
- # Example:
87
+ # @usage
88
88
  # request.params
89
89
  # # => {'name' => 'jason', 'age' => '45', 'job' => 'lumberjack'}
90
90
  # request.subset('name')
@@ -108,15 +108,50 @@ module Innate
108
108
  URI("#{scheme}://#{host}#{path}")
109
109
  end
110
110
 
111
- # Try to find out which languages the client would like to have.
111
+ # Try to find out which languages the client would like to have and sort
112
+ # them by weight, (most wanted first).
113
+ #
112
114
  # Returns and array of locales from env['HTTP_ACCEPT_LANGUAGE].
113
115
  # e.g. ["fi", "en", "ja", "fr", "de", "es", "it", "nl", "sv"]
114
-
115
- def accept_language
116
- env['HTTP_ACCEPT_LANGUAGE'].to_s.split(/(?:,|;q=[\d.,]+)/)
116
+ #
117
+ # Usage:
118
+ #
119
+ # request.accept_language
120
+ # # => ['en-us', 'en', 'de-at', 'de']
121
+ #
122
+ # @param [String #to_s] string the value of HTTP_ACCEPT_LANGUAGE
123
+ # @return [Array] list of locales
124
+ # @see Request#accept_language_with_weight
125
+ # @author manveru
126
+ def accept_language(string = env['HTTP_ACCEPT_LANGUAGE'])
127
+ return [] unless string
128
+
129
+ accept_language_with_weight(string).map{|lang, weight| lang }
117
130
  end
118
131
  alias locales accept_language
119
132
 
133
+ # Transform the HTTP_ACCEPT_LANGUAGE header into an Array with:
134
+ #
135
+ # [[lang, weight], [lang, weight], ...]
136
+ #
137
+ # This algorithm was taken and improved from the locales library.
138
+ #
139
+ # Usage:
140
+ #
141
+ # request.accept_language_with_weight
142
+ # # => [["en-us", 1.0], ["en", 0.8], ["de-at", 0.5], ["de", 0.3]]
143
+ #
144
+ # @param [String #to_s] string the value of HTTP_ACCEPT_LANGUAGE
145
+ # @return [Array] array of [lang, weight] arrays
146
+ # @see Request#accept_language
147
+ # @author manveru
148
+ def accept_language_with_weight(string = env['HTTP_ACCEPT_LANGUAGE'])
149
+ string.to_s.gsub(/\s+/, '').split(',').
150
+ map{|chunk| chunk.split(';q=', 2) }.
151
+ map{|lang, weight| [lang, weight ? weight.to_f : 1.0] }.
152
+ sort_by{|lang, weight| -weight }
153
+ end
154
+
120
155
  ipv4 = %w[ 127.0.0.1/32 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 169.254.0.0/16 ]
121
156
  ipv6 = %w[ fc00::/7 fe80::/10 fec0::/10 ::1 ]
122
157
  LOCAL = (ipv4 + ipv6).map{|a| IPAddr.new(a)} unless defined?(LOCAL)
data/lib/innate/route.rb CHANGED
@@ -99,11 +99,13 @@ module Innate
99
99
  ROUTES = []
100
100
  end
101
101
 
102
- def self.Route(key, value = nil, &block)
103
- Route[key] = value || block
104
- end
102
+ module SingletonMethods
103
+ def Route(key, value = nil, &block)
104
+ Route[key] = value || block
105
+ end
105
106
 
106
- def self.Rewrite(key, value = nil, &block)
107
- Rewrite[key] = value || block
107
+ def Rewrite(key, value = nil, &block)
108
+ Rewrite[key] = value || block
109
+ end
108
110
  end
109
111
  end
@@ -48,7 +48,8 @@ module Innate
48
48
  return if @cache_sid.empty?
49
49
 
50
50
  flash.rotate!
51
- cache[sid] = cache_sid
51
+ ttl = (Time.at(cookie_value[:expires]) - Time.now).to_i
52
+ cache.store(sid, cache_sid, :ttl => ttl)
52
53
  set_cookie(response)
53
54
  end
54
55
 
data/lib/innate/spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bacon'
2
2
 
3
3
  Bacon.summary_on_exit
4
- Bacon.extend(Bacon::TestUnitOutput)
4
+ # Bacon.extend(Bacon::TestUnitOutput)
5
5
 
6
6
  innate = File.expand_path(File.join(File.dirname(__FILE__), '../innate'))
7
7
  require(innate) unless defined?(Innate)
@@ -17,10 +17,9 @@ module Innate
17
17
  class Fiber < ::Fiber
18
18
  attr_accessor :state
19
19
 
20
- def self.new(*args)
21
- instance = super
22
- instance.state = {}
23
- instance
20
+ def initialize(*args)
21
+ super
22
+ @state = {}
24
23
  end
25
24
 
26
25
  def [](key)
@@ -1,13 +1,18 @@
1
1
  module Innate
2
2
  module Traited
3
- TRAITS = Hash.new{|h,k| h[k] = {}}
3
+ TRAITS = {}
4
4
 
5
5
  def self.included(into)
6
6
  into.extend(self)
7
7
  end
8
8
 
9
9
  def trait(hash = nil)
10
- hash ? TRAITS[self].update(hash) : TRAITS[self]
10
+ if hash
11
+ TRAITS[self] ||= {}
12
+ TRAITS[self].merge!(hash)
13
+ else
14
+ TRAITS[self] || {}
15
+ end
11
16
  end
12
17
 
13
18
  def ancestral_trait
@@ -1,3 +1,3 @@
1
1
  module Innate
2
- VERSION = "2009.02.06"
2
+ VERSION = "2009.02.21"
3
3
  end
data/lib/innate/view.rb CHANGED
@@ -12,7 +12,7 @@ module Innate
12
12
 
13
13
  def get(engine_or_ext)
14
14
  return unless engine_or_ext
15
- eoe = engine_or_ext.to_s
15
+ eoe = Array[*engine_or_ext].first.to_s
16
16
 
17
17
  if klass = TEMP[eoe]
18
18
  return klass
@@ -34,14 +34,21 @@ module Rack
34
34
 
35
35
  # Default application for Innate
36
36
  def innate
37
+ public_root = ::File.join(Innate.options.app.root.to_s,
38
+ Innate.options.app.public.to_s)
37
39
  cascade(
38
- Rack::File.new('public'),
40
+ Rack::File.new(public_root),
39
41
  Innate::Current.new(Innate::Route.new, Innate::Rewrite.new))
40
42
  end
41
43
 
42
44
  def static(path)
43
45
  require 'rack/contrib'
44
- Rack::ConditionalGet.new(Rack::ETag.new(Rack::File.new(path)))
46
+ Rack::ETag.new(Rack::ConditionalGet.new(Rack::File.new(path)))
47
+ end
48
+
49
+ def directory(path)
50
+ require 'rack/contrib'
51
+ Rack::ETag.new(Rack::ConditionalGet.new(Rack::Directory.new(path)))
45
52
  end
46
53
 
47
54
  def call(env)
@@ -54,7 +61,10 @@ module Rack
54
61
  end
55
62
 
56
63
  def compile
57
- return self if compiled?
64
+ compiled? ? self : compile!
65
+ end
66
+
67
+ def compile!
58
68
  @compiled = @middlewares.inject(@app){|a,e| e.new(a) }
59
69
  self
60
70
  end
@@ -0,0 +1,104 @@
1
+ # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
2
+ # All files in this distribution are subject to the terms of the Ruby license.
3
+
4
+ require 'spec/helper'
5
+
6
+ Innate.options.app.root = File.dirname(__FILE__)
7
+
8
+ class SpecActionLayoutMethod
9
+ Innate.node('/from_method', self)
10
+ layout('method_layout')
11
+
12
+ def method_layout
13
+ "<pre><%= @content %></pre>"
14
+ end
15
+
16
+ def index
17
+ 'Method Layout'
18
+ end
19
+
20
+ def foo
21
+ "bar"
22
+ end
23
+ end
24
+
25
+ class SpecActionLayoutFile
26
+ Innate.node('/from_file', self)
27
+ layout('file_layout')
28
+
29
+ def index
30
+ "File Layout"
31
+ end
32
+ end
33
+
34
+ class SpecActionLayoutSpecific
35
+ Innate.node('/specific', self)
36
+ layout('file_layout'){|name, wish| name == 'index' }
37
+
38
+ def index
39
+ 'Specific Layout'
40
+ end
41
+
42
+ def without
43
+ "Without wrapper"
44
+ end
45
+ end
46
+
47
+ class SpecActionLayoutDeny
48
+ Innate.node('/deny', self)
49
+ layout('file_layout'){|name, wish| name != 'without' }
50
+
51
+ def index
52
+ "Deny Layout"
53
+ end
54
+
55
+ def without
56
+ "Without wrapper"
57
+ end
58
+ end
59
+
60
+ class SpecActionLayoutMulti
61
+ Innate.node('/multi', self)
62
+ layout('file_layout'){|name, wish| name =~ /index|second/ }
63
+
64
+ def index
65
+ "Multi Layout Index"
66
+ end
67
+
68
+ def second
69
+ "Multi Layout Second"
70
+ end
71
+
72
+ def without
73
+ "Without wrapper"
74
+ end
75
+ end
76
+
77
+ describe 'Innate::Action#layout' do
78
+ behaves_like :mock
79
+
80
+ it 'uses a layout method' do
81
+ get('/from_method').body.should == '<pre>Method Layout</pre>'
82
+ get('/from_method/foo').body.should == '<pre>bar</pre>'
83
+ end
84
+
85
+ it 'uses a layout file' do
86
+ get('/from_file').body.strip.should == '<p>File Layout</p>'
87
+ end
88
+
89
+ it 'denies layout to some actions' do
90
+ get('/deny').body.strip.should == '<p>Deny Layout</p>'
91
+ get('/deny/without').body.strip.should == 'Without wrapper'
92
+ end
93
+
94
+ it 'uses layout only for specific action' do
95
+ get('/specific').body.strip.should == '<p>Specific Layout</p>'
96
+ get('/specific/without').body.strip.should == 'Without wrapper'
97
+ end
98
+
99
+ it 'uses layout only for specific actions' do
100
+ get('/multi').body.strip.should == '<p>Multi Layout Index</p>'
101
+ get('/multi/second').body.strip.should == '<p>Multi Layout Second</p>'
102
+ get('/multi/without').body.strip.should == 'Without wrapper'
103
+ end
104
+ end
@@ -0,0 +1 @@
1
+ <p><%= @content %></p>
@@ -19,14 +19,16 @@ describe $common_cache_class do
19
19
  end
20
20
 
21
21
  should 'delete' do
22
- cache.delete(:hello).should == @hello
22
+ cache.delete(:hello)
23
23
  cache.fetch(:hello).should == nil
24
24
  end
25
25
 
26
26
  should 'delete two key/value pairs at once' do
27
27
  cache.store(:hello, @hello).should == @hello
28
28
  cache.store(:innate, 'innate').should == 'innate'
29
- cache.delete(:hello, :innate).should == [@hello, 'innate']
29
+ cache.delete(:hello, :innate)
30
+ cache.fetch(:hello).should == nil
31
+ cache.fetch(:innate).should == nil
30
32
  end
31
33
 
32
34
  should 'store with ttl' do