utopia 0.12.6 → 1.0.0
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.
- 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
|