sidewalk 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/sidewalk/app_uri.rb +33 -0
- data/lib/sidewalk/application.rb +146 -0
- data/lib/sidewalk/config.rb +160 -0
- data/lib/sidewalk/controller.rb +141 -0
- data/lib/sidewalk/controller_mixins/view_templates.rb +94 -0
- data/lib/sidewalk/errors.rb +71 -0
- data/lib/sidewalk/redirect.rb +78 -0
- data/lib/sidewalk/regexp.rb +32 -5
- data/lib/sidewalk/relative_uri.rb +38 -0
- data/lib/sidewalk/request.rb +116 -13
- data/lib/sidewalk/rooted_uri.rb +32 -0
- data/lib/sidewalk/template_handlers/base.rb +30 -0
- data/lib/sidewalk/template_handlers/base_delegate.rb +39 -0
- data/lib/sidewalk/template_handlers/erb_handler.rb +40 -0
- data/lib/sidewalk/template_handlers/haml_handler.rb +19 -0
- data/lib/sidewalk/template_handlers/rxhp_handler.rb +32 -0
- data/lib/sidewalk/uri.rb +2 -0
- data/lib/sidewalk/uri_mapper.rb +89 -2
- data/lib/sidewalk/uri_match.rb +17 -1
- data/lib/sidewalk.rb +2 -0
- metadata +32 -4
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'sidewalk/errors'
|
2
|
+
|
3
|
+
module Sidewalk
|
4
|
+
# Base class for HTTP-level redirections.
|
5
|
+
#
|
6
|
+
# This, and its' subclasses are expected to be +raise+d.
|
7
|
+
class Redirect < HttpError
|
8
|
+
# Where to redirect to.
|
9
|
+
attr_reader :url
|
10
|
+
|
11
|
+
# Initialize a Redirect.
|
12
|
+
#
|
13
|
+
# You probably don't want to use this directly - use
|
14
|
+
# {PermanentRedirect} or {SeeOtherRedirect} instead.
|
15
|
+
#
|
16
|
+
# @param [String, URI] url is where to redirect to
|
17
|
+
# @param [Fixnum] status is a numeric HTTP status code
|
18
|
+
# @param [String] description is a short description of the status
|
19
|
+
# code, such as 'Moved Permanently'
|
20
|
+
def initialize url, status, description
|
21
|
+
@url = url.to_s
|
22
|
+
super status, description
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Use if a resource has permanently moved.
|
27
|
+
#
|
28
|
+
# Does not change POST into GET.
|
29
|
+
#
|
30
|
+
# Uses a 301 Moved Permanently response.
|
31
|
+
class PermanentRedirect < Redirect
|
32
|
+
def initialize url
|
33
|
+
super url, 301, 'Moved Permanently'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Use if the resource hasn't moved, but you want to redirect anyway.
|
38
|
+
#
|
39
|
+
# The new request will be a GET request.
|
40
|
+
#
|
41
|
+
# Standard usage is to redirect after a POST, to make the browser's
|
42
|
+
# Refresh and back/forward buttons work as expected.
|
43
|
+
#
|
44
|
+
# Defaults to a '303 See Other', but if you need to support pre-HTTP/1.1
|
45
|
+
# browsers, you might want to change this.
|
46
|
+
#
|
47
|
+
# @example Supporting Legacy Browsers
|
48
|
+
class SeeOtherRedirect < Redirect
|
49
|
+
def initialize url
|
50
|
+
super url, nil, nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return an appropriate status code.
|
54
|
+
#
|
55
|
+
# @return +303+ for HTTP/1.1 clients
|
56
|
+
# @return +302+ for HTTP/1.0 clients
|
57
|
+
def status request
|
58
|
+
if request.http_version == '1.1'
|
59
|
+
303
|
60
|
+
else
|
61
|
+
302
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return an appropriate description.
|
66
|
+
#
|
67
|
+
# @return 'See Other' for HTTP/1.1 clients
|
68
|
+
# @return 'Found' for HTTP/1.0 clients
|
69
|
+
def description request
|
70
|
+
case status(request)
|
71
|
+
when 303
|
72
|
+
'See Other'
|
73
|
+
when 302
|
74
|
+
'Found'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/sidewalk/regexp.rb
CHANGED
@@ -11,13 +11,40 @@ module Sidewalk
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
if
|
14
|
+
REGEXP_USING_ONIGURUMA = RUBY_VERSION.start_with?('1.8.')
|
15
|
+
if REGEXP_USING_ONIGURUMA
|
16
16
|
require 'oniguruma'
|
17
|
-
|
18
|
-
|
17
|
+
REGEXP_BASE = ::Oniguruma::ORegexp
|
19
18
|
::MatchData.send(:include, Sidewalk::Oniguruma::MatchData)
|
20
19
|
else
|
21
|
-
|
20
|
+
REGEXP_BASE = ::Regexp
|
21
|
+
end
|
22
|
+
|
23
|
+
# A Regexp class that supports named captures.
|
24
|
+
#
|
25
|
+
# This class exists just to give a portable class name to refer to.
|
26
|
+
#
|
27
|
+
# * On Ruby 1.9, this inherits +::Regexp+.
|
28
|
+
# * On Ruby 1.8, this inherits +Oniguruma::ORegexp+ (which is basically
|
29
|
+
# the same library that Ruby 1.9 uses for +::Regexp+)
|
30
|
+
#
|
31
|
+
# @example Using named captures
|
32
|
+
# regexp = Sidewalk::Regexp.new('(?<foo>bar)')
|
33
|
+
# match = regexp.match(regexp)
|
34
|
+
# match['foo'].should == 'bar'
|
35
|
+
class Regexp < REGEXP_BASE
|
36
|
+
# Whether we're using +Onigirumua::ORegexp+.
|
37
|
+
#
|
38
|
+
# Inverse of {#native?}.
|
39
|
+
def self.oniguruma?
|
40
|
+
Sidewalk::REGEXP_USING_ONIGURUMA
|
41
|
+
end
|
42
|
+
|
43
|
+
# Whether we're using +::Regexp+
|
44
|
+
#
|
45
|
+
# Inverse of {#native?}.
|
46
|
+
def self.native?
|
47
|
+
!self.oniguruma?
|
48
|
+
end
|
22
49
|
end
|
23
50
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sidewalk/controller'
|
2
|
+
require 'sidewalk/rooted_uri'
|
3
|
+
require 'sidewalk/request'
|
4
|
+
|
5
|
+
module Sidewalk
|
6
|
+
# URI relative to the current request.
|
7
|
+
#
|
8
|
+
# For example, if your app lives at 'http://www.example.com/foo',
|
9
|
+
# the current request is to '/foo/bar', +RelativeUri.new('/baz').to_s+
|
10
|
+
# will return 'http://www.example.com/foo/bar/baz', whereas {AppUri}
|
11
|
+
# would give you 'http://www.example.com/foo/baz'.
|
12
|
+
#
|
13
|
+
# Existing query data is discarded.
|
14
|
+
#
|
15
|
+
# Not a real class as the +URI+ hierarchy doesn't lend itself to
|
16
|
+
# subclassing.
|
17
|
+
module RelativeUri
|
18
|
+
# Create a URI relative to the current request.
|
19
|
+
#
|
20
|
+
# If this is called, it _must_ have {Controller#call} in the call stack
|
21
|
+
# so that {Controller#current} works - otherwise it does not have
|
22
|
+
# enough information to construct the URI.
|
23
|
+
#
|
24
|
+
# Query string data is discarded.
|
25
|
+
#
|
26
|
+
# @param [String] path is the path relative to the current request.
|
27
|
+
# @param [Hash] query is a +Hash+ of key-value query data.
|
28
|
+
def self.new path, query = {}
|
29
|
+
context = Sidewalk::Controller.current
|
30
|
+
unless context
|
31
|
+
raise ScriptError.new("Only valid when called by a controller")
|
32
|
+
end
|
33
|
+
uri = context.request.uri
|
34
|
+
|
35
|
+
Sidewalk::RootedUri.new(uri, path, query)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/sidewalk/request.rb
CHANGED
@@ -4,38 +4,135 @@ require 'rack/request'
|
|
4
4
|
require 'uri'
|
5
5
|
|
6
6
|
module Sidewalk
|
7
|
+
# An object representing an HTTP request.
|
8
|
+
#
|
9
|
+
# This is meant to provide convenient, high-level functions.
|
10
|
+
# You do have access to #{rack_environment} and #{rack_request} for
|
11
|
+
# lower-level information, but it's best avoided.
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# Instances of this class are created by the {Application}.
|
7
15
|
class Request
|
8
|
-
|
16
|
+
# The environment array provided by Rack.
|
17
|
+
#
|
18
|
+
# Please don't use this directly unless you're sure you need to.
|
19
|
+
attr_reader :rack_environment
|
20
|
+
# An instance of +Rack::Request+.
|
21
|
+
#
|
22
|
+
# This is a lower-level wrapper around {#rack_environment}
|
23
|
+
#
|
24
|
+
# Please don't use this directly unless you're sure you need to.
|
25
|
+
attr_reader :rack_request
|
26
|
+
|
27
|
+
# Create a new instance from a Rack environment array
|
28
|
+
#
|
29
|
+
# @param [Array] env is the environment data provided by Rack.
|
9
30
|
def initialize env
|
10
|
-
@
|
31
|
+
@rack_environment = env
|
11
32
|
@rack_version = env['rack.version']
|
12
33
|
|
13
34
|
sanity_check!
|
14
|
-
@rack_request = Rack::Request.new(
|
35
|
+
@rack_request = Rack::Request.new(rack_environment)
|
15
36
|
|
16
37
|
initialize_uris
|
17
38
|
end
|
18
39
|
|
40
|
+
# The HTTP headers.
|
41
|
+
#
|
42
|
+
# This does not include Rack/CGI variables - just real HTTP headers.
|
43
|
+
def headers
|
44
|
+
@headers ||= rack_environment.select{|k,v| k.start_with? 'HTTP_'}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Cookies provided by the client.
|
48
|
+
#
|
49
|
+
# @returns [Hash]
|
50
|
+
def cookies
|
51
|
+
rack_request.cookies
|
52
|
+
end
|
53
|
+
|
54
|
+
# What version of HTTP the client is using.
|
55
|
+
#
|
56
|
+
# @return '1.1'
|
57
|
+
# @return '1.0'
|
58
|
+
# @return nil
|
59
|
+
def http_version
|
60
|
+
@http_version ||= [
|
61
|
+
'HTTP_VERSION',
|
62
|
+
'SERVER_PROTOCOL',
|
63
|
+
].map{ |x| rack_environment[x] }.find.first.to_s.split('/').last
|
64
|
+
end
|
65
|
+
|
66
|
+
# The root URI of the application.
|
67
|
+
#
|
68
|
+
# If you're looking at this to construct an URI to another page, take a
|
69
|
+
# look at {Sidewalk::AppUri}.
|
70
|
+
#
|
71
|
+
# If you want to find out where the current request was to, see {#uri}.
|
72
|
+
#
|
73
|
+
# @return [URI::Common]
|
74
|
+
def root_uri
|
75
|
+
@root_uri.dup
|
76
|
+
end
|
77
|
+
|
78
|
+
# The URI of the current request.
|
79
|
+
#
|
80
|
+
# If you're looking at this to construct a relative URI, take a look at
|
81
|
+
# {Sidewalk::RelativeUri}.
|
82
|
+
#
|
83
|
+
# If you want to find out what the URI for the application is, see
|
84
|
+
# {#root_uri}.
|
85
|
+
#
|
86
|
+
# @return [URI::Common]
|
87
|
+
def uri
|
88
|
+
@request_uri.dup
|
89
|
+
end
|
90
|
+
|
91
|
+
# Whether or not this request came via HTTPS.
|
92
|
+
#
|
93
|
+
# @return [true, false]
|
19
94
|
def secure?
|
20
95
|
@secure
|
21
96
|
end
|
22
97
|
|
98
|
+
# Parameters provided via the query string.
|
99
|
+
#
|
100
|
+
# Consider using {#params} instead.
|
101
|
+
#
|
102
|
+
# @return [Hash]
|
23
103
|
def get_params
|
24
104
|
@get_params ||= @rack_request.GET
|
25
105
|
end
|
26
106
|
|
107
|
+
# The {UriMatch} created by {UriMapper}.
|
27
108
|
def uri_match
|
28
|
-
@uri_match ||=
|
109
|
+
@uri_match ||= rack_environment['sidewalk.urimatch']
|
29
110
|
end
|
30
111
|
|
112
|
+
# Paramters provided via POST.
|
113
|
+
#
|
114
|
+
# Consider using {#params} instead.
|
115
|
+
#
|
116
|
+
# @return [Hash]
|
31
117
|
def post_params
|
32
118
|
@post_params ||= @rack_request.POST
|
33
119
|
end
|
34
120
|
|
121
|
+
# Paramters provided via named captures in the URL.
|
122
|
+
#
|
123
|
+
# Consider using {#params} instead.
|
124
|
+
#
|
125
|
+
# @return [Hash]
|
35
126
|
def uri_params
|
36
127
|
uri_match.parameters
|
37
128
|
end
|
38
129
|
|
130
|
+
# Parameters provided by any means.
|
131
|
+
#
|
132
|
+
# Precedence:
|
133
|
+
# * URL captures first
|
134
|
+
# * Then query string parameters
|
135
|
+
# * Then POST parameters
|
39
136
|
def params
|
40
137
|
# URI parameters take precendence
|
41
138
|
@params ||= post_params.merge(get_params.merge(uri_params))
|
@@ -44,7 +141,7 @@ module Sidewalk
|
|
44
141
|
private
|
45
142
|
|
46
143
|
def initialize_uris
|
47
|
-
case
|
144
|
+
case rack_environment['rack.url_scheme']
|
48
145
|
when 'http'
|
49
146
|
@secure = false
|
50
147
|
uri_class = URI::HTTP
|
@@ -52,36 +149,42 @@ module Sidewalk
|
|
52
149
|
@secure = true
|
53
150
|
uri_class = URI::HTTPS
|
54
151
|
else
|
55
|
-
raise
|
152
|
+
raise ArgumentError.new(
|
153
|
+
"Unknown URL scheme: #{rack_environment['rack.url_scheme'].inspect}"
|
154
|
+
)
|
56
155
|
end
|
57
156
|
|
58
|
-
root =
|
157
|
+
root = rack_environment['SCRIPT_NAME']
|
59
158
|
unless root.start_with? '/'
|
60
159
|
root[0,0] = '/' # no prepend on 1.8
|
61
160
|
end
|
62
161
|
|
63
162
|
@root_uri = uri_class.build(
|
64
|
-
:host =>
|
65
|
-
:port =>
|
163
|
+
:host => rack_environment['SERVER_NAME'],
|
164
|
+
:port => rack_environment['SERVER_PORT'].to_i,
|
66
165
|
:path => root
|
67
166
|
).freeze
|
68
|
-
path_info =
|
167
|
+
path_info = rack_environment['PATH_INFO']
|
69
168
|
if root.end_with? '/' and path_info.start_with? '/'
|
70
169
|
path_info = path_info[1..-1]
|
71
170
|
end
|
72
171
|
@request_uri = @root_uri.dup
|
73
172
|
@request_uri.path += path_info
|
74
|
-
@request_uri.query =
|
173
|
+
@request_uri.query = rack_environment['QUERY_STRING']
|
75
174
|
@request_uri.freeze
|
76
175
|
end
|
77
176
|
|
78
177
|
def sanity_check!
|
79
178
|
# Sanity checks
|
80
179
|
unless @rack_version
|
81
|
-
raise ArgumentError.new
|
180
|
+
raise ArgumentError.new(
|
181
|
+
"env doesn't specify a Rack version"
|
182
|
+
)
|
82
183
|
end
|
83
184
|
if @rack_version != [1, 1]
|
84
|
-
raise
|
185
|
+
raise ArgumentError.new(
|
186
|
+
"Expected Rack version [1, 1], got #{@rack_version}"
|
187
|
+
)
|
85
188
|
end
|
86
189
|
end
|
87
190
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Sidewalk
|
4
|
+
# A URI relative to a specified root URI.
|
5
|
+
#
|
6
|
+
# You probably don't want to use this directly - see {AppUri} and
|
7
|
+
# {RelativeUri} for examples.
|
8
|
+
module RootedUri
|
9
|
+
# Create a new URI, relative to the specified root.
|
10
|
+
#
|
11
|
+
# @param [URI::Common] root is the uri that you want to use as the base
|
12
|
+
# @param [String] path is the sub-path to add
|
13
|
+
# @param [Hash] query is a +Hash+ of key-values to add to the query
|
14
|
+
# string.
|
15
|
+
def self.new root, path, query = {}
|
16
|
+
uri = root.dup
|
17
|
+
root_path = uri.path
|
18
|
+
root_path = root_path[0..-2] if root_path.end_with? '/'
|
19
|
+
uri.path = root_path + path
|
20
|
+
|
21
|
+
query_string = query.map do |k,v|
|
22
|
+
'%s=%s' % [
|
23
|
+
Rack::Utils.escape(k.to_s),
|
24
|
+
Rack::Utils.escape(v.to_s),
|
25
|
+
]
|
26
|
+
end.join('&')
|
27
|
+
uri.query = query_string unless query.empty?
|
28
|
+
|
29
|
+
uri
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Sidewalk
|
2
|
+
module TemplateHandlers
|
3
|
+
# Base class for TemplateHandlers.
|
4
|
+
#
|
5
|
+
# This currently just defines an interface - see {ErbHandler} for an
|
6
|
+
# example.
|
7
|
+
#
|
8
|
+
# Instances of this class may be re-used between requests. Anything
|
9
|
+
# request-specific needs to go in the {#render} method.
|
10
|
+
class Base
|
11
|
+
# Actually render a template.
|
12
|
+
#
|
13
|
+
# @param {Controller} controller is the controller that wants the
|
14
|
+
# template. Instance variables should be available to the template,
|
15
|
+
# and the handler should not change the scope. See {BaseDelegate}
|
16
|
+
# and {ErbHandler::Delegate} for an approach to this.
|
17
|
+
#
|
18
|
+
# @return [String]
|
19
|
+
def render controller
|
20
|
+
raise NotImplementedError.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# A new instance of the controller.
|
24
|
+
#
|
25
|
+
# @param [String] path a full path to a template source file.
|
26
|
+
def initialize path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sidewalk
|
2
|
+
module TemplateHandlers
|
3
|
+
# A delegate scope to run templates in.
|
4
|
+
#
|
5
|
+
# Used to provide templates access to the controller's instance
|
6
|
+
# variables, and extra helper functions, without polluting the
|
7
|
+
# controller with them. For example, {ErbHandler::Delegate} will also
|
8
|
+
# include the standard +ERB::Util+ helper functions like +#h+ (aka
|
9
|
+
# +#html_escape+)
|
10
|
+
class BaseDelegate
|
11
|
+
# A new instance of delegate.
|
12
|
+
#
|
13
|
+
# Copies all instance variables from controller to object.
|
14
|
+
#
|
15
|
+
# @param [Controller] controller
|
16
|
+
def initialize controller
|
17
|
+
controller.instance_variables.each do |name|
|
18
|
+
self.instance_variable_set(
|
19
|
+
name,
|
20
|
+
controller.instance_variable_get(name)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Render the template in the scope.
|
26
|
+
#
|
27
|
+
# The default implementation raises a NotImplementedError.
|
28
|
+
#
|
29
|
+
# @example {ErbDelegate#render}
|
30
|
+
# def render
|
31
|
+
# # @template is set by {ErbDelegate}'s constructor.
|
32
|
+
# @template.result binding
|
33
|
+
# end
|
34
|
+
def render
|
35
|
+
raise NotImplementedError.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'sidewalk/template_handlers/base'
|
2
|
+
require 'sidewalk/template_handlers/base_delegate'
|
3
|
+
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
module Sidewalk
|
7
|
+
module TemplateHandlers
|
8
|
+
# Handler for ERB Templates.
|
9
|
+
class ErbHandler < Base
|
10
|
+
def initialize path
|
11
|
+
super path
|
12
|
+
@template = ERB::new(File.read(path))
|
13
|
+
@template.filename = path
|
14
|
+
end
|
15
|
+
|
16
|
+
def render controller
|
17
|
+
Delegate.new(@template, controller).render
|
18
|
+
end
|
19
|
+
|
20
|
+
# Class representing the controller to ERB.
|
21
|
+
#
|
22
|
+
# Using a delegate so we can add extra methods to the binding
|
23
|
+
# without polluting the class - for example, most people expect an
|
24
|
+
# ERB template to have access to ERB::Util#h
|
25
|
+
class Delegate < BaseDelegate
|
26
|
+
# Pull in '#html_escape' aka '#h
|
27
|
+
include ERB::Util
|
28
|
+
|
29
|
+
def initialize template, controller
|
30
|
+
@template = template
|
31
|
+
super controller
|
32
|
+
end
|
33
|
+
|
34
|
+
def render
|
35
|
+
@template.result binding
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'sidewalk/template_handlers/base'
|
2
|
+
require 'sidewalk/template_handlers/base_delegate'
|
3
|
+
|
4
|
+
require 'haml'
|
5
|
+
|
6
|
+
module Sidewalk
|
7
|
+
module TemplateHandlers
|
8
|
+
class HamlHandler < Base
|
9
|
+
def initialize path
|
10
|
+
super path
|
11
|
+
@engine = Haml::Engine.new(File.read(path))
|
12
|
+
end
|
13
|
+
|
14
|
+
def render controller
|
15
|
+
@engine.render(BaseDelegate.new(controller))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'sidewalk/template_handlers/base'
|
2
|
+
require 'sidewalk/template_handlers/base_delegate'
|
3
|
+
|
4
|
+
require 'rxhp'
|
5
|
+
require 'rxhp/html'
|
6
|
+
|
7
|
+
module Sidewalk
|
8
|
+
module TemplateHandlers
|
9
|
+
class RxhpHandler < Base
|
10
|
+
def initialize path
|
11
|
+
@path = path
|
12
|
+
@template = File.read(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def render controller
|
16
|
+
Delegate.new(@path, @template, controller).render
|
17
|
+
end
|
18
|
+
|
19
|
+
class Delegate < BaseDelegate
|
20
|
+
include Rxhp::Html
|
21
|
+
def initialize path, template, controller
|
22
|
+
@path, @template, @controller = path, template, controller
|
23
|
+
super controller
|
24
|
+
end
|
25
|
+
|
26
|
+
def render
|
27
|
+
eval(@template, binding, @path).render
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/sidewalk/uri.rb
ADDED
data/lib/sidewalk/uri_mapper.rb
CHANGED
@@ -1,16 +1,83 @@
|
|
1
1
|
require 'sidewalk/regexp'
|
2
2
|
require 'sidewalk/uri_match'
|
3
3
|
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
4
6
|
module Sidewalk
|
7
|
+
autoload :Application, 'sidewalk/application'
|
8
|
+
|
9
|
+
# Maps URIs to Controllers.
|
10
|
+
#
|
11
|
+
# Used to decide how to respond to a given HTTP request.
|
5
12
|
class UriMapper
|
6
|
-
|
13
|
+
# Initialize a UriMapper.
|
14
|
+
#
|
15
|
+
# This converts a convenient-to-write map into a fast-to-lookup map.
|
16
|
+
#
|
17
|
+
# The input hash takes +String+ regular expression patterns as keys,
|
18
|
+
# and values can either be another map, a {Controller} class (not an
|
19
|
+
# instance), a +Symbol+ or +String+ that is the name of a {Controller}
|
20
|
+
# class, or a +Proc+.
|
21
|
+
#
|
22
|
+
# If the value is a +String+, it will:
|
23
|
+
# * see if a +Class+ with the same name exists; if so, use that.
|
24
|
+
# * try to +require+ +foo_bar_controller.rb+ for 'FooBarController'
|
25
|
+
# * see if a +Class+ now exists with the same name
|
26
|
+
# * if so, done; if not, bail out.
|
27
|
+
#
|
28
|
+
# It looks for controller files in
|
29
|
+
# {Application#local_root}+/controllers+
|
30
|
+
#
|
31
|
+
# You usually won't interact with this class directly - instead you'll
|
32
|
+
# usually pass the input hash to {Sidewalk::Application}.
|
33
|
+
#
|
34
|
+
# Keys are required to be +String+s instead of +Regexp+s because they
|
35
|
+
# are converted to a +Sidewalk::+{Regexp} behind the scenes; this is
|
36
|
+
# is the same thing as a +::Regexp+ on Ruby 1.9, but on Ruby 1.8 it
|
37
|
+
# adds several additional features such as named captures.
|
38
|
+
#
|
39
|
+
# @example A Simple Map
|
40
|
+
# urimap = {
|
41
|
+
# '$' => :IndexController,
|
42
|
+
# 'hello$' => :HelloController,
|
43
|
+
# }
|
44
|
+
# run Sidewalk::Application.new(urimap)
|
45
|
+
#
|
46
|
+
# @example A Nested Map
|
47
|
+
# urimap = {
|
48
|
+
# '$' => :IndexController,
|
49
|
+
# 'foo/' => {
|
50
|
+
# '$' => :FooController,
|
51
|
+
# 'bar$' => :FooBarController,
|
52
|
+
# },
|
53
|
+
# }
|
54
|
+
# run Sidewalk::Application.new(urimap)
|
55
|
+
#
|
56
|
+
# @example A Map With Named Captures
|
57
|
+
# urimap = {
|
58
|
+
# '$' => :IndexController,
|
59
|
+
# 'foo/$' => :FooController,
|
60
|
+
# '~(?<username>[^/]+)/$' => :UserController
|
61
|
+
# }
|
62
|
+
# run Sidewalk::Application.new(urimap)
|
63
|
+
#
|
64
|
+
# @param [Hash] uri_map
|
7
65
|
def initialize uri_map = {}
|
8
66
|
unless uri_map.is_a? Hash
|
9
67
|
raise ArgumentError.new('URI map must be a Hash')
|
10
68
|
end
|
69
|
+
$LOAD_PATH.push File.join(
|
70
|
+
Sidewalk::Application.local_root,
|
71
|
+
'controllers'
|
72
|
+
)
|
11
73
|
@uri_map = Sidewalk::UriMapper.convert_map(uri_map)
|
74
|
+
$LOAD_PATH.pop
|
12
75
|
end
|
13
76
|
|
77
|
+
# Given a path, find what should respond to it.
|
78
|
+
#
|
79
|
+
# @return [UriMatch] if something was found.
|
80
|
+
# @return [nil] if there is no match.
|
14
81
|
def map path
|
15
82
|
Sidewalk::UriMapper.map(
|
16
83
|
[], #stack
|
@@ -19,7 +86,17 @@ module Sidewalk
|
|
19
86
|
)
|
20
87
|
end
|
21
88
|
|
22
|
-
#
|
89
|
+
# The normalized URI map.
|
90
|
+
#
|
91
|
+
# @return [Hash<Regexp,(Controller|Proc)>]
|
92
|
+
attr_reader :uri_map
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Convert uri_map from easy-to-write to fast-to-use.
|
97
|
+
#
|
98
|
+
# - Replace string keys with Sidewalk::Regexp instances
|
99
|
+
# - Attempt to load classes for symbols
|
23
100
|
def self.convert_map uri_map
|
24
101
|
out = {}
|
25
102
|
uri_map.each do |key, value|
|
@@ -36,6 +113,16 @@ module Sidewalk
|
|
36
113
|
key = Sidewalk::Regexp.new(key)
|
37
114
|
if value.is_a? Hash
|
38
115
|
out[key] = convert_map(value)
|
116
|
+
elsif value.is_a? Class
|
117
|
+
out[key] = value
|
118
|
+
elsif value.to_s.end_with? 'Controller'
|
119
|
+
# Attempt to load the class
|
120
|
+
begin
|
121
|
+
out[key] = value.to_s.constantize
|
122
|
+
rescue NameError
|
123
|
+
require value.to_s.underscore
|
124
|
+
retry
|
125
|
+
end
|
39
126
|
else
|
40
127
|
out[key] = value
|
41
128
|
end
|