utopia 0.12.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/Gemfile +6 -0
- data/README.md +48 -14
- data/Rakefile +5 -0
- data/bin/utopia +132 -15
- data/lib/utopia.rb +13 -10
- data/lib/utopia/content.rb +140 -0
- data/lib/utopia/content/link.rb +124 -0
- data/lib/utopia/content/links.rb +228 -0
- data/lib/utopia/content/node.rb +387 -0
- data/lib/utopia/content/processor.rb +128 -0
- data/lib/utopia/content/tag.rb +102 -0
- data/lib/utopia/controller.rb +137 -0
- data/lib/utopia/controller/action.rb +112 -0
- data/lib/utopia/controller/base.rb +174 -0
- data/lib/utopia/{middleware/controller → controller}/variables.rb +36 -38
- data/lib/utopia/exception_handler.rb +79 -0
- data/lib/utopia/extensions/array.rb +2 -2
- data/lib/utopia/localization.rb +143 -0
- data/lib/utopia/mail_exceptions.rb +136 -0
- data/lib/utopia/middleware.rb +7 -22
- data/lib/utopia/path.rb +150 -60
- data/lib/utopia/redirector.rb +152 -0
- data/lib/utopia/{extensions/hash.rb → session.rb} +4 -6
- data/lib/utopia/session/encrypted_cookie.rb +46 -48
- data/lib/utopia/{middleware/directory_index.rb → session/lazy_hash.rb} +44 -27
- data/lib/utopia/static.rb +255 -0
- data/lib/utopia/tags/deferred.rb +12 -8
- data/lib/utopia/tags/environment.rb +18 -6
- data/lib/utopia/tags/node.rb +12 -8
- data/lib/utopia/tags/override.rb +12 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/.bowerrc +3 -0
- data/{lib/utopia/setup → setup}/Gemfile +1 -1
- data/setup/Rakefile +4 -0
- data/{lib/utopia/setup → setup}/cache/head/readme.txt +0 -0
- data/{lib/utopia/setup → setup}/cache/meta/readme.txt +0 -0
- data/setup/config.ru +64 -0
- data/{lib/utopia/setup → setup}/lib/readme.txt +0 -0
- data/{lib/utopia/setup → setup}/pages/_heading.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/_page.xnode +1 -1
- data/{lib/utopia/setup → setup}/pages/_static/icon.png +0 -0
- data/setup/pages/_static/site.css +70 -0
- data/{lib/utopia/setup → setup}/pages/errors/exception.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/errors/file-not-found.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/links.yaml +0 -0
- data/setup/pages/welcome/index.xnode +17 -0
- data/{lib/utopia/setup → setup}/public/readme.txt +0 -0
- data/spec/utopia/content/link_spec.rb +108 -0
- data/spec/utopia/content/links/foo/index.xnode +0 -0
- data/spec/utopia/content/links/foo/links.yaml +2 -0
- data/spec/utopia/content/links/foo/test.de.xnode +0 -0
- data/spec/utopia/content/links/foo/test.en.xnode +0 -0
- data/spec/utopia/content/links/links.yaml +9 -0
- data/spec/utopia/content/links/welcome.xnode +0 -0
- data/spec/utopia/content/localized/five/index.en.xnode +0 -0
- data/spec/utopia/content/localized/four/index.en.xnode +0 -0
- data/spec/utopia/content/localized/four/index.zh.xnode +0 -0
- data/spec/utopia/content/localized/four/links.yaml +4 -0
- data/spec/utopia/content/localized/links.yaml +16 -0
- data/spec/utopia/content/localized/one.xnode +0 -0
- data/spec/utopia/content/localized/three/index.xnode +0 -0
- data/spec/utopia/content/localized/two.en.xnode +0 -0
- data/spec/utopia/content/localized/two.zh.xnode +0 -0
- data/spec/utopia/content/node/ordered/first.xnode +0 -0
- data/spec/utopia/content/node/ordered/index.xnode +0 -0
- data/spec/utopia/content/node/ordered/links.yaml +4 -0
- data/spec/utopia/content/node/ordered/second.xnode +0 -0
- data/spec/utopia/content/node/related/foo.en.xnode +0 -0
- data/spec/utopia/content/node/related/foo.ja.xnode +0 -0
- data/spec/utopia/content/node/related/links.yaml +4 -0
- data/spec/utopia/content/node_spec.rb +63 -0
- data/spec/utopia/{middleware/content_spec.rb → content/processor_spec.rb} +34 -23
- data/spec/utopia/content_spec.rb +87 -0
- data/spec/utopia/content_spec.ru +10 -0
- data/spec/utopia/{middleware/controller_spec.rb → controller_spec.rb} +61 -16
- data/spec/utopia/controller_spec.ru +4 -0
- data/spec/utopia/extensions_spec.rb +6 -17
- data/spec/utopia/localization_spec.rb +60 -0
- data/spec/utopia/localization_spec.ru +11 -0
- data/{lib/utopia/tags.rb → spec/utopia/middleware_spec.rb} +8 -14
- data/spec/utopia/{middleware/content_root → pages}/_heading.xnode +0 -0
- data/spec/utopia/pages/content/_show-value.xnode +1 -0
- data/spec/utopia/pages/content/test-partial.xnode +1 -0
- data/spec/utopia/pages/controller/controller.rb +28 -0
- data/spec/utopia/pages/controller/index.xnode +1 -0
- data/spec/utopia/pages/controller/nested/controller.rb +4 -0
- data/spec/utopia/{middleware/content_root → pages}/index.xnode +0 -0
- data/spec/utopia/pages/localized.de.txt +1 -0
- data/spec/utopia/pages/localized.en.txt +1 -0
- data/spec/utopia/pages/localized.jp.txt +1 -0
- data/spec/utopia/pages/node/index.xnode +1 -0
- data/spec/utopia/pages/test.txt +1 -0
- data/spec/utopia/path_spec.rb +109 -0
- data/spec/utopia/rack_spec.rb +2 -0
- data/spec/utopia/session_spec.rb +82 -0
- data/spec/utopia/session_spec.ru +20 -0
- data/spec/utopia/spec_helper.rb +16 -0
- data/{lib/utopia/extensions/string.rb → spec/utopia/static_spec.rb} +24 -15
- data/spec/utopia/static_spec.ru +4 -0
- data/utopia.gemspec +3 -3
- metadata +138 -54
- data/lib/utopia/extensions/regexp.rb +0 -33
- data/lib/utopia/link.rb +0 -288
- data/lib/utopia/middleware/all.rb +0 -33
- data/lib/utopia/middleware/content.rb +0 -157
- data/lib/utopia/middleware/content/node.rb +0 -386
- data/lib/utopia/middleware/content/processor.rb +0 -123
- data/lib/utopia/middleware/controller.rb +0 -130
- data/lib/utopia/middleware/controller/action.rb +0 -121
- data/lib/utopia/middleware/controller/base.rb +0 -184
- data/lib/utopia/middleware/exception_handler.rb +0 -80
- data/lib/utopia/middleware/localization.rb +0 -147
- data/lib/utopia/middleware/localization/name.rb +0 -69
- data/lib/utopia/middleware/mail_exceptions.rb +0 -138
- data/lib/utopia/middleware/redirector.rb +0 -146
- data/lib/utopia/middleware/requester.rb +0 -126
- data/lib/utopia/middleware/static.rb +0 -295
- data/lib/utopia/setup.rb +0 -60
- data/lib/utopia/setup/config.ru +0 -47
- data/lib/utopia/setup/pages/_static/background.png +0 -0
- data/lib/utopia/setup/pages/_static/site.css +0 -48
- data/lib/utopia/setup/pages/welcome/index.xnode +0 -7
- data/lib/utopia/tag.rb +0 -105
- data/lib/utopia/tags/all.rb +0 -34
@@ -0,0 +1,128 @@
|
|
1
|
+
# Copyright, 2012, 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 'trenni/parser'
|
22
|
+
require 'trenni/strings'
|
23
|
+
|
24
|
+
require_relative 'tag'
|
25
|
+
|
26
|
+
module Utopia
|
27
|
+
class Content
|
28
|
+
class Processor
|
29
|
+
def self.parse_xml(xml_data, delegate)
|
30
|
+
processor = self.new(delegate)
|
31
|
+
|
32
|
+
processor.parse(xml_data)
|
33
|
+
end
|
34
|
+
|
35
|
+
class UnbalancedTagError < StandardError
|
36
|
+
def initialize(scanner, start_position, current_tag, closing_tag)
|
37
|
+
@scanner = scanner
|
38
|
+
@start_position = start_position
|
39
|
+
@current_tag = current_tag
|
40
|
+
@closing_tag = closing_tag
|
41
|
+
|
42
|
+
@starting_line = Trenni::Parser.line_at_offset(@scanner.string, @start_position)
|
43
|
+
@ending_line = Trenni::Parser.line_at_offset(@scanner.string, @scanner.pos)
|
44
|
+
end
|
45
|
+
|
46
|
+
attr :scanner
|
47
|
+
attr :start_position
|
48
|
+
attr :current_tag
|
49
|
+
attr :closing_tag
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"Unbalanced Tag Error. " \
|
53
|
+
"Line #{@starting_line[:line_number]}: #{@current_tag} has been closed by #{@closing_tag} on line #{@ending_line[:line_number]}!"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(delegate)
|
58
|
+
@delegate = delegate
|
59
|
+
@stack = []
|
60
|
+
|
61
|
+
@parser = Trenni::Parser.new(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(input)
|
65
|
+
@parser.parse(input)
|
66
|
+
|
67
|
+
unless @stack.empty?
|
68
|
+
current_tag, current_position = @stack.pop
|
69
|
+
|
70
|
+
raise UnbalancedTagError.new(@scanner, current_position, current_tag.name, 'EOF')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def begin_parse(scanner)
|
75
|
+
@scanner = scanner
|
76
|
+
end
|
77
|
+
|
78
|
+
def text(text)
|
79
|
+
@delegate.cdata(text)
|
80
|
+
end
|
81
|
+
|
82
|
+
def cdata(text)
|
83
|
+
@delegate.cdata(Trenni::Strings::to_html(text))
|
84
|
+
end
|
85
|
+
|
86
|
+
def comment(text)
|
87
|
+
@delegate.cdata("<!#{text}>")
|
88
|
+
end
|
89
|
+
|
90
|
+
def begin_tag(tag_name, begin_tag_type)
|
91
|
+
if begin_tag_type == :opened
|
92
|
+
@stack << [Tag.new(tag_name, {}), @scanner.pos]
|
93
|
+
else
|
94
|
+
current_tag, current_position = @stack.pop
|
95
|
+
|
96
|
+
if tag_name != current_tag.name
|
97
|
+
raise UnbalancedTagError.new(@scanner, current_position, current_tag.name, tag_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
@delegate.tag_end(current_tag)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def finish_tag(begin_tag_type, end_tag_type)
|
105
|
+
if begin_tag_type == :opened # <...
|
106
|
+
if end_tag_type == :closed # <.../>
|
107
|
+
cur, pos = @stack.pop
|
108
|
+
cur.closed = true
|
109
|
+
|
110
|
+
@delegate.tag_complete(cur)
|
111
|
+
elsif end_tag_type == :opened # <...>
|
112
|
+
cur, pos = @stack.last
|
113
|
+
|
114
|
+
@delegate.tag_begin(cur)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def attribute(name, value)
|
120
|
+
@stack.last[0].attributes[name] = value
|
121
|
+
end
|
122
|
+
|
123
|
+
def instruction(content)
|
124
|
+
cdata("<?#{content}?>")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Copyright, 2012, 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
|
+
module Utopia
|
22
|
+
class Content
|
23
|
+
# This represents an individual SGML tag, e.g. <a>, </a> or <a />, with attributes. Attribute values must be escaped.
|
24
|
+
class Tag
|
25
|
+
def == other
|
26
|
+
if Tag === other
|
27
|
+
[@name, @attributes, @closed] == [other.name, other.attributes, other.closed]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.closed(name, attributes = {})
|
32
|
+
tag = Tag.new(name, attributes)
|
33
|
+
tag.closed = true
|
34
|
+
|
35
|
+
return tag
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(name, attributes = {})
|
39
|
+
@name = name
|
40
|
+
@attributes = attributes
|
41
|
+
@closed = false
|
42
|
+
end
|
43
|
+
|
44
|
+
attr :name
|
45
|
+
attr :attributes
|
46
|
+
attr :closed, true
|
47
|
+
|
48
|
+
def [](key)
|
49
|
+
@attributes[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_html(content = nil, buffer = StringIO.new)
|
53
|
+
write_full_html(buffer, content)
|
54
|
+
|
55
|
+
return buffer.string
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_hash
|
59
|
+
@attributes
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s(content = nil)
|
63
|
+
buffer = StringIO.new
|
64
|
+
write_full_html(buffer, content)
|
65
|
+
return buffer.string
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_open_html(buffer, terminate = false)
|
69
|
+
buffer ||= StringIO.new
|
70
|
+
buffer.write "<#{name}"
|
71
|
+
|
72
|
+
@attributes.each do |key, value|
|
73
|
+
if value
|
74
|
+
buffer.write " #{key}=\"#{value}\""
|
75
|
+
else
|
76
|
+
buffer.write " #{key}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if terminate
|
81
|
+
buffer.write "/>"
|
82
|
+
else
|
83
|
+
buffer.write ">"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_close_html(buffer)
|
88
|
+
buffer.write "</#{name}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_full_html(buffer, content = nil)
|
92
|
+
if @closed && content == nil
|
93
|
+
write_open_html(buffer, true)
|
94
|
+
else
|
95
|
+
write_open_html(buffer)
|
96
|
+
buffer.write(content)
|
97
|
+
write_close_html(buffer)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# Copyright, 2012, 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 'path'
|
22
|
+
|
23
|
+
require_relative 'controller/variables'
|
24
|
+
require_relative 'controller/action'
|
25
|
+
require_relative 'controller/base'
|
26
|
+
|
27
|
+
class Rack::Request
|
28
|
+
def controller(&block)
|
29
|
+
if block_given?
|
30
|
+
env["utopia.controller"].instance_eval(&block)
|
31
|
+
else
|
32
|
+
env["utopia.controller"]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Utopia
|
38
|
+
class Controller
|
39
|
+
CONTROLLER_RB = "controller.rb".freeze
|
40
|
+
|
41
|
+
def initialize(app, options = {})
|
42
|
+
@app = app
|
43
|
+
@root = options[:root] || Utopia::default_root
|
44
|
+
|
45
|
+
@controllers = {}
|
46
|
+
|
47
|
+
@cache_controllers = options[:cache_controllers] || false
|
48
|
+
end
|
49
|
+
|
50
|
+
attr :app
|
51
|
+
|
52
|
+
def lookup_controller(path)
|
53
|
+
if @cache_controllers
|
54
|
+
return @controllers.fetch(path.to_s) do |key|
|
55
|
+
@controllers[key] = load_controller_file(path)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
return load_controller_file(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def load_controller_file(path)
|
63
|
+
uri_path = path
|
64
|
+
base_path = File.join(@root, uri_path.components)
|
65
|
+
|
66
|
+
controller_path = File.join(base_path, CONTROLLER_RB)
|
67
|
+
# puts "load_controller_file(#{path.inspect}) => #{controller_path}"
|
68
|
+
|
69
|
+
if File.exist?(controller_path)
|
70
|
+
klass = Class.new(Base)
|
71
|
+
|
72
|
+
# base_path is expected to be a string representing a filesystem path:
|
73
|
+
klass.const_set(:BASE_PATH, base_path)
|
74
|
+
|
75
|
+
# uri_path is expected to be an instance of Path:
|
76
|
+
klass.const_set(:URI_PATH, uri_path)
|
77
|
+
|
78
|
+
klass.const_set(:CONTROLLER, self)
|
79
|
+
|
80
|
+
$LOAD_PATH.unshift(base_path)
|
81
|
+
|
82
|
+
klass.class_eval(File.read(controller_path), controller_path)
|
83
|
+
|
84
|
+
$LOAD_PATH.delete(base_path)
|
85
|
+
|
86
|
+
return klass.new
|
87
|
+
else
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def invoke_controllers(variables, request, done = Set.new)
|
93
|
+
path = Path.create(request.path_info)
|
94
|
+
controller_path = Path.new
|
95
|
+
|
96
|
+
path.descend do |controller_path|
|
97
|
+
# puts "Invoke controller: #{controller_path}"
|
98
|
+
if controller = lookup_controller(controller_path)
|
99
|
+
# We only want to invoke controllers which have not already been invoked:
|
100
|
+
unless done.include? controller
|
101
|
+
# If we get throw :rewrite, location, the URL has been rewritten and we need to request again:
|
102
|
+
location = catch(:rewrite) do
|
103
|
+
# Invoke the controller and if it returns a result, send it back out:
|
104
|
+
if result = controller.process!(request, path)
|
105
|
+
return result
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if location
|
110
|
+
# Rewrite relative paths based on the controller's URI:
|
111
|
+
request.env['PATH_INFO'] = Path[location].expand(controller.class.uri_path).to_s
|
112
|
+
|
113
|
+
return invoke_controllers(variables, request, done)
|
114
|
+
end
|
115
|
+
|
116
|
+
done << controller
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# No controller gave a useful result:
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def call(env)
|
126
|
+
variables = (env["utopia.controller"] ||= Variables.new)
|
127
|
+
|
128
|
+
request = Rack::Request.new(env)
|
129
|
+
|
130
|
+
if result = invoke_controllers(variables, request)
|
131
|
+
return result
|
132
|
+
end
|
133
|
+
|
134
|
+
return @app.call(env)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Copyright, 2014, 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
|
+
module Utopia
|
22
|
+
class Controller
|
23
|
+
class Action < Hash
|
24
|
+
attr_accessor :callback
|
25
|
+
attr_accessor :options
|
26
|
+
|
27
|
+
def callback?
|
28
|
+
@callback != nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def indirect?
|
32
|
+
@options[:indirect]
|
33
|
+
end
|
34
|
+
|
35
|
+
def eql? other
|
36
|
+
super and self.callback.eql? other.callback and self.options.eql? other.options
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
[super, callback, options].hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def == other
|
44
|
+
super and (self.callback == other.callback) and (self.options == other.options)
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def append(path, index, actions = [])
|
50
|
+
# ** is greedy, it always matches if possible:
|
51
|
+
if match_all = self[:**]
|
52
|
+
# Match all remaining input:
|
53
|
+
actions << match_all if match_all.callback?
|
54
|
+
end
|
55
|
+
|
56
|
+
if index < path.size
|
57
|
+
name = path[index].to_sym
|
58
|
+
|
59
|
+
if match_name = self[name]
|
60
|
+
# Match the exact name:
|
61
|
+
match_name.append(path, index+1, actions)
|
62
|
+
end
|
63
|
+
|
64
|
+
if match_one = self[:*]
|
65
|
+
# Match one input:
|
66
|
+
match_one.append(path, index+1, actions)
|
67
|
+
end
|
68
|
+
else
|
69
|
+
# Got to end, matched completely:
|
70
|
+
actions << self if self.callback?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
public
|
75
|
+
|
76
|
+
# relative_path = 2014/mr-potato
|
77
|
+
# actions => {:** => A}
|
78
|
+
def select(relative_path)
|
79
|
+
actions = []
|
80
|
+
|
81
|
+
append(relative_path.reverse, 0, actions)
|
82
|
+
|
83
|
+
return actions
|
84
|
+
end
|
85
|
+
|
86
|
+
def define(path, options = {}, &callback)
|
87
|
+
current = self
|
88
|
+
|
89
|
+
path.reverse.each do |name|
|
90
|
+
current = (current[name.to_sym] ||= Action.new)
|
91
|
+
end
|
92
|
+
|
93
|
+
current.options = options
|
94
|
+
current.callback = callback
|
95
|
+
|
96
|
+
return current
|
97
|
+
end
|
98
|
+
|
99
|
+
def invoke!(controller, *arguments)
|
100
|
+
controller.instance_exec(*arguments, &@callback)
|
101
|
+
end
|
102
|
+
|
103
|
+
def inspect
|
104
|
+
if callback?
|
105
|
+
"<action " + super + ":#{callback.source_location}(#{options})>"
|
106
|
+
else
|
107
|
+
"<action " + super + ">"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|