utopia 1.7.1 → 1.8.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 +2 -3
- data/README.md +142 -11
- data/benchmarks/string_vs_symbol.rb +12 -0
- data/lib/utopia/command.rb +16 -13
- data/lib/utopia/content.rb +1 -5
- data/lib/utopia/content/node.rb +9 -4
- data/lib/utopia/{extensions/rack.rb → content/response.rb} +33 -30
- data/lib/utopia/content/tag.rb +14 -17
- data/lib/utopia/content/transaction.rb +19 -17
- data/lib/utopia/controller.rb +29 -8
- data/lib/utopia/controller/actions.rb +148 -0
- data/lib/utopia/controller/base.rb +9 -49
- data/lib/utopia/controller/respond.rb +1 -1
- data/lib/utopia/controller/rewrite.rb +9 -1
- data/lib/utopia/controller/variables.rb +1 -0
- data/lib/utopia/localization.rb +4 -1
- data/lib/utopia/middleware.rb +0 -2
- data/lib/utopia/path.rb +9 -0
- data/lib/utopia/path/matcher.rb +0 -1
- data/lib/utopia/redirection.rb +3 -2
- data/lib/utopia/session.rb +119 -2
- data/lib/utopia/session/lazy_hash.rb +1 -3
- data/lib/utopia/setup.rb +73 -0
- data/lib/utopia/static.rb +9 -2
- data/lib/utopia/version.rb +1 -1
- data/setup/examples/wiki/controller.rb +41 -0
- data/setup/examples/wiki/edit.xnode +15 -0
- data/setup/examples/wiki/index.xnode +10 -0
- data/setup/examples/wiki/welcome/content.md +3 -0
- data/setup/server/config/environment.yaml +1 -0
- data/setup/server/git/hooks/post-receive +4 -5
- data/setup/site/Gemfile +5 -0
- data/setup/site/config.ru +2 -1
- data/setup/site/config/environment.rb +5 -17
- data/setup/site/pages/_page.xnode +4 -2
- data/setup/site/pages/links.yaml +1 -1
- data/setup/site/pages/welcome/index.xnode +33 -15
- data/setup/site/public/_static/site.css +72 -4
- data/setup/site/tasks/utopia.rake +8 -0
- data/spec/utopia/{rack_spec.rb → content/response_spec.rb} +12 -19
- data/spec/utopia/content_spec.rb +2 -3
- data/spec/utopia/controller/{action_spec.rb → actions_spec.rb} +18 -32
- data/spec/utopia/controller/middleware_spec.rb +10 -10
- data/spec/utopia/controller/middleware_spec/controller/controller.rb +3 -3
- data/spec/utopia/controller/middleware_spec/controller/nested/controller.rb +1 -1
- data/spec/utopia/controller/middleware_spec/redirect/controller.rb +1 -1
- data/spec/utopia/controller/respond_spec.rb +3 -2
- data/spec/utopia/controller/respond_spec/api/controller.rb +2 -2
- data/spec/utopia/controller/respond_spec/errors/controller.rb +1 -1
- data/spec/utopia/controller/rewrite_spec.rb +1 -1
- data/spec/utopia/controller/sequence_spec.rb +12 -16
- data/spec/utopia/exceptions/handler_spec/controller.rb +2 -2
- data/spec/utopia/performance_spec/config.ru +1 -0
- data/spec/utopia/session_spec.rb +34 -1
- data/spec/utopia/session_spec.ru +3 -3
- data/spec/utopia/setup_spec.rb +2 -2
- data/utopia.gemspec +2 -2
- metadata +18 -12
- data/lib/utopia/controller/action.rb +0 -116
- data/lib/utopia/session/encrypted_cookie.rb +0 -118
@@ -0,0 +1 @@
|
|
1
|
+
RACK_ENV: production
|
@@ -41,7 +41,7 @@ WHOAMI = `whoami`.chomp!
|
|
41
41
|
|
42
42
|
# We should find out if we need to use sudo or not:
|
43
43
|
SUDO = if WHOAMI != DEPLOY_USER
|
44
|
-
[
|
44
|
+
['sudo', '-u', DEPLOY_USER]
|
45
45
|
end
|
46
46
|
|
47
47
|
CommandFailure = Class.new(StandardError)
|
@@ -72,9 +72,8 @@ Dir.chdir(GIT_WORK_TREE) do
|
|
72
72
|
|
73
73
|
if File.exist? 'Rakefile'
|
74
74
|
sudo %W{bundle exec rake deploy}
|
75
|
+
|
76
|
+
puts "Restarting server..."
|
77
|
+
sudo %W{bundle exec rake restart}
|
75
78
|
end
|
76
|
-
|
77
|
-
puts "Restarting server..."
|
78
|
-
sudo %W{mkdir -p tmp} unless File.exist?('tmp')
|
79
|
-
sudo %W{touch tmp/restart.txt}
|
80
79
|
end
|
data/setup/site/Gemfile
CHANGED
data/setup/site/config.ru
CHANGED
@@ -32,7 +32,8 @@ use Utopia::Localization,
|
|
32
32
|
:nonlocalized => ['/_static/', '/_cache/']
|
33
33
|
|
34
34
|
use Utopia::Controller,
|
35
|
-
cache_controllers: (RACK_ENV == :production)
|
35
|
+
cache_controllers: (RACK_ENV == :production),
|
36
|
+
base: Utopia::Controller::Base
|
36
37
|
|
37
38
|
use Utopia::Static
|
38
39
|
|
@@ -1,20 +1,8 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
Encoding.default_internal = Encoding::UTF_8
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.setup
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
if File.exist? environment_path
|
9
|
-
require 'yaml'
|
10
|
-
ENV.update(YAML.load_file(environment_path))
|
11
|
-
end
|
5
|
+
require 'utopia/setup'
|
6
|
+
Utopia.setup
|
12
7
|
|
13
|
-
|
14
|
-
RACK_ENV = ENV.fetch('RACK_ENV', :development).to_sym unless defined?(RACK_ENV)
|
15
|
-
|
16
|
-
# Allow loading library code from lib directory:
|
17
|
-
$LOAD_PATH << File.expand_path('../lib', __dir__)
|
18
|
-
|
19
|
-
# Load utopia framework:
|
20
|
-
require 'utopia'
|
8
|
+
RACK_ENV = ENV.fetch('RACK_ENV', :development).to_sym unless defined? RACK_ENV
|
@@ -10,14 +10,16 @@
|
|
10
10
|
<title>Utopia</title>
|
11
11
|
<?r end ?>
|
12
12
|
|
13
|
+
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous" />
|
14
|
+
|
13
15
|
<link rel="icon" type="image/png" href="/_static/icon.png" />
|
14
16
|
<link rel="stylesheet" href="/_static/site.css" type="text/css" media="screen" />
|
15
17
|
</head>
|
16
18
|
|
17
19
|
<body class="#{attributes[:class]}">
|
18
|
-
<
|
20
|
+
<header>
|
19
21
|
<img src="/_static/utopia.svg" />
|
20
|
-
</
|
22
|
+
</header>
|
21
23
|
|
22
24
|
<div id="page">
|
23
25
|
<content />
|
data/setup/site/pages/links.yaml
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
errors:
|
2
|
-
display: false
|
2
|
+
display: false
|
@@ -1,17 +1,35 @@
|
|
1
1
|
<page class="front">
|
2
|
-
<heading>Welcome to Utopia
|
2
|
+
<heading>Welcome to Utopia...</heading>
|
3
3
|
|
4
|
-
<
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
4
|
+
<section class="features">
|
5
|
+
<div>
|
6
|
+
<i class="fa fa-bolt"></i>
|
7
|
+
<h2>Low latency and high throughput</h2>
|
8
|
+
<p>Utopia has been carefully tuned for low-latency and algorithmic efficiency. During development, requests reload the entire stack, while in production everything is cached using <a href="http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Map.html">thread-safe data-structures</a>. Serve thousands of requests per second with ease.</p>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div>
|
12
|
+
<i class="fa fa-cubes"></i>
|
13
|
+
<h2>Modular code and structure</h2>
|
14
|
+
<p>Utopia provides independently useful <a href="https://github.com/rack/rack">Rack</a> middleware and has been designed with simplicify in mind. Several fully-featured webapps and a ton of commercial websites have guided the development of the Utopia stack. It is capable of handling a diverse range of requirements.</p>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div>
|
18
|
+
<i class="fa fa-code"></i>
|
19
|
+
<h2>Well tested and maintained</h2>
|
20
|
+
<p>Utopia comprises a <a href="https://github.com/ioquatix/utopia">core gem</a> and several supporting libraries, the main ones being <a href="https://github.com/ioquatix/trenni">trenni</a> for templates and parsing, and <a href="https://github.com/ioquatix/http-accept">http-accept</a> for HTTP header processing. Together, these gems have over 90% test coverage.</p>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<div>
|
24
|
+
<i class="fa fa-server"></i>
|
25
|
+
<h2>Batteries included</h2>
|
26
|
+
<p>Utopia includes both a recommended development model and a server deployment model out of the box. This is exposed via the <code>utopia</code> command. We recommend use of <a href="https://www.nginx.com">Nginx</a> and <a href="https://www.phusionpassenger.com">Passenger</a> for server side deployment.</p>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div>
|
30
|
+
<i class="fa fa-globe"></i>
|
31
|
+
<h2>Standards compliant localization</h2>
|
32
|
+
<p>Utopia supports the <code>Accept-Language</code> header and transparently selects the correct view to render. Build multi-lingual websites and webapps easily: translate content incrementally as required, or not at all.</p>
|
33
|
+
</div>
|
34
|
+
</section>
|
35
|
+
</page>
|
@@ -2,11 +2,13 @@
|
|
2
2
|
html {
|
3
3
|
font-family: "PT Sans", Verdana, Helvetica, Arial, sans-serif;
|
4
4
|
}
|
5
|
+
|
5
6
|
@media (min-width: 48em) {
|
6
7
|
html {
|
7
8
|
font-size: 16px;
|
8
9
|
}
|
9
10
|
}
|
11
|
+
|
10
12
|
@media (min-width: 58em) {
|
11
13
|
html {
|
12
14
|
font-size: 20px;
|
@@ -20,7 +22,7 @@ body {
|
|
20
22
|
background-color: #fafafa;
|
21
23
|
}
|
22
24
|
|
23
|
-
|
25
|
+
body > header {
|
24
26
|
margin: 1em 0 1em 0;
|
25
27
|
|
26
28
|
background-color: white;
|
@@ -30,7 +32,7 @@ body {
|
|
30
32
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
31
33
|
}
|
32
34
|
|
33
|
-
|
35
|
+
body > header img {
|
34
36
|
display: block;
|
35
37
|
margin: auto;
|
36
38
|
height: 4em;
|
@@ -45,7 +47,6 @@ p strong {
|
|
45
47
|
}
|
46
48
|
|
47
49
|
h1, h2, h3, h4, h5, h6 {
|
48
|
-
color: #333;
|
49
50
|
margin: 2em 1em 1em 1em;
|
50
51
|
color: #4E8DD9;
|
51
52
|
}
|
@@ -62,7 +63,7 @@ a:hover {
|
|
62
63
|
color: #55c;
|
63
64
|
}
|
64
65
|
|
65
|
-
p, dl, h3 {
|
66
|
+
p, ul, ol, dl, h3 {
|
66
67
|
margin: 2em;
|
67
68
|
}
|
68
69
|
|
@@ -85,3 +86,70 @@ body.front h1 {
|
|
85
86
|
|
86
87
|
text-align: center;
|
87
88
|
}
|
89
|
+
|
90
|
+
footer {
|
91
|
+
text-align: right;
|
92
|
+
margin: 2rem;
|
93
|
+
}
|
94
|
+
|
95
|
+
section.features {
|
96
|
+
display: flex;
|
97
|
+
flex-wrap: wrap;
|
98
|
+
justify-content: space-around;
|
99
|
+
|
100
|
+
margin: 1rem;
|
101
|
+
}
|
102
|
+
|
103
|
+
section.features > div {
|
104
|
+
box-sizing: border-box;
|
105
|
+
|
106
|
+
flex-basis: 20rem;
|
107
|
+
flex-grow: 1;
|
108
|
+
|
109
|
+
color: #171e42;
|
110
|
+
margin: 1rem;
|
111
|
+
padding: 1rem;
|
112
|
+
|
113
|
+
padding-left: 3rem;
|
114
|
+
|
115
|
+
position: relative;
|
116
|
+
}
|
117
|
+
|
118
|
+
section.features > div i {
|
119
|
+
position: absolute;
|
120
|
+
left: 0rem;
|
121
|
+
|
122
|
+
font-size: 1.5rem;
|
123
|
+
text-align: center;
|
124
|
+
|
125
|
+
width: 3rem;
|
126
|
+
color: #fafafa;
|
127
|
+
text-shadow: 0px 0px 1px #000;
|
128
|
+
}
|
129
|
+
|
130
|
+
section.features p {
|
131
|
+
margin: 0;
|
132
|
+
maring-bottom: 1rem;
|
133
|
+
font-size: 80%;
|
134
|
+
}
|
135
|
+
|
136
|
+
section.features h2 {
|
137
|
+
margin: 0;
|
138
|
+
font-size: 1.1rem;
|
139
|
+
padding: 0;
|
140
|
+
}
|
141
|
+
|
142
|
+
form fieldset {
|
143
|
+
border: 0;
|
144
|
+
}
|
145
|
+
|
146
|
+
form fieldset textarea {
|
147
|
+
box-sizing: border-box;
|
148
|
+
|
149
|
+
width: 100%;
|
150
|
+
height: 10rem;
|
151
|
+
}
|
152
|
+
|
153
|
+
form fieldset.footer {
|
154
|
+
text-align: right;
|
155
|
+
}
|
@@ -4,6 +4,14 @@ task :deploy do
|
|
4
4
|
# This task is typiclly run after the site is updated but before the server is restarted.
|
5
5
|
end
|
6
6
|
|
7
|
+
desc 'Restart the application server'
|
8
|
+
task :restart do
|
9
|
+
# This task is run after the deployment task above.
|
10
|
+
if passenger_config = `which passenger-config`.chomp!
|
11
|
+
sh(passenger_config, 'restart-app', '--ignore-passenger-not-running', File.dirname(__dir__))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
7
15
|
desc 'Set up the environment for running your web application'
|
8
16
|
task :environment do
|
9
17
|
require_relative '../config/environment'
|
@@ -18,42 +18,35 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'utopia/
|
22
|
-
require 'utopia/extensions/rack'
|
21
|
+
require 'utopia/content/response'
|
23
22
|
|
24
|
-
module Utopia::
|
25
|
-
describe
|
23
|
+
module Utopia::Content::ResponseSpec
|
24
|
+
describe Utopia::Content::Response do
|
26
25
|
it "should specify not to cache content" do
|
27
|
-
|
26
|
+
subject.cache!(1000)
|
27
|
+
subject.do_not_cache!
|
28
28
|
|
29
|
-
|
30
|
-
response.do_not_cache!
|
29
|
+
expect(subject.headers['Cache-Control']).to be == "no-cache, must-revalidate"
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
expires_header = Time.parse(response['Expires'])
|
31
|
+
expires_header = Time.parse(subject.headers['Expires'])
|
35
32
|
expect(expires_header).to be <= Time.now
|
36
33
|
end
|
37
34
|
|
38
35
|
it "should specify to cache content" do
|
39
|
-
response = Rack::Response.new
|
40
|
-
|
41
36
|
duration = 120
|
42
37
|
expires = Time.now + 100 # At least this far into the future
|
43
|
-
|
38
|
+
subject.cache!(duration)
|
44
39
|
|
45
|
-
expect(
|
40
|
+
expect(subject.headers['Cache-Control']).to be == "public, max-age=120"
|
46
41
|
|
47
|
-
expires_header = Time.parse(
|
42
|
+
expires_header = Time.parse(subject.headers['Expires'])
|
48
43
|
expect(expires_header).to be >= expires
|
49
44
|
end
|
50
45
|
|
51
46
|
it "should set content type" do
|
52
|
-
|
53
|
-
|
54
|
-
response.content_type! "text/html"
|
47
|
+
subject.content_type! "text/html"
|
55
48
|
|
56
|
-
expect(
|
49
|
+
expect(subject.headers['Content-Type']).to be == "text/html"
|
57
50
|
end
|
58
51
|
end
|
59
52
|
end
|
data/spec/utopia/content_spec.rb
CHANGED
@@ -80,9 +80,8 @@ module Utopia::ContentSpec
|
|
80
80
|
node = content.lookup_node(path)
|
81
81
|
expect(node).to be_kind_of Utopia::Content::Node
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
expect(output.string).to be == '<h1>Hello World</h1>'
|
83
|
+
status, headers, body = node.process!({}, {})
|
84
|
+
expect(body.join).to be == '<h1>Hello World</h1>'
|
86
85
|
end
|
87
86
|
|
88
87
|
it "should fetch template and use cache" do
|
@@ -22,54 +22,40 @@
|
|
22
22
|
|
23
23
|
require 'utopia/controller'
|
24
24
|
|
25
|
-
module Utopia::Controller::
|
26
|
-
describe Utopia::Controller::Action do
|
27
|
-
it "
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
expect(a).to be == b
|
33
|
-
expect(a.hash).to be == b.hash
|
34
|
-
expect(a).to be_eql b
|
35
|
-
|
36
|
-
expect(a).to_not be == c
|
37
|
-
expect(a.hash).to_not be == c.hash
|
38
|
-
expect(a).to_not be_eql c
|
25
|
+
module Utopia::Controller::ActionsSpec
|
26
|
+
describe Utopia::Controller::Actions::Action do
|
27
|
+
it "can be a hash key" do
|
28
|
+
expect(subject).to be == subject
|
29
|
+
expect(subject.hash).to be == subject.hash
|
30
|
+
expect(subject).to be_eql subject
|
39
31
|
end
|
40
32
|
|
41
33
|
it "should resolve callbacks" do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
indirect_action = actions.define(['**']) {puts 'indirect_action'}
|
46
|
-
indirect_named_action = actions.define(['**', 'r']) {puts 'indirect_named_action'}
|
34
|
+
specific_action = subject.define(['a', 'b', 'c']) {puts 'specific_action'}
|
35
|
+
indirect_action = subject.define(['**']) {puts 'indirect_action'}
|
36
|
+
indirect_named_action = subject.define(['**', 'r']) {puts 'indirect_named_action'}
|
47
37
|
|
48
38
|
expect(specific_action).to_not be == indirect_action
|
49
39
|
expect(indirect_action).to_not be == indirect_named_action
|
50
40
|
|
51
|
-
expect(
|
52
|
-
expect(
|
41
|
+
expect(subject.matching(['a', 'b', 'c'])).to be == [indirect_action, specific_action]
|
42
|
+
expect(subject.matching(['q'])).to be == [indirect_action]
|
53
43
|
|
54
|
-
expect(
|
55
|
-
expect(
|
44
|
+
expect(subject.matching(['q', 'r'])).to be == [indirect_action, indirect_named_action]
|
45
|
+
expect(subject.matching(['q', 'r', 's'])).to be == [indirect_action]
|
56
46
|
end
|
57
47
|
|
58
48
|
it "should be greedy matching" do
|
59
|
-
|
49
|
+
greedy_action = subject.define(['**', 'r']) {puts 'greedy_action'}
|
60
50
|
|
61
|
-
|
62
|
-
|
63
|
-
expect(actions.select(['g', 'r'])).to be_include greedy_action
|
64
|
-
expect(actions.select(['r'])).to be_include greedy_action
|
51
|
+
expect(subject.matching(['g', 'r'])).to be_include greedy_action
|
52
|
+
expect(subject.matching(['r'])).to be_include greedy_action
|
65
53
|
end
|
66
54
|
|
67
55
|
it "should match patterns" do
|
68
|
-
|
69
|
-
|
70
|
-
variable_action = actions.define(['*', 'summary', '*']) {puts 'variable_action'}
|
56
|
+
variable_action = subject.define(['*', 'summary', '*']) {puts 'variable_action'}
|
71
57
|
|
72
|
-
expect(
|
58
|
+
expect(subject.matching(['10', 'summary', '20'])).to be_include variable_action
|
73
59
|
end
|
74
60
|
end
|
75
61
|
end
|
@@ -37,28 +37,28 @@ module Utopia::Controller::MiddlewareSpec
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it "should successfully call the controller method" do
|
40
|
-
get "/controller/
|
40
|
+
get "/controller/flat"
|
41
41
|
|
42
42
|
expect(last_response.status).to be == 200
|
43
|
-
expect(last_response.body).to be == '
|
43
|
+
expect(last_response.body).to be == 'flat'
|
44
44
|
end
|
45
45
|
|
46
|
-
it "should
|
47
|
-
get "/controller/
|
46
|
+
it "should invoke controller method from the top level" do
|
47
|
+
get "/controller/hello-world"
|
48
48
|
|
49
49
|
expect(last_response.status).to be == 200
|
50
50
|
expect(last_response.body).to be == 'Hello World'
|
51
51
|
end
|
52
|
-
|
53
|
-
it "should
|
54
|
-
get "/controller/
|
52
|
+
|
53
|
+
it "should invoke the controller method with a nested path" do
|
54
|
+
get "/controller/nested/hello-world"
|
55
55
|
|
56
56
|
expect(last_response.status).to be == 200
|
57
|
-
expect(last_response.body).to be == '
|
57
|
+
expect(last_response.body).to be == 'Hello World'
|
58
58
|
end
|
59
59
|
|
60
|
-
it "
|
61
|
-
get "/controller/
|
60
|
+
it "shouldn't call the nested controller method" do
|
61
|
+
get "/controller/nested/flat"
|
62
62
|
|
63
63
|
expect(last_response.status).to be == 404
|
64
64
|
end
|