utopia 1.9.11 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +3 -2
- data/.gitignore +4 -1
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -1
- data/README.md +2 -2
- data/Rakefile +10 -10
- data/benchmarks/call_vs_check.rb +36 -0
- data/benchmarks/const_vs_hash.rb +33 -0
- data/documentation/Gemfile +5 -0
- data/documentation/Guardfile +20 -0
- data/documentation/config.ru +6 -13
- data/documentation/config/puma.rb +20 -0
- data/documentation/pages/_editor.xnode +64 -0
- data/documentation/pages/_heading.xnode +2 -2
- data/documentation/pages/_page.xnode +1 -2
- data/documentation/pages/errors/exception.xnode +3 -3
- data/documentation/pages/errors/file-not-found.xnode +3 -3
- data/documentation/pages/wiki/bower-integration/content.md +1 -1
- data/documentation/pages/wiki/content.md +6 -8
- data/documentation/pages/wiki/controller.rb +3 -3
- data/documentation/pages/wiki/edit.xnode +7 -19
- data/documentation/pages/wiki/middleware/content/content.md +4 -10
- data/documentation/pages/wiki/{controller → middleware/controller}/actions/content.md +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/links.yaml +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/rewrite/content.md +3 -3
- data/documentation/pages/wiki/show.xnode +4 -6
- data/documentation/pages/wiki/updating-utopia/content.md +55 -0
- data/documentation/pages/wiki/your-first-page/content.md +5 -3
- data/documentation/public/materials +1 -0
- data/lib/utopia.rb +3 -4
- data/lib/utopia/command.rb +4 -284
- data/lib/utopia/command/server.rb +115 -0
- data/lib/utopia/command/setup.rb +78 -0
- data/lib/utopia/command/site.rb +183 -0
- data/lib/utopia/content.rb +83 -59
- data/lib/utopia/content/{transaction.rb → document.rb} +116 -110
- data/lib/utopia/content/link.rb +7 -2
- data/lib/utopia/content/links.rb +2 -1
- data/lib/utopia/content/markup.rb +7 -2
- data/lib/utopia/{tags/deferred.rb → content/namespace.rb} +25 -6
- data/lib/utopia/content/node.rb +74 -76
- data/lib/utopia/content/response.rb +22 -3
- data/lib/utopia/content/tags.rb +66 -0
- data/lib/utopia/controller.rb +10 -18
- data/lib/utopia/controller/actions.rb +10 -0
- data/lib/utopia/controller/base.rb +2 -1
- data/lib/utopia/controller/respond.rb +1 -1
- data/lib/utopia/controller/rewrite.rb +8 -4
- data/lib/utopia/exceptions.rb +1 -0
- data/lib/utopia/exceptions/handler.rb +7 -2
- data/lib/utopia/exceptions/mailer.rb +33 -12
- data/lib/utopia/{tags/node.rb → extensions/array_split.rb} +11 -9
- data/lib/utopia/{tags/environment.rb → extensions/date_comparisons.rb} +24 -14
- data/lib/utopia/http.rb +2 -0
- data/lib/utopia/locale.rb +1 -0
- data/lib/utopia/localization.rb +37 -28
- data/lib/utopia/logger.rb +1 -0
- data/lib/utopia/logger/compact_formatter.rb +1 -0
- data/lib/utopia/middleware.rb +11 -1
- data/lib/utopia/path.rb +1 -0
- data/lib/utopia/path/matcher.rb +14 -2
- data/lib/utopia/redirection.rb +13 -16
- data/lib/utopia/session.rb +14 -6
- data/lib/utopia/setup.rb +3 -1
- data/lib/utopia/static.rb +11 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/server/git/hooks/post-receive +0 -4
- data/setup/site/.gitignore +9 -0
- data/setup/site/.rspec +1 -0
- data/setup/site/Gemfile +4 -0
- data/setup/site/Guardfile +17 -0
- data/setup/site/Rakefile +2 -2
- data/setup/site/config.ru +5 -12
- data/setup/site/pages/_heading.xnode +2 -2
- data/setup/site/pages/_page.xnode +1 -1
- data/setup/site/pages/errors/exception.xnode +3 -3
- data/setup/site/pages/errors/file-not-found.xnode +3 -3
- data/setup/site/pages/welcome/index.xnode +3 -3
- data/setup/site/public/_static/site.css +4 -0
- data/setup/site/spec/spec_helper.rb +29 -0
- data/setup/site/tasks/deploy.rake +13 -0
- data/setup/site/tasks/development.rake +34 -0
- data/setup/site/tasks/environment.rake +17 -0
- data/spec/mock_node.rb +15 -0
- data/spec/spec_helper.rb +29 -0
- data/{lib/utopia/extensions/date.rb → spec/utopia/content/document_spec.rb} +31 -21
- data/spec/utopia/content/markup_spec.rb +2 -2
- data/spec/utopia/content/{tag_spec.rb → namespace_spec.rb} +17 -10
- data/spec/utopia/content/tags_spec.rb +80 -0
- data/spec/utopia/content_spec.rb +1 -1
- data/spec/utopia/content_spec.ru +1 -6
- data/spec/utopia/content_spec/_heading.xnode +1 -1
- data/spec/utopia/content_spec/content/test-partial.xnode +1 -1
- data/spec/utopia/content_spec/index.xnode +1 -1
- data/spec/utopia/controller/middleware_spec.ru +1 -3
- data/spec/utopia/controller/respond_spec.rb +2 -22
- data/spec/utopia/controller/respond_spec.ru +1 -5
- data/spec/utopia/controller/respond_spec/errors/file-not-found.xnode +7 -6
- data/spec/utopia/exceptions/handler_spec.ru +1 -2
- data/spec/utopia/exceptions/mailer_spec.ru +1 -2
- data/spec/utopia/extensions_spec.rb +2 -2
- data/spec/utopia/localization_spec.ru +1 -2
- data/spec/utopia/performance_spec.rb +2 -6
- data/spec/utopia/performance_spec/config.ru +5 -12
- data/spec/utopia/performance_spec/pages/_heading.xnode +2 -2
- data/spec/utopia/performance_spec/pages/_page.xnode +1 -1
- data/spec/utopia/performance_spec/pages/errors/exception.xnode +3 -3
- data/spec/utopia/performance_spec/pages/errors/file-not-found.xnode +3 -3
- data/spec/utopia/performance_spec/pages/welcome/index.xnode +3 -3
- data/spec/utopia/setup_spec.rb +79 -15
- data/utopia.gemspec +3 -3
- metadata +41 -27
- data/.simplecov +0 -9
- data/documentation/pages/welcome/index.xnode +0 -41
- data/lib/utopia/content/tag.rb +0 -90
- data/lib/utopia/extensions/array.rb +0 -29
- data/lib/utopia/tags/override.rb +0 -33
- data/setup/site/.simplecov +0 -9
- data/setup/site/tasks/test.rake +0 -10
- data/setup/site/tasks/utopia.rake +0 -41
- data/spec/utopia/controller/respond_spec/rewrite/controller.rb +0 -12
data/lib/utopia/content/node.rb
CHANGED
@@ -20,12 +20,14 @@
|
|
20
20
|
|
21
21
|
require_relative 'markup'
|
22
22
|
require_relative 'links'
|
23
|
-
|
23
|
+
|
24
|
+
require_relative 'document'
|
24
25
|
|
25
26
|
require 'pathname'
|
26
27
|
|
27
28
|
module Utopia
|
28
29
|
class Content
|
30
|
+
# Represents an immutable node within the content hierarchy.
|
29
31
|
class Node
|
30
32
|
def initialize(controller, uri_path, request_path, file_path)
|
31
33
|
@controller = controller
|
@@ -39,12 +41,16 @@ module Utopia
|
|
39
41
|
attr :uri_path
|
40
42
|
attr :file_path
|
41
43
|
|
44
|
+
def name
|
45
|
+
@uri_path.basename
|
46
|
+
end
|
47
|
+
|
42
48
|
def link
|
43
49
|
return Link.new(:file, uri_path)
|
44
50
|
end
|
45
51
|
|
46
52
|
def lookup_node(path)
|
47
|
-
@controller.lookup_node(path)
|
53
|
+
@controller.lookup_node(@uri_path + Path[path])
|
48
54
|
end
|
49
55
|
|
50
56
|
def local_path(path = '.', base = nil)
|
@@ -60,17 +66,6 @@ module Utopia
|
|
60
66
|
end
|
61
67
|
end
|
62
68
|
|
63
|
-
def lookup(tag)
|
64
|
-
from_path = parent_path
|
65
|
-
|
66
|
-
# If the current node is called 'foo', we can't lookup 'foo' in the current directory or we will have infinite recursion.
|
67
|
-
if tag.name == @uri_path.basename
|
68
|
-
from_path = from_path.dirname
|
69
|
-
end
|
70
|
-
|
71
|
-
return @controller.lookup_tag(tag.name, from_path)
|
72
|
-
end
|
73
|
-
|
74
69
|
def parent_path
|
75
70
|
uri_path.dirname
|
76
71
|
end
|
@@ -105,83 +100,86 @@ module Utopia
|
|
105
100
|
def sibling_links(**options)
|
106
101
|
return Links.index(@controller.root, siblings_path, options)
|
107
102
|
end
|
108
|
-
|
109
|
-
|
103
|
+
|
104
|
+
# Lookup the given tag which is being rendered within the given node. Invoked by {Document}.
|
105
|
+
# @return [Node] The node which will be used to render the tag.
|
106
|
+
def lookup_tag(tag)
|
107
|
+
return @controller.lookup_tag(tag.name, self)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Invoked when the node is being rendered by {Document}.
|
111
|
+
def call(document, state)
|
110
112
|
# Load the template:
|
111
113
|
template = @controller.fetch_template(@file_path)
|
112
114
|
|
113
115
|
# Evaluate the template/code:
|
114
|
-
context = Context.new(
|
116
|
+
context = Context.new(document, state)
|
115
117
|
markup = template.to_buffer(context)
|
116
118
|
|
117
|
-
# Render the resulting markup into the
|
118
|
-
|
119
|
+
# Render the resulting markup into the document:
|
120
|
+
document.parse_markup(markup)
|
119
121
|
end
|
120
|
-
|
122
|
+
|
121
123
|
def process!(request, attributes = {})
|
122
|
-
|
123
|
-
|
124
|
-
output = transaction.render_node(self, attributes)
|
125
|
-
|
126
|
-
return [200, transaction.headers, [output]]
|
124
|
+
Document.render(self, request, attributes).to_a
|
127
125
|
end
|
128
|
-
end
|
129
126
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
127
|
+
# This is a special context in which a limited set of well defined methods are exposed in the content view.
|
128
|
+
Context = Struct.new(:document, :state) do
|
129
|
+
def partial(*args, &block)
|
130
|
+
if block_given?
|
131
|
+
state.defer(&block)
|
132
|
+
else
|
133
|
+
state.defer do |document|
|
134
|
+
document.tag(*args)
|
135
|
+
end
|
138
136
|
end
|
139
137
|
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
138
|
+
|
139
|
+
alias deferred_tag partial
|
140
|
+
|
141
|
+
def controller
|
142
|
+
document.controller
|
143
|
+
end
|
144
|
+
|
145
|
+
def localization
|
146
|
+
document.localization
|
147
|
+
end
|
148
|
+
|
149
|
+
def request
|
150
|
+
document.request
|
151
|
+
end
|
152
|
+
|
153
|
+
def response
|
154
|
+
document
|
155
|
+
end
|
156
|
+
|
157
|
+
def attributes
|
158
|
+
state.attributes
|
159
|
+
end
|
160
|
+
|
161
|
+
def [] key
|
162
|
+
state.attributes.fetch(key) {document.attributes[key]}
|
163
|
+
end
|
164
|
+
|
165
|
+
alias current state
|
166
|
+
|
167
|
+
def content
|
168
|
+
document.content
|
169
|
+
end
|
173
170
|
|
174
|
-
|
175
|
-
|
176
|
-
|
171
|
+
def parent
|
172
|
+
document.parent
|
173
|
+
end
|
177
174
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
175
|
+
def first
|
176
|
+
document.first
|
177
|
+
end
|
178
|
+
|
179
|
+
def links(*arguments, &block)
|
180
|
+
state.node.links(*arguments, &block)
|
181
|
+
end
|
184
182
|
end
|
185
183
|
end
|
186
184
|
end
|
187
|
-
end
|
185
|
+
end
|
@@ -24,25 +24,44 @@ module Utopia
|
|
24
24
|
EXPIRES = 'Expires'.freeze
|
25
25
|
CACHE_CONTROL = 'Cache-Control'.freeze
|
26
26
|
CONTENT_TYPE = 'Content-Type'.freeze
|
27
|
+
NO_CACHE = 'no-cache'.freeze
|
27
28
|
|
29
|
+
# A basic content response, including useful defaults for typical HTML5 content.
|
28
30
|
class Response
|
29
31
|
def initialize
|
30
32
|
@status = 200
|
31
33
|
@headers = {}
|
34
|
+
@body = []
|
35
|
+
|
36
|
+
# The default content type:
|
37
|
+
self.content_type = "text/html; charset=utf-8"
|
32
38
|
end
|
33
39
|
|
34
40
|
attr :status
|
35
41
|
attr :headers
|
42
|
+
attr :body
|
43
|
+
|
44
|
+
def content
|
45
|
+
@body.join
|
46
|
+
end
|
47
|
+
|
48
|
+
def lookup(tag)
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_a
|
53
|
+
[@status, @headers, @body]
|
54
|
+
end
|
36
55
|
|
37
56
|
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
38
57
|
def do_not_cache!
|
39
58
|
@headers[CACHE_CONTROL] = "no-cache, must-revalidate"
|
40
59
|
@headers[EXPIRES] = Time.now.httpdate
|
41
60
|
end
|
42
|
-
|
43
|
-
# Specify that the content
|
61
|
+
|
62
|
+
# Specify that the content could be cached.
|
44
63
|
def cache!(duration = 3600, access: "public")
|
45
|
-
unless @headers[CACHE_CONTROL]
|
64
|
+
unless cache_control = @headers[CACHE_CONTROL] and cache_control.include?(NO_CACHE)
|
46
65
|
@headers[CACHE_CONTROL] = "#{access}, max-age=#{duration}"
|
47
66
|
@headers[EXPIRES] = (Time.now + duration).httpdate
|
48
67
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'namespace'
|
22
|
+
|
23
|
+
module Utopia
|
24
|
+
class Content
|
25
|
+
# Tags which provide intrinsic behaviour within the content middleware.
|
26
|
+
module Tags
|
27
|
+
extend Namespace
|
28
|
+
|
29
|
+
# Invokes a node and renders a single node to the output stream.
|
30
|
+
# @param path [String] The path of the node to invoke.
|
31
|
+
tag('node') do |document, state|
|
32
|
+
path = Path[state[:path]]
|
33
|
+
|
34
|
+
node = document.lookup_node(path)
|
35
|
+
|
36
|
+
document.render_node(node)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Invokes a deferred tag from the current state. Works together with {Document::State#defer}.
|
40
|
+
# @param id [String] The id of the deferred to invoke.
|
41
|
+
tag('deferred') do |document, state|
|
42
|
+
id = state[:id].to_i
|
43
|
+
|
44
|
+
deferred = document.parent.deferred[id]
|
45
|
+
|
46
|
+
deferred.call(document, state)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Renders the content of the parent node into the output of the document.
|
50
|
+
tag('content') do |document, state|
|
51
|
+
# We are invoking this node within a parent who has content, and we want to generate output equal to that.
|
52
|
+
document.write(document.parent.content)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Render the contents only if in the correct environment.
|
56
|
+
# @param only [String] A comma separated list of environments to check.
|
57
|
+
tag('environment') do |document, state|
|
58
|
+
environment = document.attributes.fetch(:environment){RACK_ENV}.to_s
|
59
|
+
|
60
|
+
if state[:only].split(',').include?(environment)
|
61
|
+
document.parse_markup(state.content)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/utopia/controller.rb
CHANGED
@@ -54,39 +54,31 @@ module Utopia
|
|
54
54
|
request.env[VARIABLES_KEY]
|
55
55
|
end
|
56
56
|
|
57
|
-
|
57
|
+
# @param root [String] The content root where controllers will be loaded from.
|
58
|
+
# @param base [Class] The base class for controllers.
|
59
|
+
def initialize(app, root: Utopia::default_root, base: Controller::Base)
|
58
60
|
@app = app
|
59
|
-
@root = root
|
61
|
+
@root = root
|
60
62
|
|
61
|
-
|
62
|
-
@controller_cache = Concurrent::Map.new
|
63
|
-
else
|
64
|
-
@controller_cache = nil
|
65
|
-
end
|
63
|
+
@controller_cache = Concurrent::Map.new
|
66
64
|
|
67
|
-
|
68
|
-
|
69
|
-
@base = base || Controller::Base.dup.prepend(Controller::Actions)
|
65
|
+
@base = base
|
70
66
|
end
|
71
67
|
|
72
68
|
attr :app
|
73
69
|
|
74
70
|
def freeze
|
75
|
-
|
71
|
+
return self if frozen?
|
76
72
|
|
77
|
-
|
78
|
-
|
73
|
+
@root.freeze
|
74
|
+
@base.freeze
|
79
75
|
|
80
76
|
super
|
81
77
|
end
|
82
78
|
|
83
79
|
# Fetch the controller for the given relative path. May be cached.
|
84
80
|
def lookup_controller(path)
|
85
|
-
|
86
|
-
@controller_cache.fetch_or_store(path.to_s) do
|
87
|
-
load_controller_file(path)
|
88
|
-
end
|
89
|
-
else
|
81
|
+
@controller_cache.fetch_or_store(path.to_s) do
|
90
82
|
load_controller_file(path)
|
91
83
|
end
|
92
84
|
end
|
@@ -22,11 +22,17 @@ require_relative '../http'
|
|
22
22
|
|
23
23
|
module Utopia
|
24
24
|
class Controller
|
25
|
+
# A controller layer which invokes functinality based on the request path.
|
26
|
+
# @example
|
27
|
+
# on '*' do |request, path|
|
28
|
+
# succeed! content: 'Hello World'
|
29
|
+
# end
|
25
30
|
module Actions
|
26
31
|
def self.prepended(base)
|
27
32
|
base.extend(ClassMethods)
|
28
33
|
end
|
29
34
|
|
35
|
+
# A nested action lookup hash table.
|
30
36
|
class Action < Hash
|
31
37
|
def initialize(options = {}, &block)
|
32
38
|
@options = options
|
@@ -53,7 +59,10 @@ module Utopia
|
|
53
59
|
super and @callback == other.callback and @options == other.options
|
54
60
|
end
|
55
61
|
|
62
|
+
# Matches 0 or more path components.
|
56
63
|
WILDCARD_GREEDY = '**'.freeze
|
64
|
+
|
65
|
+
# Matches any 1 path component.
|
57
66
|
WILDCARD = '*'.freeze
|
58
67
|
|
59
68
|
# Given a path, iterate over all actions that match. Actions match from most specific to most general.
|
@@ -112,6 +121,7 @@ module Utopia
|
|
112
121
|
end
|
113
122
|
end
|
114
123
|
|
124
|
+
# Exposed to the controller class.
|
115
125
|
module ClassMethods
|
116
126
|
def self.extended(klass)
|
117
127
|
klass.instance_eval do
|
@@ -22,6 +22,7 @@ require_relative '../http'
|
|
22
22
|
|
23
23
|
module Utopia
|
24
24
|
class Controller
|
25
|
+
# The base implementation of a controller class.
|
25
26
|
class Base
|
26
27
|
# A string which is the full path to the directory which contains the controller.
|
27
28
|
def self.base_path
|
@@ -87,7 +88,7 @@ module Utopia
|
|
87
88
|
end
|
88
89
|
|
89
90
|
# Request relative redirect. Respond with a redirect to the given target.
|
90
|
-
def redirect!
|
91
|
+
def redirect!(target, status = 302)
|
91
92
|
status = HTTP::Status.new(status, 300...400)
|
92
93
|
location = target.to_s
|
93
94
|
|
@@ -23,7 +23,7 @@ require_relative '../path/matcher'
|
|
23
23
|
|
24
24
|
module Utopia
|
25
25
|
class Controller
|
26
|
-
#
|
26
|
+
# A controller layer which provides a convenient way to respond to different requested content types. The order in which you add converters matters, as it determines how the incoming Accept: header is mapped, e.g. the first converter is also defined as matching the media range */*.
|
27
27
|
module Respond
|
28
28
|
def self.prepended(base)
|
29
29
|
base.extend(ClassMethods)
|
@@ -25,15 +25,16 @@ module Utopia
|
|
25
25
|
class Controller
|
26
26
|
# This controller layer rewrites the path before executing controller actions. When the rule matches, the supplied block is executed.
|
27
27
|
# @example
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
28
|
+
# prepend Rewrite
|
29
|
+
# rewrite.extract_prefix id: Integer do
|
30
|
+
# @user = User.find(@id)
|
31
|
+
# end
|
32
32
|
module Rewrite
|
33
33
|
def self.prepended(base)
|
34
34
|
base.extend(ClassMethods)
|
35
35
|
end
|
36
36
|
|
37
|
+
# A abstract rule which can match against a request path.
|
37
38
|
class Rule
|
38
39
|
def apply_match_to_context(match_data, context)
|
39
40
|
match_data.names.each do |name|
|
@@ -42,6 +43,7 @@ module Utopia
|
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
46
|
+
# A rule which extracts a prefix pattern from the request path.
|
45
47
|
class ExtractPrefixRule < Rule
|
46
48
|
def initialize(patterns, block)
|
47
49
|
@matcher = Path::Matcher.new(patterns)
|
@@ -70,6 +72,7 @@ module Utopia
|
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
75
|
+
# Rewrite a request path based on a set of defined rules.
|
73
76
|
class Rewriter
|
74
77
|
def initialize
|
75
78
|
@rules = []
|
@@ -94,6 +97,7 @@ module Utopia
|
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
100
|
+
# Exposed to the controller class.
|
97
101
|
module ClassMethods
|
98
102
|
def rewrite
|
99
103
|
@rewriter ||= Rewriter.new
|