innate 2009.04
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2981 -0
- data/COPYING +18 -0
- data/MANIFEST +127 -0
- data/README.md +563 -0
- data/Rakefile +35 -0
- data/example/app/retro_games.rb +60 -0
- data/example/app/todo/layout/default.xhtml +11 -0
- data/example/app/todo/spec/todo.rb +63 -0
- data/example/app/todo/start.rb +51 -0
- data/example/app/todo/view/index.xhtml +39 -0
- data/example/app/whywiki_erb/layout/wiki.html.erb +15 -0
- data/example/app/whywiki_erb/spec/wiki.rb +19 -0
- data/example/app/whywiki_erb/start.rb +42 -0
- data/example/app/whywiki_erb/view/edit.erb +6 -0
- data/example/app/whywiki_erb/view/index.erb +12 -0
- data/example/custom_middleware.rb +35 -0
- data/example/hello.rb +11 -0
- data/example/howto_spec.rb +35 -0
- data/example/link.rb +27 -0
- data/example/provides.rb +31 -0
- data/example/session.rb +38 -0
- data/innate.gemspec +29 -0
- data/lib/innate.rb +269 -0
- data/lib/innate/action.rb +150 -0
- data/lib/innate/adapter.rb +76 -0
- data/lib/innate/cache.rb +134 -0
- data/lib/innate/cache/api.rb +128 -0
- data/lib/innate/cache/drb.rb +58 -0
- data/lib/innate/cache/file_based.rb +41 -0
- data/lib/innate/cache/marshal.rb +17 -0
- data/lib/innate/cache/memory.rb +22 -0
- data/lib/innate/cache/yaml.rb +17 -0
- data/lib/innate/current.rb +37 -0
- data/lib/innate/dynamap.rb +96 -0
- data/lib/innate/helper.rb +183 -0
- data/lib/innate/helper/aspect.rb +124 -0
- data/lib/innate/helper/cgi.rb +54 -0
- data/lib/innate/helper/flash.rb +36 -0
- data/lib/innate/helper/link.rb +94 -0
- data/lib/innate/helper/redirect.rb +85 -0
- data/lib/innate/helper/render.rb +87 -0
- data/lib/innate/helper/send_file.rb +26 -0
- data/lib/innate/log.rb +20 -0
- data/lib/innate/log/color_formatter.rb +43 -0
- data/lib/innate/log/hub.rb +73 -0
- data/lib/innate/middleware_compiler.rb +65 -0
- data/lib/innate/mock.rb +49 -0
- data/lib/innate/node.rb +1025 -0
- data/lib/innate/options.rb +37 -0
- data/lib/innate/options/dsl.rb +202 -0
- data/lib/innate/options/stub.rb +7 -0
- data/lib/innate/request.rb +141 -0
- data/lib/innate/response.rb +23 -0
- data/lib/innate/route.rb +110 -0
- data/lib/innate/session.rb +121 -0
- data/lib/innate/session/flash.rb +94 -0
- data/lib/innate/spec.rb +23 -0
- data/lib/innate/state.rb +27 -0
- data/lib/innate/state/accessor.rb +130 -0
- data/lib/innate/state/fiber.rb +74 -0
- data/lib/innate/state/thread.rb +47 -0
- data/lib/innate/traited.rb +85 -0
- data/lib/innate/trinity.rb +18 -0
- data/lib/innate/version.rb +3 -0
- data/lib/innate/view.rb +60 -0
- data/lib/innate/view/erb.rb +15 -0
- data/lib/innate/view/etanni.rb +36 -0
- data/lib/innate/view/none.rb +9 -0
- data/spec/example/app/retro_games.rb +30 -0
- data/spec/example/hello.rb +13 -0
- data/spec/example/link.rb +25 -0
- data/spec/example/provides.rb +16 -0
- data/spec/example/session.rb +22 -0
- data/spec/helper.rb +10 -0
- data/spec/innate/action/layout.rb +107 -0
- data/spec/innate/action/layout/file_layout.xhtml +1 -0
- data/spec/innate/cache/common.rb +47 -0
- data/spec/innate/cache/marshal.rb +5 -0
- data/spec/innate/cache/memory.rb +5 -0
- data/spec/innate/cache/yaml.rb +5 -0
- data/spec/innate/dynamap.rb +22 -0
- data/spec/innate/helper.rb +86 -0
- data/spec/innate/helper/aspect.rb +75 -0
- data/spec/innate/helper/cgi.rb +37 -0
- data/spec/innate/helper/flash.rb +118 -0
- data/spec/innate/helper/link.rb +139 -0
- data/spec/innate/helper/redirect.rb +160 -0
- data/spec/innate/helper/render.rb +133 -0
- data/spec/innate/helper/send_file.rb +21 -0
- data/spec/innate/helper/view/aspect_hello.xhtml +1 -0
- data/spec/innate/helper/view/locals.xhtml +1 -0
- data/spec/innate/helper/view/loop.xhtml +4 -0
- data/spec/innate/helper/view/num.xhtml +1 -0
- data/spec/innate/helper/view/partial.xhtml +1 -0
- data/spec/innate/helper/view/recursive.xhtml +7 -0
- data/spec/innate/mock.rb +84 -0
- data/spec/innate/node/mapping.rb +37 -0
- data/spec/innate/node/node.rb +134 -0
- data/spec/innate/node/resolve.rb +82 -0
- data/spec/innate/node/view/another_layout/another_layout.xhtml +3 -0
- data/spec/innate/node/view/bar.xhtml +1 -0
- data/spec/innate/node/view/foo.html.xhtml +1 -0
- data/spec/innate/node/view/only_view.xhtml +1 -0
- data/spec/innate/node/view/with_layout.xhtml +1 -0
- data/spec/innate/node/wrap_action_call.rb +83 -0
- data/spec/innate/options.rb +115 -0
- data/spec/innate/parameter.rb +154 -0
- data/spec/innate/provides.rb +99 -0
- data/spec/innate/provides/list.html.xhtml +1 -0
- data/spec/innate/provides/list.txt.xhtml +1 -0
- data/spec/innate/request.rb +77 -0
- data/spec/innate/route.rb +135 -0
- data/spec/innate/session.rb +54 -0
- data/spec/innate/state/fiber.rb +58 -0
- data/spec/innate/state/thread.rb +40 -0
- data/spec/innate/traited.rb +55 -0
- data/tasks/bacon.rake +66 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/gem.rake +22 -0
- data/tasks/gem_installer.rake +76 -0
- data/tasks/grancher.rake +12 -0
- data/tasks/install_dependencies.rake +4 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/rcov.rake +19 -0
- data/tasks/release.rake +51 -0
- data/tasks/reversion.rake +8 -0
- data/tasks/setup.rake +28 -0
- metadata +181 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
module Innate
|
2
|
+
# this has to be run after a couple of other files have been required
|
3
|
+
|
4
|
+
options.dsl do
|
5
|
+
o "Innate::start will not start an adapter if true",
|
6
|
+
:started, false
|
7
|
+
|
8
|
+
o "Will send ::setup to each element during Innate::start",
|
9
|
+
:setup, [Innate::Cache, Innate::Node]
|
10
|
+
|
11
|
+
o "Trap this signal to issue shutdown, nil/false to disable trap",
|
12
|
+
:trap, 'SIGINT'
|
13
|
+
|
14
|
+
o "The compiler for middleware",
|
15
|
+
:middleware_compiler, Innate::MiddlewareCompiler
|
16
|
+
|
17
|
+
o "Indicates which default middleware to use, (:dev|:live)",
|
18
|
+
:mode, :dev
|
19
|
+
|
20
|
+
o "The directories this application resides in",
|
21
|
+
:roots, [File.dirname($0)]
|
22
|
+
|
23
|
+
o "The directories containing static files to be served",
|
24
|
+
:publics, ['public']
|
25
|
+
|
26
|
+
o "Directories containing the view templates",
|
27
|
+
:views, ['view']
|
28
|
+
|
29
|
+
o "Directories containing the layout templates",
|
30
|
+
:layouts, ['layout']
|
31
|
+
|
32
|
+
o "Prefix used to create relative links",
|
33
|
+
:prefix, '/'
|
34
|
+
|
35
|
+
trigger(:mode){|v| Innate.middleware_recompile(v) }
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module Innate
|
2
|
+
# Provides a minimal DSL to describe options with defaults and metadata.
|
3
|
+
#
|
4
|
+
# The example below should demonstrate the major features, note that key
|
5
|
+
# lookup wanders up the hierarchy until there is a match found or the parent
|
6
|
+
# of the Options class is itself, in which case nil will be returned.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# class Calculator
|
11
|
+
# @options = Options.new(:foo)
|
12
|
+
# def self.options; @options; end
|
13
|
+
#
|
14
|
+
# options.dsl do
|
15
|
+
# o "Which method to use", :method, :plus
|
16
|
+
# o "Default arguments", :args, [1, 2]
|
17
|
+
# sub(:minus){ o("Default arguments", :args, [4, 3]) }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def self.calculate(method = nil, *args)
|
21
|
+
# method ||= options[:method]
|
22
|
+
# args = args.empty? ? options[method, :args] : args
|
23
|
+
# self.send(method, *args)
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def self.plus(n1, n2)
|
27
|
+
# n1 + n2
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def self.minus(n1, n2)
|
31
|
+
# n1 - n2
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Calculator.calculate
|
36
|
+
# # => 3
|
37
|
+
# Calculator.options[:method] = :minus
|
38
|
+
# # => :minus
|
39
|
+
# Calculator.calculate
|
40
|
+
# # => 1
|
41
|
+
# Calculator.calculate(:plus, 4, 5)
|
42
|
+
# # => 9
|
43
|
+
#
|
44
|
+
class Options
|
45
|
+
def initialize(name, parent = self)
|
46
|
+
@name, @parent, = name, parent
|
47
|
+
@hash = {}
|
48
|
+
yield(self) if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Shortcut for instance_eval
|
52
|
+
def dsl(&block)
|
53
|
+
instance_eval(&block) if block
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create a new Options instance with +name+ and pass +block+ on to its #dsl.
|
58
|
+
# Assigns the new instance to the +name+ Symbol on current instance.
|
59
|
+
def sub(name, &block)
|
60
|
+
name = name.to_sym
|
61
|
+
|
62
|
+
case found = @hash[name]
|
63
|
+
when Options
|
64
|
+
found.dsl(&block)
|
65
|
+
else
|
66
|
+
found = @hash[name] = Options.new(name, self).dsl(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
found
|
70
|
+
end
|
71
|
+
|
72
|
+
# Store an option in the Options instance.
|
73
|
+
#
|
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 option(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)
|
83
|
+
end
|
84
|
+
alias o option
|
85
|
+
|
86
|
+
# To avoid lookup on the parent, we can set a default to the internal Hash.
|
87
|
+
# Parameters as in {Options#o}, but without the +key+.
|
88
|
+
def default(doc, value, other = {})
|
89
|
+
@hash.default = other.merge(:doc => doc, :value => value)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Add a block that will be called when a new value is set.
|
93
|
+
def trigger(key, &block)
|
94
|
+
@hash[key.to_sym][:trigger] = block
|
95
|
+
end
|
96
|
+
|
97
|
+
# Try to retrieve the corresponding Hash for the passed keys, will try to
|
98
|
+
# retrieve the key from a parent if no match is found on the current
|
99
|
+
# instance. If multiple keys are passed it will try to find a matching
|
100
|
+
# child and pass the request on to it.
|
101
|
+
def get(key, *keys)
|
102
|
+
if keys.empty?
|
103
|
+
if value = @hash[key.to_sym]
|
104
|
+
value
|
105
|
+
elsif @parent != self
|
106
|
+
@parent.get(key)
|
107
|
+
else
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
elsif sub_options = get(key)
|
111
|
+
sub_options.get(*keys)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param [Array] keys
|
116
|
+
# @param [Object] value
|
117
|
+
def set_value(keys, value)
|
118
|
+
get(*keys)[:value] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
# Retrieve only the :value from the value hash if found via +keys+.
|
122
|
+
def [](*keys)
|
123
|
+
if value = get(*keys)
|
124
|
+
value.is_a?(Hash) ? value[:value] : value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Assign new :value to the value hash on the current instance.
|
129
|
+
#
|
130
|
+
# TODO: allow arbitrary assignments
|
131
|
+
def []=(key, value)
|
132
|
+
if ns = @hash[key.to_sym]
|
133
|
+
ns[:value] = value
|
134
|
+
ns[:trigger].call(value) if ns[:trigger].respond_to?(:call)
|
135
|
+
elsif existing = get(key)
|
136
|
+
option(existing[:doc].to_s.dup, key, value)
|
137
|
+
else
|
138
|
+
raise(ArgumentError, "No key for %p exists" % [key])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def method_missing(meth, *args)
|
143
|
+
case meth.to_s
|
144
|
+
when /^(.*)=$/
|
145
|
+
self[$1] = args.first
|
146
|
+
else
|
147
|
+
self[meth]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def merge!(hash)
|
152
|
+
hash.each_pair do |key, value|
|
153
|
+
set_value(key.to_s.split('.'), value)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_hash
|
158
|
+
@hash
|
159
|
+
end
|
160
|
+
|
161
|
+
def each_option(&block)
|
162
|
+
@hash.each(&block)
|
163
|
+
end
|
164
|
+
|
165
|
+
def each_pair
|
166
|
+
@hash.each do |key, values|
|
167
|
+
yield(key, self[key])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def inspect
|
172
|
+
@hash.inspect
|
173
|
+
end
|
174
|
+
|
175
|
+
def pretty_print(q)
|
176
|
+
q.pp_hash @hash
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# extend your class with this
|
181
|
+
module Optioned
|
182
|
+
def self.included(into)
|
183
|
+
into.extend(SingletonMethods)
|
184
|
+
|
185
|
+
snaked = into.name.split('::').last
|
186
|
+
snaked = snaked.gsub(/\B[A-Z][^A-Z]/, '_\&').downcase.gsub(' ', '_')
|
187
|
+
|
188
|
+
options = Innate.options.sub(snaked)
|
189
|
+
into.instance_variable_set(:@options, options)
|
190
|
+
end
|
191
|
+
|
192
|
+
module SingletonMethods
|
193
|
+
attr_reader :options
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def options
|
199
|
+
self.class.options
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Innate
|
2
|
+
|
3
|
+
# Subclass Rack::Request and add some convenient methods.
|
4
|
+
#
|
5
|
+
# An instance is available via the #request method in your Node.
|
6
|
+
#
|
7
|
+
# NOTE:
|
8
|
+
# Please make sure to read the documentation of Rack::Request together with
|
9
|
+
# this, as there are a lot of features available.
|
10
|
+
#
|
11
|
+
# A list of methods from Rack::Request so you get a gist of it:
|
12
|
+
#
|
13
|
+
# ## Generally
|
14
|
+
#
|
15
|
+
# * body
|
16
|
+
# * cookies
|
17
|
+
# * env
|
18
|
+
# * fullpath
|
19
|
+
# * host
|
20
|
+
# * port
|
21
|
+
# * scheme
|
22
|
+
# * url
|
23
|
+
#
|
24
|
+
# ## ENV shortcuts
|
25
|
+
#
|
26
|
+
# * accept_encoding
|
27
|
+
# * content_charset
|
28
|
+
# * content_length
|
29
|
+
# * content_type
|
30
|
+
# * ip
|
31
|
+
# * media_type
|
32
|
+
# * media_type_params
|
33
|
+
# * path_info
|
34
|
+
# * path_info=
|
35
|
+
# * query_string
|
36
|
+
# * referrer
|
37
|
+
# * script_name
|
38
|
+
# * script_name=
|
39
|
+
# * xhr?
|
40
|
+
#
|
41
|
+
# ## Query request method
|
42
|
+
#
|
43
|
+
# * delete?
|
44
|
+
# * get?
|
45
|
+
# * head?
|
46
|
+
# * post?
|
47
|
+
# * put?
|
48
|
+
# * request_method
|
49
|
+
#
|
50
|
+
# ## parameter handling
|
51
|
+
#
|
52
|
+
# * []
|
53
|
+
# * []=
|
54
|
+
# * form_data?
|
55
|
+
# * params
|
56
|
+
# * values_at
|
57
|
+
|
58
|
+
class Request < Rack::Request
|
59
|
+
# Currently handled request from Innate::STATE[:request]
|
60
|
+
# Call it from anywhere via Innate::Request.current
|
61
|
+
def self.current
|
62
|
+
Current.request
|
63
|
+
end
|
64
|
+
|
65
|
+
# Let's allow #[] to act like #values_at.
|
66
|
+
#
|
67
|
+
# Usage given a GET request like /hey?foo=duh&bar=doh
|
68
|
+
#
|
69
|
+
# request[:foo, :bar] # => ['duh', 'doh']
|
70
|
+
#
|
71
|
+
# Both +value+ and the elements of +keys+ will be turned into String by #to_s.
|
72
|
+
def [](value, *keys)
|
73
|
+
return super(value) if keys.empty?
|
74
|
+
[value, *keys].map{|key| super(key) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# the full request URI provided by Rack::Request
|
78
|
+
# e.g. "http://localhost:7000/controller/action?foo=bar.xhtml"
|
79
|
+
def request_uri
|
80
|
+
env['REQUEST_URI'] || env['PATH_INFO']
|
81
|
+
end
|
82
|
+
|
83
|
+
# Answers with a subset of request.params with only the key/value pairs for
|
84
|
+
# which you pass the keys.
|
85
|
+
# Valid keys are objects that respond to :to_s
|
86
|
+
#
|
87
|
+
# @example usage
|
88
|
+
#
|
89
|
+
# request.params
|
90
|
+
# # => {'name' => 'jason', 'age' => '45', 'job' => 'lumberjack'}
|
91
|
+
# request.subset('name')
|
92
|
+
# # => {'name' => 'jason'}
|
93
|
+
# request.subset(:name, :job)
|
94
|
+
# # => {'name' => 'jason', 'job' => 'lumberjack'}
|
95
|
+
|
96
|
+
def subset(*keys)
|
97
|
+
keys = keys.map{|key| key.to_s }
|
98
|
+
params.reject{|key, value| not keys.include?(key) }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Try to figure out the domain we are running on, this might work for some
|
102
|
+
# deployments but fail for others, given the combination of servers in
|
103
|
+
# front.
|
104
|
+
#
|
105
|
+
# @example usage
|
106
|
+
#
|
107
|
+
# domain
|
108
|
+
# # => #<URI::HTTPS:0xb769ecb0 URL:https://localhost:7000/>
|
109
|
+
# domain('/foo')
|
110
|
+
# # => #<URI::HTTPS:0xb769ecb0 URL:https://localhost:7000/foo>
|
111
|
+
#
|
112
|
+
# @param [#to_s] path
|
113
|
+
#
|
114
|
+
# @return [URI]
|
115
|
+
#
|
116
|
+
# @api external
|
117
|
+
# @author manveru
|
118
|
+
def domain(path = nil, options = {})
|
119
|
+
uri = URI(self.url)
|
120
|
+
uri.path = path.to_s if path
|
121
|
+
uri.query = nil unless options[:keep_query]
|
122
|
+
uri
|
123
|
+
end
|
124
|
+
|
125
|
+
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 ]
|
126
|
+
ipv6 = %w[ fc00::/7 fe80::/10 fec0::/10 ::1 ]
|
127
|
+
LOCAL = (ipv4 + ipv6).map{|a| IPAddr.new(a)} unless defined?(LOCAL)
|
128
|
+
|
129
|
+
# Request is from a local network?
|
130
|
+
# Checks both IPv4 and IPv6
|
131
|
+
# Answer is true if the IP address making the request is from local network.
|
132
|
+
# Optional argument address can be used to check any IP address.
|
133
|
+
|
134
|
+
def local_net?(address = ip)
|
135
|
+
addr = IPAddr.new(address)
|
136
|
+
LOCAL.find{|range| range.include?(addr) }
|
137
|
+
rescue ArgumentError => ex
|
138
|
+
raise ArgumentError, ex unless ex.message == 'invalid address'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Innate
|
2
|
+
class Response < Rack::Response
|
3
|
+
include Optioned
|
4
|
+
|
5
|
+
options.dsl do
|
6
|
+
o "Default headers, will not override headers already set",
|
7
|
+
:headers, {'Content-Type' => 'text/html'}
|
8
|
+
end
|
9
|
+
|
10
|
+
def reset
|
11
|
+
self.status = 200
|
12
|
+
self.header.delete('Content-Type')
|
13
|
+
body.clear
|
14
|
+
self.length = 0
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def finish
|
19
|
+
options.headers.each{|key, value| self[key] ||= value }
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/innate/route.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Innate
|
2
|
+
# Innate support simple routing using string, regex and lambda based routers.
|
3
|
+
# Route are stored in a dictionary, which supports hash-like access but
|
4
|
+
# preserves order, so routes are evaluated in the order they are added.
|
5
|
+
#
|
6
|
+
# This middleware should wrap Innate::DynaMap.
|
7
|
+
#
|
8
|
+
# String routers are the simplest way to route in Innate. One path is
|
9
|
+
# translated into another:
|
10
|
+
#
|
11
|
+
# Innate::Route[ '/foo' ] = '/bar'
|
12
|
+
# '/foo' => '/bar'
|
13
|
+
#
|
14
|
+
# Regex routers allow matching against paths using regex. Matches within
|
15
|
+
# your regex using () are substituted in the new path using printf-like
|
16
|
+
# syntax.
|
17
|
+
#
|
18
|
+
# Innate::Route[ %r!^/(\d+)\.te?xt$! ] = "/text/%d"
|
19
|
+
# '/123.txt' => '/text/123'
|
20
|
+
# '/789.text' => '/text/789'
|
21
|
+
#
|
22
|
+
# For more complex routing, lambda routers can be used. Lambda routers are
|
23
|
+
# passed in the current path and request object, and must return either a new
|
24
|
+
# path string, or nil.
|
25
|
+
#
|
26
|
+
# Innate::Route[ 'name of route' ] = lambda{ |path, request|
|
27
|
+
# '/bar' if path == '/foo' and request[:bar] == '1'
|
28
|
+
# }
|
29
|
+
# '/foo' => '/foo'
|
30
|
+
# '/foo?bar=1' => '/bar'
|
31
|
+
#
|
32
|
+
# Lambda routers can also use this alternative syntax:
|
33
|
+
#
|
34
|
+
# Innate::Route('name of route') do |path, request|
|
35
|
+
# '/bar' if path == '/foo' and request[:bar] == '1'
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# NOTE: Use self::ROUTES notation in singleton methods to force correct
|
39
|
+
# lookup.
|
40
|
+
|
41
|
+
class Route
|
42
|
+
ROUTES = []
|
43
|
+
|
44
|
+
def self.[](key)
|
45
|
+
found = self::ROUTES.assoc(key)
|
46
|
+
found[1] if found
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.[]=(key, value)
|
50
|
+
self::ROUTES.delete_if{|k,v| k == key }
|
51
|
+
self::ROUTES << [key, value]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.clear
|
55
|
+
self::ROUTES.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(app = Innate::DynaMap)
|
59
|
+
@app = app
|
60
|
+
end
|
61
|
+
|
62
|
+
def call(env)
|
63
|
+
path = env['PATH_INFO']
|
64
|
+
path << '/' if path.empty?
|
65
|
+
|
66
|
+
if modified = resolve(path)
|
67
|
+
Log.debug("%s routes %p to %p" % [self.class.name, path, modified])
|
68
|
+
env['PATH_INFO'] = modified
|
69
|
+
end
|
70
|
+
|
71
|
+
@app.call(env)
|
72
|
+
end
|
73
|
+
|
74
|
+
def resolve(path)
|
75
|
+
self.class::ROUTES.each do |key, value|
|
76
|
+
if key.is_a?(Regexp)
|
77
|
+
md = path.match(key)
|
78
|
+
return value % md.to_a[1..-1] if md
|
79
|
+
|
80
|
+
elsif value.respond_to?(:call)
|
81
|
+
new_path = value.call(path, Current.request)
|
82
|
+
return new_path if new_path
|
83
|
+
|
84
|
+
elsif value.respond_to?(:to_str)
|
85
|
+
return value.to_str if path == key
|
86
|
+
|
87
|
+
else
|
88
|
+
Log.error("Invalid route %p => %p" % [key, value])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Identical with Innate::Route, but is called before any Node::call is made
|
97
|
+
class Rewrite < Route
|
98
|
+
ROUTES = []
|
99
|
+
end
|
100
|
+
|
101
|
+
module SingletonMethods
|
102
|
+
def Route(key, value = nil, &block)
|
103
|
+
Route[key] = value || block
|
104
|
+
end
|
105
|
+
|
106
|
+
def Rewrite(key, value = nil, &block)
|
107
|
+
Rewrite[key] = value || block
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|