utopia 1.7.1 → 1.8.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 +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
|