utopia 1.4.0 → 1.5.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/.gitignore +1 -0
- data/.travis.yml +5 -1
- data/Gemfile +3 -0
- data/README.md +11 -0
- data/benchmarks/hash_vs_openstruct.rb +52 -0
- data/benchmarks/struct_vs_class.rb +89 -0
- data/bin/utopia +4 -5
- data/lib/utopia.rb +1 -0
- data/lib/utopia/content.rb +24 -15
- data/lib/utopia/content/node.rb +69 -3
- data/lib/utopia/content/processor.rb +32 -5
- data/lib/utopia/content/transaction.rb +138 -147
- data/lib/utopia/content_length.rb +50 -0
- data/lib/utopia/controller.rb +4 -0
- data/lib/utopia/controller/variables.rb +1 -1
- data/lib/utopia/http.rb +2 -0
- data/lib/utopia/localization.rb +4 -8
- data/lib/utopia/path.rb +13 -13
- data/lib/utopia/static.rb +25 -14
- data/lib/utopia/tags/environment.rb +1 -1
- data/lib/utopia/tags/override.rb +1 -1
- data/lib/utopia/version.rb +1 -1
- data/setup/server/git/hooks/post-receive +32 -24
- data/setup/site/Gemfile +8 -0
- data/setup/site/Rakefile +29 -6
- data/setup/site/config.ru +8 -8
- data/setup/site/pages/_heading.xnode +1 -1
- data/setup/site/pages/_page.xnode +2 -2
- data/spec/utopia/content_spec.rb +3 -3
- data/spec/utopia/controller/sequence_spec.rb +1 -1
- data/spec/utopia/controller/variables_spec.rb +1 -1
- data/spec/utopia/pages/node/index.xnode +1 -1
- data/spec/utopia/performance_spec.rb +90 -0
- data/spec/utopia/performance_spec/cache/head/readme.txt +1 -0
- data/spec/utopia/performance_spec/cache/meta/readme.txt +1 -0
- data/spec/utopia/performance_spec/config.ru +39 -0
- data/spec/utopia/performance_spec/lib/readme.txt +1 -0
- data/spec/utopia/performance_spec/pages/_heading.xnode +2 -0
- data/spec/utopia/performance_spec/pages/_page.xnode +26 -0
- data/spec/utopia/performance_spec/pages/api/controller.rb +7 -0
- data/spec/utopia/performance_spec/pages/errors/exception.xnode +5 -0
- data/spec/utopia/performance_spec/pages/errors/file-not-found.xnode +5 -0
- data/spec/utopia/performance_spec/pages/links.yaml +2 -0
- data/spec/utopia/performance_spec/pages/welcome/index.xnode +17 -0
- data/spec/utopia/rack_helper.rb +5 -2
- data/spec/utopia/setup_spec.rb +93 -0
- data/utopia.gemspec +1 -1
- metadata +34 -5
- data/lib/utopia/mail_exceptions.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 452905bf411c5f82c5fe02c778c50a27b25234cb
|
4
|
+
data.tar.gz: 43df0cc23493c453bddf7d19b58b02e65bb15619
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ab0769846592da4bb8ce571d48cfc7465d3b773b64739781bae3fd4c6900603d162ad0a7e72d49ad049ca011858256a87191d9d13a9027fc703934bccf2224f
|
7
|
+
data.tar.gz: 2988e596315c47ee2026d8fe5f64d075bb3b99e3fe3fdf35e3ab0217716cee83cb9f472f16dface2e2526d4c086b21fe9a7732f1f535126e3ebfc47667094c21
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
|
+
before_install:
|
4
|
+
# For testing purposes:
|
5
|
+
- git config --global user.email "you@example.com"
|
6
|
+
- git config --global user.name "Your Name"
|
3
7
|
rvm:
|
4
8
|
- 2.1.8
|
5
9
|
- 2.2.4
|
6
10
|
- 2.3.0
|
7
11
|
- ruby-head
|
8
12
|
- rbx-2
|
9
|
-
env: COVERAGE=true
|
13
|
+
env: COVERAGE=true BENCHMARK=true
|
10
14
|
matrix:
|
11
15
|
allow_failures:
|
12
16
|
- rvm: rbx-2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -74,6 +74,17 @@ Then, Nginx is configured like so:
|
|
74
74
|
rewrite ^ http://www.example.com$uri permanent;
|
75
75
|
}
|
76
76
|
|
77
|
+
#### Compression
|
78
|
+
|
79
|
+
We suggest [enabling gzip compression](https://zoompf.com/blog/2012/02/lose-the-wait-http-compression):
|
80
|
+
|
81
|
+
gzip on;
|
82
|
+
gzip_vary on;
|
83
|
+
gzip_comp_level 6;
|
84
|
+
gzip_http_version 1.1;
|
85
|
+
gzip_proxied any;
|
86
|
+
gzip_types text/* image/svg+xml application/json application/javascript;
|
87
|
+
|
77
88
|
## Usage
|
78
89
|
|
79
90
|
Utopia builds on top of Rack with the following middleware:
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
# This benchmark compares accessing an instance variable vs accessing a struct member (via a function). The actual method dispatch is about 25% slower.
|
5
|
+
|
6
|
+
puts "Ruby #{RUBY_VERSION} at #{Time.now}"
|
7
|
+
|
8
|
+
NAME = "Test Name"
|
9
|
+
EMAIL = "test@example.org"
|
10
|
+
|
11
|
+
test = nil
|
12
|
+
|
13
|
+
class ObjectHash
|
14
|
+
def []= key, value
|
15
|
+
instance_variable_set(key, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def [] key
|
19
|
+
instance_variable_get(key)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# There IS a measuarble difference:
|
24
|
+
Benchmark.ips do |x|
|
25
|
+
x.report("Hash") do |i|
|
26
|
+
i.times do
|
27
|
+
p = {name: NAME, email: EMAIL}
|
28
|
+
|
29
|
+
test = p[:name] + p[:email]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report("OpenStruct") do |i|
|
34
|
+
i.times do
|
35
|
+
p = OpenStruct.new(name: NAME, email: EMAIL)
|
36
|
+
|
37
|
+
test = p.name + p.email
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report("ObjectHash") do |i|
|
42
|
+
i.times do
|
43
|
+
o = ObjectHash.new
|
44
|
+
o[:@name] = NAME
|
45
|
+
o[:@email] = EMAIL
|
46
|
+
|
47
|
+
test = o[:@name] + o[:@email]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
x.compare!
|
52
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
|
3
|
+
# This benchmark compares accessing an instance variable vs accessing a struct member (via a function). The actual method dispatch is about 25% slower.
|
4
|
+
|
5
|
+
puts "Ruby #{RUBY_VERSION} at #{Time.now}"
|
6
|
+
|
7
|
+
ItemsStruct = Struct.new(:items) do
|
8
|
+
def initialize
|
9
|
+
super []
|
10
|
+
end
|
11
|
+
|
12
|
+
def push_me_pull_you(value = :x)
|
13
|
+
items = self.items
|
14
|
+
|
15
|
+
items << value
|
16
|
+
items.pop
|
17
|
+
end
|
18
|
+
|
19
|
+
def empty?
|
20
|
+
self.items.empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ItemsClass
|
25
|
+
def initialize
|
26
|
+
@items = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def push_me_pull_you(value = :x)
|
30
|
+
items = @items
|
31
|
+
|
32
|
+
items << value
|
33
|
+
items.pop
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty?
|
37
|
+
@items.empty?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# There IS a measuarble difference:
|
42
|
+
Benchmark.ips do |x|
|
43
|
+
x.report("Struct#empty?") do |times|
|
44
|
+
i = 0
|
45
|
+
instance = ItemsStruct.new
|
46
|
+
|
47
|
+
while i < times
|
48
|
+
break unless instance.empty?
|
49
|
+
i += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
x.report("Class#empty?") do |times|
|
54
|
+
i = 0
|
55
|
+
instance = ItemsClass.new
|
56
|
+
|
57
|
+
while i < times
|
58
|
+
break unless instance.empty?
|
59
|
+
i += 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
x.compare!
|
64
|
+
end
|
65
|
+
|
66
|
+
# This shows that in the presence of additional work, the difference is neglegible.
|
67
|
+
Benchmark.ips do |x|
|
68
|
+
x.report("Struct#push_me_pull_you") do |times|
|
69
|
+
i = 0
|
70
|
+
a = A.new
|
71
|
+
|
72
|
+
while i < times
|
73
|
+
a.push_me_pull_you(i)
|
74
|
+
i += 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
x.report("Class#push_me_pull_you") do |times|
|
79
|
+
i = 0
|
80
|
+
b = B.new
|
81
|
+
|
82
|
+
while i < times
|
83
|
+
b.push_me_pull_you(i)
|
84
|
+
i += 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
x.compare!
|
89
|
+
end
|
data/bin/utopia
CHANGED
@@ -20,14 +20,11 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
-
require 'rubygems'
|
24
|
-
require 'rake'
|
25
|
-
|
26
23
|
require_relative '../lib/utopia/version'
|
27
24
|
|
25
|
+
require 'rake'
|
28
26
|
require 'fileutils'
|
29
27
|
require 'find'
|
30
|
-
require 'rake'
|
31
28
|
|
32
29
|
$app = Rake.application = Rake::Application.new
|
33
30
|
$app.init(File.basename($0))
|
@@ -61,7 +58,7 @@ desc "Create a local utopia instance which includes a basic website template.\n"
|
|
61
58
|
task :create do
|
62
59
|
destination_root = File.expand_path(ARGV.last || '.', Dir.getwd)
|
63
60
|
|
64
|
-
$stderr.puts "Setting up initial site in #{destination_root}..."
|
61
|
+
$stderr.puts "Setting up initial site in #{destination_root} for Utopia v#{Utopia::VERSION}..."
|
65
62
|
|
66
63
|
Setup::Site::DIRECTORIES.each do |directory|
|
67
64
|
FileUtils.mkdir_p(File.join(destination_root, directory))
|
@@ -93,6 +90,8 @@ task :create do
|
|
93
90
|
end
|
94
91
|
|
95
92
|
Dir.chdir(destination_root) do
|
93
|
+
puts "Setting up site in #{destination_root}..."
|
94
|
+
|
96
95
|
if `which bundle`.strip != ''
|
97
96
|
puts "Generating initial package list with bundle..."
|
98
97
|
sh("bundle", "install", "--quiet")
|
data/lib/utopia.rb
CHANGED
@@ -26,6 +26,7 @@ require_relative 'utopia/localization'
|
|
26
26
|
require_relative 'utopia/exceptions'
|
27
27
|
require_relative 'utopia/redirection'
|
28
28
|
require_relative 'utopia/static'
|
29
|
+
require_relative 'utopia/content_length'
|
29
30
|
|
30
31
|
require_relative 'utopia/tags/deferred'
|
31
32
|
require_relative 'utopia/tags/environment'
|
data/lib/utopia/content.rb
CHANGED
@@ -57,22 +57,18 @@ module Utopia
|
|
57
57
|
|
58
58
|
attr :root
|
59
59
|
|
60
|
-
def
|
60
|
+
def fetch_template(path)
|
61
61
|
if @template_cache
|
62
62
|
@template_cache.fetch_or_store(path.to_s) do
|
63
|
-
Trenni::Template.
|
63
|
+
Trenni::Template.load_file(path)
|
64
64
|
end
|
65
65
|
else
|
66
|
-
Trenni::Template.
|
66
|
+
Trenni::Template.load_file(path)
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
#
|
71
|
-
def
|
72
|
-
if @tags.key? name
|
73
|
-
return @tags[name]
|
74
|
-
end
|
75
|
-
|
70
|
+
# This function looks up a named tag in a given path. It's a hotspot and needs improvement.
|
71
|
+
private def fetch_tag(name, parent_path)
|
76
72
|
if String === name && name.index('/')
|
77
73
|
name = Path.create(name)
|
78
74
|
end
|
@@ -84,26 +80,39 @@ module Utopia
|
|
84
80
|
else
|
85
81
|
name_path = name + XNODE_EXTENSION
|
86
82
|
end
|
87
|
-
|
88
|
-
parent_path.
|
89
|
-
|
83
|
+
|
84
|
+
components = parent_path.components.dup
|
85
|
+
|
86
|
+
while components.any?
|
87
|
+
tag_path = File.join(root, components, name_path)
|
90
88
|
|
91
89
|
if File.exist? tag_path
|
92
|
-
return Node.new(self,
|
90
|
+
return Node.new(self, Path[components] + name, parent_path + name, tag_path)
|
93
91
|
end
|
94
92
|
|
95
93
|
if String === name_path
|
96
|
-
tag_path = File.join(root,
|
94
|
+
tag_path = File.join(root, components, '_' + name_path)
|
97
95
|
|
98
96
|
if File.exist? tag_path
|
99
|
-
return Node.new(self,
|
97
|
+
return Node.new(self, Path[components] + name, parent_path + name, tag_path)
|
100
98
|
end
|
101
99
|
end
|
100
|
+
|
101
|
+
components.pop
|
102
102
|
end
|
103
103
|
|
104
104
|
return nil
|
105
105
|
end
|
106
106
|
|
107
|
+
# Look up a named tag such as <entry />
|
108
|
+
def lookup_tag(name, parent_path)
|
109
|
+
if @tags.key? name
|
110
|
+
return @tags[name]
|
111
|
+
end
|
112
|
+
|
113
|
+
return fetch_tag(name, parent_path)
|
114
|
+
end
|
115
|
+
|
107
116
|
# The request_path is an absolute uri path, e.g. /foo/bar. If an xnode file exists on disk for this exact path, it is instantiated, otherwise nil.
|
108
117
|
def lookup_node(request_path)
|
109
118
|
name = request_path.last
|
data/lib/utopia/content/node.rb
CHANGED
@@ -107,14 +107,80 @@ module Utopia
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def call(transaction, state)
|
110
|
-
|
110
|
+
template = @controller.fetch_template(@file_path)
|
111
111
|
|
112
|
-
|
112
|
+
context = Context.new(transaction, state)
|
113
|
+
markup = template.evaluate(context)
|
114
|
+
|
115
|
+
transaction.parse_markup(markup)
|
113
116
|
end
|
114
117
|
|
115
118
|
def process!(request, response, attributes = {})
|
116
119
|
transaction = Transaction.new(request, response)
|
117
|
-
|
120
|
+
output = transaction.render_node(self, attributes)
|
121
|
+
response.write(output)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# This is a special context in which a limited set of well defined methods are exposed in the content view.
|
126
|
+
Node::Context = Struct.new(:transaction, :state) do
|
127
|
+
def initialize(transaction, state)
|
128
|
+
# We expose all attributes as instance variables within the context:
|
129
|
+
state.attributes.each do |key, value|
|
130
|
+
self.instance_variable_set("@#{key}".to_sym, value)
|
131
|
+
end
|
132
|
+
|
133
|
+
super
|
134
|
+
end
|
135
|
+
|
136
|
+
def partial(*args, &block)
|
137
|
+
if block_given?
|
138
|
+
state.defer(&block)
|
139
|
+
else
|
140
|
+
state.defer do |transaction|
|
141
|
+
transaction.tag(*args)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
alias deferred_tag partial
|
147
|
+
|
148
|
+
def controller
|
149
|
+
transaction.controller
|
150
|
+
end
|
151
|
+
|
152
|
+
def localization
|
153
|
+
transaction.localization
|
154
|
+
end
|
155
|
+
|
156
|
+
def request
|
157
|
+
transaction.request
|
158
|
+
end
|
159
|
+
|
160
|
+
def response
|
161
|
+
transaction.response
|
162
|
+
end
|
163
|
+
|
164
|
+
def attributes
|
165
|
+
state.attributes
|
166
|
+
end
|
167
|
+
|
168
|
+
def [] key
|
169
|
+
state.attributes.fetch(key) {transaction.attributes[key]}
|
170
|
+
end
|
171
|
+
|
172
|
+
alias current state
|
173
|
+
|
174
|
+
def content
|
175
|
+
transaction.content
|
176
|
+
end
|
177
|
+
|
178
|
+
def parent
|
179
|
+
transaction.parent
|
180
|
+
end
|
181
|
+
|
182
|
+
def first
|
183
|
+
transaction.first
|
118
184
|
end
|
119
185
|
end
|
120
186
|
end
|
@@ -25,11 +25,34 @@ require_relative 'tag'
|
|
25
25
|
|
26
26
|
module Utopia
|
27
27
|
class Content
|
28
|
+
class SymbolicHash < Hash
|
29
|
+
def [] key
|
30
|
+
raise KeyError.new("attribute #{key} is a string, prefer a symbol") if key.is_a? String
|
31
|
+
super key.to_sym
|
32
|
+
end
|
33
|
+
|
34
|
+
def []= key, value
|
35
|
+
super key.to_sym, value
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch(key, *args, &block)
|
39
|
+
key = key.to_sym
|
40
|
+
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
def include? key
|
45
|
+
key = key.to_sym
|
46
|
+
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
28
51
|
class Processor
|
29
|
-
def self.
|
52
|
+
def self.parse_markup(markup, delegate)
|
30
53
|
processor = self.new(delegate)
|
31
54
|
|
32
|
-
processor.parse(
|
55
|
+
processor.parse(markup)
|
33
56
|
end
|
34
57
|
|
35
58
|
class UnbalancedTagError < StandardError
|
@@ -73,6 +96,10 @@ module Utopia
|
|
73
96
|
def begin_parse(scanner)
|
74
97
|
@scanner = scanner
|
75
98
|
end
|
99
|
+
|
100
|
+
def doctype(attributes)
|
101
|
+
@delegate.cdata("<!DOCTYPE#{attributes}>")
|
102
|
+
end
|
76
103
|
|
77
104
|
def text(text)
|
78
105
|
@delegate.cdata(text)
|
@@ -83,12 +110,12 @@ module Utopia
|
|
83
110
|
end
|
84
111
|
|
85
112
|
def comment(text)
|
86
|
-
@delegate.cdata("
|
113
|
+
@delegate.cdata("<!--#{text}-->")
|
87
114
|
end
|
88
115
|
|
89
116
|
def begin_tag(tag_name, begin_tag_type)
|
90
117
|
if begin_tag_type == :opened
|
91
|
-
@stack << [Tag.new(tag_name,
|
118
|
+
@stack << [Tag.new(tag_name, SymbolicHash.new), @scanner.pos]
|
92
119
|
else
|
93
120
|
current_tag, current_position = @stack.pop
|
94
121
|
|
@@ -116,7 +143,7 @@ module Utopia
|
|
116
143
|
end
|
117
144
|
|
118
145
|
def attribute(name, value)
|
119
|
-
@stack.last[0].attributes[name] = value
|
146
|
+
@stack.last[0].attributes[name.to_sym] = value
|
120
147
|
end
|
121
148
|
|
122
149
|
def instruction(content)
|