rjspotter-innate 2009.06.29
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.
- data/AUTHORS +10 -0
- data/CHANGELOG +3261 -0
- data/COPYING +18 -0
- data/MANIFEST +127 -0
- data/README.md +563 -0
- data/Rakefile +39 -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 +41 -0
- data/lib/innate.rb +269 -0
- data/lib/innate/action.rb +137 -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 +44 -0
- data/lib/innate/cache/marshal.rb +20 -0
- data/lib/innate/cache/memory.rb +21 -0
- data/lib/innate/cache/yaml.rb +20 -0
- data/lib/innate/current.rb +35 -0
- data/lib/innate/dynamap.rb +96 -0
- data/lib/innate/helper.rb +185 -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 +152 -0
- data/lib/innate/helper/send_file.rb +26 -0
- data/lib/innate/log.rb +20 -0
- data/lib/innate/log/color_formatter.rb +49 -0
- data/lib/innate/log/hub.rb +77 -0
- data/lib/innate/middleware_compiler.rb +65 -0
- data/lib/innate/mock.rb +49 -0
- data/lib/innate/node.rb +1029 -0
- data/lib/innate/options.rb +37 -0
- data/lib/innate/options/dsl.rb +205 -0
- data/lib/innate/options/stub.rb +7 -0
- data/lib/innate/request.rb +141 -0
- data/lib/innate/response.rb +24 -0
- data/lib/innate/route.rb +114 -0
- data/lib/innate/session.rb +133 -0
- data/lib/innate/session/flash.rb +94 -0
- data/lib/innate/spec.rb +1 -0
- data/lib/innate/spec/bacon.rb +28 -0
- data/lib/innate/state.rb +26 -0
- data/lib/innate/state/accessor.rb +130 -0
- data/lib/innate/traited.rb +90 -0
- data/lib/innate/trinity.rb +18 -0
- data/lib/innate/version.rb +3 -0
- data/lib/innate/view.rb +97 -0
- data/lib/innate/view/erb.rb +14 -0
- data/lib/innate/view/etanni.rb +33 -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 +121 -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 +115 -0
- data/spec/innate/helper/link.rb +139 -0
- data/spec/innate/helper/redirect.rb +171 -0
- data/spec/innate/helper/render.rb +165 -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/modes.rb +61 -0
- data/spec/innate/node/mapping.rb +37 -0
- data/spec/innate/node/node.rb +135 -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 +123 -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 +79 -0
- data/spec/innate/route.rb +135 -0
- data/spec/innate/session.rb +58 -0
- data/spec/innate/traited.rb +55 -0
- data/tasks/authors.rake +30 -0
- data/tasks/bacon.rake +66 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/gem.rake +22 -0
- data/tasks/gem_setup.rake +99 -0
- data/tasks/grancher.rake +12 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/rcov.rake +19 -0
- data/tasks/release.rake +53 -0
- data/tasks/reversion.rake +8 -0
- data/tasks/setup.rake +6 -0
- data/tasks/ycov.rake +84 -0
- metadata +218 -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,205 @@
|
|
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}
|
82
|
+
convert[:trigger] = trigger if trigger
|
83
|
+
@hash[key.to_sym] = other.merge(convert)
|
84
|
+
end
|
85
|
+
alias o option
|
86
|
+
|
87
|
+
# To avoid lookup on the parent, we can set a default to the internal Hash.
|
88
|
+
# Parameters as in {Options#o}, but without the +key+.
|
89
|
+
def default(doc, value, other = {})
|
90
|
+
@hash.default = other.merge(:doc => doc, :value => value)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add a block that will be called when a new value is set.
|
94
|
+
def trigger(key, &block)
|
95
|
+
@hash[key.to_sym][:trigger] = block
|
96
|
+
end
|
97
|
+
|
98
|
+
# Try to retrieve the corresponding Hash for the passed keys, will try to
|
99
|
+
# retrieve the key from a parent if no match is found on the current
|
100
|
+
# instance. If multiple keys are passed it will try to find a matching
|
101
|
+
# child and pass the request on to it.
|
102
|
+
def get(key, *keys)
|
103
|
+
if keys.empty?
|
104
|
+
if value = @hash[key.to_sym]
|
105
|
+
value
|
106
|
+
elsif @parent != self
|
107
|
+
@parent.get(key)
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
elsif sub_options = get(key)
|
112
|
+
sub_options.get(*keys)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param [Array] keys
|
117
|
+
# @param [Object] value
|
118
|
+
def set_value(keys, value)
|
119
|
+
got = get(*keys)
|
120
|
+
return got[:value] = value if got
|
121
|
+
raise(IndexError, "There is no option available for %p" % [keys])
|
122
|
+
end
|
123
|
+
|
124
|
+
# Retrieve only the :value from the value hash if found via +keys+.
|
125
|
+
def [](*keys)
|
126
|
+
if value = get(*keys)
|
127
|
+
value.is_a?(Hash) ? value[:value] : value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Assign new :value to the value hash on the current instance.
|
132
|
+
#
|
133
|
+
# TODO: allow arbitrary assignments
|
134
|
+
def []=(key, value)
|
135
|
+
if ns = @hash[key.to_sym]
|
136
|
+
ns[:value] = value
|
137
|
+
ns[:trigger].call(value) if ns[:trigger].respond_to?(:call)
|
138
|
+
elsif existing = get(key)
|
139
|
+
option(existing[:doc].to_s.dup, key, value)
|
140
|
+
else
|
141
|
+
raise(ArgumentError, "No key for %p exists" % [key])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def method_missing(meth, *args)
|
146
|
+
case meth.to_s
|
147
|
+
when /^(.*)=$/
|
148
|
+
self[$1] = args.first
|
149
|
+
else
|
150
|
+
self[meth]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def merge!(hash)
|
155
|
+
hash.each_pair do |key, value|
|
156
|
+
set_value(key.to_s.split('.'), value)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_hash
|
161
|
+
@hash
|
162
|
+
end
|
163
|
+
|
164
|
+
def each_option(&block)
|
165
|
+
@hash.each(&block)
|
166
|
+
end
|
167
|
+
|
168
|
+
def each_pair
|
169
|
+
@hash.each do |key, values|
|
170
|
+
yield(key, self[key])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def inspect
|
175
|
+
@hash.inspect
|
176
|
+
end
|
177
|
+
|
178
|
+
def pretty_print(q)
|
179
|
+
q.pp_hash @hash
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# extend your class with this
|
184
|
+
module Optioned
|
185
|
+
def self.included(into)
|
186
|
+
into.extend(SingletonMethods)
|
187
|
+
|
188
|
+
snaked = into.name.split('::').last
|
189
|
+
snaked = snaked.gsub(/\B[A-Z][^A-Z]/, '_\&').downcase.gsub(' ', '_')
|
190
|
+
|
191
|
+
options = Innate.options.sub(snaked)
|
192
|
+
into.instance_variable_set(:@options, options)
|
193
|
+
end
|
194
|
+
|
195
|
+
module SingletonMethods
|
196
|
+
attr_reader :options
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def options
|
202
|
+
self.class.options
|
203
|
+
end
|
204
|
+
end
|
205
|
+
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 Thread.current[: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,24 @@
|
|
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
|
+
Current.session.flush(self)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/innate/route.rb
ADDED
@@ -0,0 +1,114 @@
|
|
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
|
+
# Please note that Rack::File is put before Route and Rewrite, that means
|
9
|
+
# that you cannot apply routes to static files unless you add your own route
|
10
|
+
# middleware before.
|
11
|
+
#
|
12
|
+
# String routers are the simplest way to route in Innate. One path is
|
13
|
+
# translated into another:
|
14
|
+
#
|
15
|
+
# Innate::Route[ '/foo' ] = '/bar'
|
16
|
+
# '/foo' => '/bar'
|
17
|
+
#
|
18
|
+
# Regex routers allow matching against paths using regex. Matches within
|
19
|
+
# your regex using () are substituted in the new path using printf-like
|
20
|
+
# syntax.
|
21
|
+
#
|
22
|
+
# Innate::Route[ %r!^/(\d+)\.te?xt$! ] = "/text/%d"
|
23
|
+
# '/123.txt' => '/text/123'
|
24
|
+
# '/789.text' => '/text/789'
|
25
|
+
#
|
26
|
+
# For more complex routing, lambda routers can be used. Lambda routers are
|
27
|
+
# passed in the current path and request object, and must return either a new
|
28
|
+
# path string, or nil.
|
29
|
+
#
|
30
|
+
# Innate::Route[ 'name of route' ] = lambda{ |path, request|
|
31
|
+
# '/bar' if path == '/foo' and request[:bar] == '1'
|
32
|
+
# }
|
33
|
+
# '/foo' => '/foo'
|
34
|
+
# '/foo?bar=1' => '/bar'
|
35
|
+
#
|
36
|
+
# Lambda routers can also use this alternative syntax:
|
37
|
+
#
|
38
|
+
# Innate::Route('name of route') do |path, request|
|
39
|
+
# '/bar' if path == '/foo' and request[:bar] == '1'
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# NOTE: Use self::ROUTES notation in singleton methods to force correct
|
43
|
+
# lookup.
|
44
|
+
|
45
|
+
class Route
|
46
|
+
ROUTES = []
|
47
|
+
|
48
|
+
def self.[](key)
|
49
|
+
found = self::ROUTES.assoc(key)
|
50
|
+
found[1] if found
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.[]=(key, value)
|
54
|
+
self::ROUTES.delete_if{|k,v| k == key }
|
55
|
+
self::ROUTES << [key, value]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.clear
|
59
|
+
self::ROUTES.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(app = Innate::DynaMap)
|
63
|
+
@app = app
|
64
|
+
end
|
65
|
+
|
66
|
+
def call(env)
|
67
|
+
path = env['PATH_INFO']
|
68
|
+
path << '/' if path.empty?
|
69
|
+
|
70
|
+
if modified = resolve(path)
|
71
|
+
Log.debug("%s routes %p to %p" % [self.class.name, path, modified])
|
72
|
+
env['PATH_INFO'] = modified
|
73
|
+
end
|
74
|
+
|
75
|
+
@app.call(env)
|
76
|
+
end
|
77
|
+
|
78
|
+
def resolve(path)
|
79
|
+
self.class::ROUTES.each do |key, value|
|
80
|
+
if key.is_a?(Regexp)
|
81
|
+
md = path.match(key)
|
82
|
+
return value % md.to_a[1..-1] if md
|
83
|
+
|
84
|
+
elsif value.respond_to?(:call)
|
85
|
+
new_path = value.call(path, Current.request)
|
86
|
+
return new_path if new_path
|
87
|
+
|
88
|
+
elsif value.respond_to?(:to_str)
|
89
|
+
return value.to_str if path == key
|
90
|
+
|
91
|
+
else
|
92
|
+
Log.error("Invalid route %p => %p" % [key, value])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Identical with Innate::Route, but is called before any Node::call is made
|
101
|
+
class Rewrite < Route
|
102
|
+
ROUTES = []
|
103
|
+
end
|
104
|
+
|
105
|
+
module SingletonMethods
|
106
|
+
def Route(key, value = nil, &block)
|
107
|
+
Route[key] = value || block
|
108
|
+
end
|
109
|
+
|
110
|
+
def Rewrite(key, value = nil, &block)
|
111
|
+
Rewrite[key] = value || block
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|