manveru-innate 2009.02.06
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1409 -0
- data/COPYING +18 -0
- data/MANIFEST +100 -0
- data/README.md +485 -0
- data/Rakefile +139 -0
- data/example/app/retro_games.rb +57 -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 +45 -0
- data/example/app/whywiki_erb/view/edit.html.erb +6 -0
- data/example/app/whywiki_erb/view/index.html.erb +10 -0
- data/example/custom_middleware.rb +43 -0
- data/example/error_handling.rb +31 -0
- data/example/hello.rb +12 -0
- data/example/howto_spec.rb +60 -0
- data/example/link.rb +35 -0
- data/example/providing_hash.rb +46 -0
- data/example/session.rb +42 -0
- data/innate.gemspec +118 -0
- data/lib/innate.rb +191 -0
- data/lib/innate/action.rb +156 -0
- data/lib/innate/adapter.rb +89 -0
- data/lib/innate/cache.rb +117 -0
- data/lib/innate/cache/api.rb +106 -0
- data/lib/innate/cache/drb.rb +58 -0
- data/lib/innate/cache/file_based.rb +39 -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/core_compatibility/basic_object.rb +9 -0
- data/lib/innate/core_compatibility/string.rb +3 -0
- data/lib/innate/current.rb +37 -0
- data/lib/innate/dynamap.rb +81 -0
- data/lib/innate/helper.rb +195 -0
- data/lib/innate/helper/aspect.rb +62 -0
- data/lib/innate/helper/cgi.rb +39 -0
- data/lib/innate/helper/flash.rb +36 -0
- data/lib/innate/helper/link.rb +55 -0
- data/lib/innate/helper/partial.rb +90 -0
- data/lib/innate/helper/redirect.rb +85 -0
- data/lib/innate/helper/send_file.rb +18 -0
- data/lib/innate/log.rb +23 -0
- data/lib/innate/log/color_formatter.rb +43 -0
- data/lib/innate/log/hub.rb +72 -0
- data/lib/innate/mock.rb +49 -0
- data/lib/innate/node.rb +471 -0
- data/lib/innate/options.rb +91 -0
- data/lib/innate/options/dsl.rb +155 -0
- data/lib/innate/request.rb +165 -0
- data/lib/innate/response.rb +18 -0
- data/lib/innate/route.rb +109 -0
- data/lib/innate/session.rb +104 -0
- data/lib/innate/session/flash.rb +94 -0
- data/lib/innate/setup.rb +23 -0
- data/lib/innate/spec.rb +42 -0
- data/lib/innate/state.rb +22 -0
- data/lib/innate/state/accessor.rb +130 -0
- data/lib/innate/state/fiber.rb +68 -0
- data/lib/innate/state/thread.rb +39 -0
- data/lib/innate/traited.rb +20 -0
- data/lib/innate/trinity.rb +22 -0
- data/lib/innate/version.rb +3 -0
- data/lib/innate/view.rb +67 -0
- data/lib/innate/view/erb.rb +17 -0
- data/lib/innate/view/none.rb +9 -0
- data/lib/rack/middleware_compiler.rb +62 -0
- data/lib/rack/reloader.rb +192 -0
- data/spec/example/hello.rb +14 -0
- data/spec/example/link.rb +29 -0
- data/spec/helper.rb +2 -0
- data/spec/innate/cache/common.rb +45 -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 +66 -0
- data/spec/innate/helper/aspect.rb +80 -0
- data/spec/innate/helper/cgi.rb +37 -0
- data/spec/innate/helper/flash.rb +148 -0
- data/spec/innate/helper/link.rb +82 -0
- data/spec/innate/helper/partial.rb +66 -0
- data/spec/innate/helper/redirect.rb +148 -0
- data/spec/innate/helper/send_file.rb +21 -0
- data/spec/innate/helper/view/aspect_hello.erb +1 -0
- data/spec/innate/helper/view/locals.erb +1 -0
- data/spec/innate/helper/view/loop.erb +4 -0
- data/spec/innate/helper/view/num.erb +1 -0
- data/spec/innate/helper/view/partial.erb +1 -0
- data/spec/innate/helper/view/recursive.erb +8 -0
- data/spec/innate/mock.rb +84 -0
- data/spec/innate/node.rb +180 -0
- data/spec/innate/node/bar.html +1 -0
- data/spec/innate/node/foo.html.erb +1 -0
- data/spec/innate/node/with_layout.erb +3 -0
- data/spec/innate/options.rb +90 -0
- data/spec/innate/parameter.rb +154 -0
- data/spec/innate/request.rb +73 -0
- data/spec/innate/route.rb +129 -0
- data/spec/innate/session.rb +59 -0
- data/spec/innate/traited.rb +55 -0
- metadata +160 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'innate/options/dsl'
|
2
|
+
|
3
|
+
module Innate
|
4
|
+
@options = Options.new(:innate)
|
5
|
+
|
6
|
+
def self.options; @options end
|
7
|
+
|
8
|
+
# This has to speak for itself.
|
9
|
+
|
10
|
+
options.dsl do
|
11
|
+
o "IP address or hostname that Ramaze will respond to - 0.0.0.0 for all",
|
12
|
+
:host, "0.0.0.0", :short => :H
|
13
|
+
|
14
|
+
o "Port for the server",
|
15
|
+
:port, 7000, :short => :p
|
16
|
+
|
17
|
+
o "Indicate that calls Innate::start will be ignored",
|
18
|
+
:started, false
|
19
|
+
|
20
|
+
o "Web server to run on",
|
21
|
+
:adapter, :webrick, :short => :a
|
22
|
+
|
23
|
+
o "Will send ::setup to each element during Innate::start",
|
24
|
+
:setup, [Innate::Cache, Innate::Node]
|
25
|
+
|
26
|
+
o "Headers that will be merged into the response before Node::call",
|
27
|
+
:header, {'Content-Type' => 'text/html'}
|
28
|
+
|
29
|
+
o "Trap this signal to issue shutdown, nil/false to disable trap",
|
30
|
+
:trap, 'SIGINT'
|
31
|
+
|
32
|
+
o "Keep state in Thread or Fiber, fall back to Thread if Fiber not available",
|
33
|
+
:state, :Fiber
|
34
|
+
|
35
|
+
sub :log do
|
36
|
+
o "Array of parameters for Logger.new, default to stderr for CGI",
|
37
|
+
:params, [$stderr]
|
38
|
+
o "Use ANSI colors for logging, nil does auto-detection by checking for #tty?",
|
39
|
+
:color, nil
|
40
|
+
end
|
41
|
+
|
42
|
+
sub :redirect do
|
43
|
+
o "Default response HTTP status on redirect",
|
44
|
+
:status, 302
|
45
|
+
end
|
46
|
+
|
47
|
+
sub :env do
|
48
|
+
o "Hostname of this machine",
|
49
|
+
:host, ENV['HOSTNAME'] # FIXME: cross-platform
|
50
|
+
o "Username executing the application",
|
51
|
+
:user, ENV['USER'] # FIXME: cross-platform
|
52
|
+
end
|
53
|
+
|
54
|
+
sub :app do
|
55
|
+
o "Unique identifier for this application",
|
56
|
+
:name, 'pristine'
|
57
|
+
o "Root directory containing the application",
|
58
|
+
:root, File.dirname($0)
|
59
|
+
o "Root directory for view templates, relative to app subdir",
|
60
|
+
:view, '/view'
|
61
|
+
o "Root directory for layout templates, relative to app subdir",
|
62
|
+
:layout, '/layout'
|
63
|
+
end
|
64
|
+
|
65
|
+
sub :session do
|
66
|
+
o "Key for the session cookie",
|
67
|
+
:key, 'innate.sid'
|
68
|
+
o "Domain the cookie relates to, unspecified if false",
|
69
|
+
:domain, false
|
70
|
+
o "Path the cookie relates to",
|
71
|
+
:path, '/'
|
72
|
+
o "Use secure cookie",
|
73
|
+
:secure, false
|
74
|
+
o "Time of cookie expiration",
|
75
|
+
:expires, Time.at(2147483647)
|
76
|
+
end
|
77
|
+
|
78
|
+
sub :cache do
|
79
|
+
o "Assign a cache to each of these names on Innate::Cache::setup",
|
80
|
+
:names, [:session]
|
81
|
+
|
82
|
+
default "If no option for the cache name exists, fall back to this",
|
83
|
+
Innate::Cache::Memory
|
84
|
+
end
|
85
|
+
|
86
|
+
sub :action do
|
87
|
+
o "wish => Action#method",
|
88
|
+
:wish, {'json' => :as_json, 'html' => :as_html, 'yaml' => :as_yaml}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,155 @@
|
|
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, &block)
|
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
|
+
# +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)
|
81
|
+
end
|
82
|
+
|
83
|
+
# To avoid lookup on the parent, we can set a default to the internal Hash.
|
84
|
+
# Parameters as in #o, but without the +key+.
|
85
|
+
def default(doc, value, other = {})
|
86
|
+
@hash.default = other.merge(:doc => doc, :value => value)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Try to retrieve the corresponding Hash for the passed keys, will try to
|
90
|
+
# retrieve the key from a parent if no match is found on the current
|
91
|
+
# instance. If multiple keys are passed it will try to find a matching
|
92
|
+
# child and pass the request on to it.
|
93
|
+
def get(key, *keys)
|
94
|
+
if keys.empty?
|
95
|
+
if value = @hash[key.to_sym]
|
96
|
+
value
|
97
|
+
elsif @parent != self
|
98
|
+
@parent.get(key)
|
99
|
+
else
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
elsif sub_options = get(key)
|
103
|
+
sub_options.get(*keys)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Retrieve only the :value from the value hash if found via +keys+.
|
108
|
+
def [](*keys)
|
109
|
+
if value = get(*keys)
|
110
|
+
value.is_a?(Hash) ? value[:value] : value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Assign new :value to the value hash on the current instance.
|
115
|
+
#
|
116
|
+
# TODO: allow arbitrary assignments
|
117
|
+
|
118
|
+
def []=(key, value)
|
119
|
+
if ns = @hash[key.to_sym]
|
120
|
+
ns[:value] = value
|
121
|
+
else
|
122
|
+
raise(ArgumentError, "No key for %p exists" % [key])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def method_missing(meth, *args)
|
127
|
+
case meth.to_s
|
128
|
+
when /^(.*)=$/
|
129
|
+
self[$1] = args.first
|
130
|
+
else
|
131
|
+
self[meth]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def merge!(hash)
|
136
|
+
hash.each do |key, value|
|
137
|
+
self[key] = value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
include Enumerable
|
142
|
+
|
143
|
+
def each(&block)
|
144
|
+
@hash.each(&block)
|
145
|
+
end
|
146
|
+
|
147
|
+
def inspect
|
148
|
+
@hash.inspect
|
149
|
+
end
|
150
|
+
|
151
|
+
def pretty_print(q)
|
152
|
+
q.pp_hash @hash
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,165 @@
|
|
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{|k| super(k) }
|
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:
|
88
|
+
# request.params
|
89
|
+
# # => {'name' => 'jason', 'age' => '45', 'job' => 'lumberjack'}
|
90
|
+
# request.subset('name')
|
91
|
+
# # => {'name' => 'jason'}
|
92
|
+
# request.subset(:name, :job)
|
93
|
+
# # => {'name' => 'jason', 'job' => 'lumberjack'}
|
94
|
+
|
95
|
+
def subset(*keys)
|
96
|
+
keys = keys.map{|k| k.to_s }
|
97
|
+
params.reject{|k,v| not keys.include?(k) }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Try to figure out the domain we are running on, this might work for some
|
101
|
+
# deployments but fail for others, given the combination of servers in
|
102
|
+
# front.
|
103
|
+
|
104
|
+
def domain(path = '/')
|
105
|
+
scheme = self.scheme || 'http'
|
106
|
+
host = env['HTTP_HOST']
|
107
|
+
|
108
|
+
URI("#{scheme}://#{host}#{path}")
|
109
|
+
end
|
110
|
+
|
111
|
+
# Try to find out which languages the client would like to have.
|
112
|
+
# Returns and array of locales from env['HTTP_ACCEPT_LANGUAGE].
|
113
|
+
# 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.,]+)/)
|
117
|
+
end
|
118
|
+
alias locales accept_language
|
119
|
+
|
120
|
+
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
|
+
ipv6 = %w[ fc00::/7 fe80::/10 fec0::/10 ::1 ]
|
122
|
+
LOCAL = (ipv4 + ipv6).map{|a| IPAddr.new(a)} unless defined?(LOCAL)
|
123
|
+
|
124
|
+
# Request is from a local network?
|
125
|
+
# Checks both IPv4 and IPv6
|
126
|
+
# Answer is true if the IP address making the request is from local network.
|
127
|
+
# Optional argument address can be used to check any IP address.
|
128
|
+
|
129
|
+
def local_net?(address = ip)
|
130
|
+
addr = IPAddr.new(address)
|
131
|
+
LOCAL.find{|range| range.include?(addr) }
|
132
|
+
rescue ArgumentError => ex
|
133
|
+
raise ArgumentError, ex unless ex.message == 'invalid address'
|
134
|
+
end
|
135
|
+
|
136
|
+
INTERESTING_HTTP_VARIABLES =
|
137
|
+
(/USER|HOST|REQUEST|REMOTE|FORWARD|REFER|PATH|QUERY|VERSION|KEEP|CACHE/)
|
138
|
+
|
139
|
+
# Interesting HTTP variables from env
|
140
|
+
def http_variables
|
141
|
+
env.reject{|key, value| key.to_s !~ INTERESTING_HTTP_VARIABLES }
|
142
|
+
end
|
143
|
+
alias http_vars http_variables
|
144
|
+
|
145
|
+
REQUEST_STRING_FORMAT = "#<%s params=%p cookies=%p env=%p>"
|
146
|
+
|
147
|
+
def to_s
|
148
|
+
REQUEST_STRING_FORMAT % [self.class, params, cookies, http_variables]
|
149
|
+
end
|
150
|
+
alias inspect to_s
|
151
|
+
|
152
|
+
# Pretty prints current action with parameters, cookies and enviroment
|
153
|
+
# variables.
|
154
|
+
def pretty_print(pp)
|
155
|
+
pp.object_group(self){
|
156
|
+
group = { 'params' => params, 'cookies' => cookies, 'env' => http_vars }
|
157
|
+
group.each do |name, hash|
|
158
|
+
pp.breakable
|
159
|
+
pp.text " @#{name}="
|
160
|
+
pp.nest(name.size + 3){ pp.pp_hash(hash) }
|
161
|
+
end
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Innate
|
2
|
+
|
3
|
+
# In order to reset the body contents we also need to reset the length set by
|
4
|
+
# Response#write - until I can submit a patch to Rack and the next release we
|
5
|
+
# just do this.
|
6
|
+
|
7
|
+
class Response < Rack::Response
|
8
|
+
attr_accessor :length
|
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
|
+
end
|
18
|
+
end
|
data/lib/innate/route.rb
ADDED
@@ -0,0 +1,109 @@
|
|
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
|
+
env['PATH_INFO'] = modified
|
68
|
+
end
|
69
|
+
|
70
|
+
@app.call(env)
|
71
|
+
end
|
72
|
+
|
73
|
+
def resolve(path)
|
74
|
+
request = Current.request
|
75
|
+
|
76
|
+
self.class::ROUTES.each do |key, value|
|
77
|
+
if key.is_a?(Regexp)
|
78
|
+
md = path.match(key)
|
79
|
+
return value % md.to_a[1..-1] if md
|
80
|
+
|
81
|
+
elsif value.respond_to?(:call)
|
82
|
+
new_path = value.call(path, Request.current)
|
83
|
+
return new_path if new_path
|
84
|
+
|
85
|
+
elsif value.respond_to?(:to_str)
|
86
|
+
return value.to_str if path == key
|
87
|
+
|
88
|
+
else
|
89
|
+
Log.error("Invalid route %p => %p" % [key, value])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Identical with Innate::Route, but is called before any Node::call is made
|
98
|
+
class Rewrite < Route
|
99
|
+
ROUTES = []
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.Route(key, value = nil, &block)
|
103
|
+
Route[key] = value || block
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.Rewrite(key, value = nil, &block)
|
107
|
+
Rewrite[key] = value || block
|
108
|
+
end
|
109
|
+
end
|