manveru-innate 2009.02.06
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/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
|