utopia 1.8.3 → 1.9.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/documentation/.bowerrc +4 -0
  4. data/documentation/.rspec +4 -0
  5. data/documentation/Gemfile +27 -0
  6. data/documentation/Rakefile +5 -0
  7. data/documentation/config.ru +50 -0
  8. data/documentation/config/README.md +7 -0
  9. data/documentation/config/environment.rb +10 -0
  10. data/documentation/lib/readme.txt +1 -0
  11. data/documentation/pages/_heading.xnode +2 -0
  12. data/documentation/pages/_page.xnode +28 -0
  13. data/documentation/pages/errors/exception.xnode +5 -0
  14. data/documentation/pages/errors/file-not-found.xnode +5 -0
  15. data/documentation/pages/links.yaml +2 -0
  16. data/documentation/pages/welcome/index.xnode +41 -0
  17. data/documentation/pages/wiki/content.md +7 -0
  18. data/{setup/examples → documentation/pages}/wiki/controller.rb +8 -6
  19. data/documentation/pages/wiki/development-environment-setup/content.md +14 -0
  20. data/{setup/examples → documentation/pages}/wiki/edit.xnode +1 -1
  21. data/documentation/pages/wiki/server-setup/content.md +40 -0
  22. data/documentation/pages/wiki/show.xnode +12 -0
  23. data/documentation/pages/wiki/your-first-page/content.md +31 -0
  24. data/documentation/public +1 -0
  25. data/documentation/spec/website_context.rb +11 -0
  26. data/documentation/spec/website_spec.rb +15 -0
  27. data/documentation/tasks/test.rake +10 -0
  28. data/documentation/tasks/utopia.rake +38 -0
  29. data/lib/utopia/content.rb +2 -2
  30. data/lib/utopia/content/link.rb +5 -1
  31. data/lib/utopia/content/markup.rb +86 -66
  32. data/lib/utopia/content/tag.rb +28 -45
  33. data/lib/utopia/content/transaction.rb +31 -25
  34. data/lib/utopia/controller/actions.rb +1 -0
  35. data/lib/utopia/redirection.rb +4 -4
  36. data/lib/utopia/version.rb +1 -1
  37. data/setup/site/public/_static/site.css +20 -8
  38. data/spec/utopia/content/markup_spec.rb +10 -10
  39. data/spec/utopia/content/tag_spec.rb +36 -0
  40. data/spec/utopia/controller/middleware_spec.ru +4 -1
  41. data/spec/utopia/controller/middleware_spec/controller/controller.rb +2 -0
  42. data/spec/utopia/controller/middleware_spec/controller/nested/controller.rb +2 -0
  43. data/spec/utopia/controller/middleware_spec/redirect/controller.rb +2 -0
  44. data/spec/utopia/controller/middleware_spec/redirect/test/controller.rb +2 -0
  45. data/spec/utopia/controller/respond_spec.ru +5 -2
  46. data/spec/utopia/exceptions/handler_spec.ru +3 -1
  47. data/spec/utopia/exceptions/handler_spec/controller.rb +2 -0
  48. data/spec/utopia/exceptions/mailer_spec.ru +6 -2
  49. data/spec/utopia/localization_spec.rb +2 -2
  50. data/spec/utopia/localization_spec.ru +2 -1
  51. data/spec/utopia/localization_spec/controller.rb +2 -0
  52. data/spec/utopia/performance_spec/config.ru +1 -0
  53. data/spec/utopia/performance_spec/pages/api/controller.rb +1 -1
  54. data/utopia.gemspec +3 -3
  55. metadata +36 -14
  56. data/ext/utopia/xnode/fast_scanner/extconf.rb +0 -6
  57. data/ext/utopia/xnode/fast_scanner/parser.c +0 -289
  58. data/setup/examples/wiki/index.xnode +0 -10
  59. data/setup/examples/wiki/welcome/content.md +0 -3
@@ -0,0 +1,38 @@
1
+
2
+ desc 'Run by git post-update hook when deployed to a web server'
3
+ task :deploy do
4
+ # This task is typiclly run after the site is updated but before the server is restarted.
5
+ end
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
+
15
+ desc 'Set up the environment for running your web application'
16
+ task :environment do
17
+ require_relative '../config/environment'
18
+ end
19
+
20
+ desc 'Run a server for testing your web application'
21
+ task :server => :environment do
22
+ port = ENV.fetch('SERVER_PORT', 9292).to_s
23
+ exec('puma', '-v', '-p', port)
24
+ end
25
+
26
+ desc 'Start an interactive console for your web application'
27
+ task :console => :environment do
28
+ require 'pry'
29
+ require 'rack/test'
30
+
31
+ include Rack::Test::Methods
32
+
33
+ def app
34
+ @app ||= Rack::Builder.parse_file(File.expand_path("../config.ru", __dir__)).first
35
+ end
36
+
37
+ Pry.start
38
+ end
@@ -60,10 +60,10 @@ module Utopia
60
60
  def fetch_template(path)
61
61
  if @template_cache
62
62
  @template_cache.fetch_or_store(path.to_s) do
63
- Trenni::Template.load_file(path)
63
+ Trenni::MarkupTemplate.load_file(path)
64
64
  end
65
65
  else
66
- Trenni::Template.load_file(path)
66
+ Trenni::MarkupTemplate.load_file(path)
67
67
  end
68
68
  end
69
69
 
@@ -21,6 +21,8 @@
21
21
  require 'yaml'
22
22
  require 'trenni/builder'
23
23
 
24
+ require 'trenni/strings'
25
+
24
26
  require_relative '../path'
25
27
  require_relative '../locale'
26
28
 
@@ -86,7 +88,7 @@ module Utopia
86
88
  @info.fetch(:title, @title)
87
89
  end
88
90
 
89
- def to_href(**options)
91
+ def to_anchor(**options)
90
92
  Trenni::Builder.fragment(options[:builder]) do |builder|
91
93
  if href?
92
94
  relative_href(options[:base])
@@ -102,6 +104,8 @@ module Utopia
102
104
  end
103
105
  end
104
106
 
107
+ alias to_href to_anchor
108
+
105
109
  def to_s
106
110
  "\#<#{self.class}(#{self.kind}) title=#{title.inspect} href=#{href.inspect}>"
107
111
  end
@@ -18,7 +18,8 @@
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 'trenni/parser'
21
+ require 'trenni/parsers'
22
+ require 'trenni/entities'
22
23
  require 'trenni/strings'
23
24
 
24
25
  require_relative 'tag'
@@ -48,106 +49,125 @@ module Utopia
48
49
  end
49
50
  end
50
51
 
51
- class Markup
52
- def self.parse!(markup, delegate)
53
- # This is for compatibility with the existing API which passes in a string:
54
- markup = Trenni::Buffer.new(markup) if markup.is_a? String
52
+ class MarkupParser
53
+ class ParsedTag
54
+ def initialize(name, offset)
55
+ @offset = offset
56
+ @tag = Tag.new(name, false, SymbolicHash.new)
57
+ end
58
+
59
+ attr :tag
60
+ attr :offset
55
61
 
56
- self.new(markup, delegate).parse!
62
+ def to_s
63
+ @tag.to_s
64
+ end
57
65
  end
58
-
66
+
59
67
  class UnbalancedTagError < StandardError
60
- def initialize(scanner, start_position, current_tag, closing_tag)
61
- @scanner = scanner
62
- @start_position = start_position
63
- @current_tag = current_tag
68
+ def initialize(buffer, opening_tag, closing_tag = nil)
69
+ @buffer = buffer
70
+ @opening_tag = current_tag
64
71
  @closing_tag = closing_tag
65
-
66
- @starting_line = Trenni::Location.new(@scanner.string, @start_position)
67
- @ending_line = Trenni::Location.new(@scanner.string, @scanner.pos)
68
72
  end
69
73
 
70
- attr :scanner
71
- attr :start_position
74
+ attr :buffer
72
75
  attr :current_tag
73
76
  attr :closing_tag
74
-
77
+
78
+ def start_location
79
+ Trenni::Location.new(@buffer.read, current_tag.offset)
80
+ end
81
+
82
+ def end_location
83
+ if closing_tag and closing_tag.respond_to? :offset
84
+ Trenni::Location.new(@buffer.read, closing_tag.offset)
85
+ end
86
+ end
87
+
75
88
  def to_s
76
- "Unbalanced Tag Error. Line #{@starting_line}: #{@current_tag} has been closed by #{@closing_tag} on line #{@ending_line}!"
89
+ if @closing_tag
90
+ "#{start_location}: #{@opening_tag} was not closed!"
91
+ else
92
+ "#{start_location}: #{@opening_tag} was closed by #{@closing_tag}!"
93
+ end
77
94
  end
78
95
  end
79
-
80
- def initialize(buffer, delegate)
96
+
97
+ def self.parse(buffer, delegate, entities = Trenni::Entities::HTML5)
98
+ # This is for compatibility with the existing API which passes in a string:
99
+ buffer = Trenni::Buffer(buffer)
100
+
101
+ self.new(buffer, delegate).parse!
102
+ end
103
+
104
+ def initialize(buffer, delegate, entities = Trenni::Entities::HTML5)
81
105
  @buffer = buffer
106
+
82
107
  @delegate = delegate
108
+ @entities = entities
109
+
110
+ @current = nil
83
111
  @stack = []
84
112
  end
85
-
113
+
86
114
  def parse!
87
- Trenni::Parser.new(@buffer, self).parse!
115
+ Trenni::Parsers.parse_markup(@buffer, self, @entities)
88
116
 
89
- unless @stack.empty?
90
- current_tag, current_position = @stack.pop
91
-
92
- raise UnbalancedTagError.new(@scanner, current_position, current_tag.name, 'EOF')
117
+ if tag = @stack.pop
118
+ raise UnbalancedTagError.new(@buffer, tag)
93
119
  end
94
120
  end
95
121
 
96
- def begin_parse(scanner)
97
- @scanner = scanner
98
- end
99
-
100
- def doctype(attributes)
101
- @delegate.cdata("<!DOCTYPE #{attributes}>")
122
+ def open_tag_begin(name, offset)
123
+ @current = ParsedTag.new(name, offset)
102
124
  end
103
125
 
104
- def text(text)
105
- @delegate.cdata(text)
126
+ def attribute(key, value)
127
+ @current.tag.attributes[key] = value
106
128
  end
107
129
 
108
- def cdata(text)
109
- @delegate.cdata("<![CDATA[#{text}]]>")
130
+ def open_tag_end(self_closing)
131
+ if self_closing
132
+ @current.tag.closed = true
133
+ @delegate.tag_complete(@current.tag)
134
+ else
135
+ @stack << @current
136
+ @delegate.tag_begin(@current.tag)
137
+ end
138
+
139
+ @current = nil
110
140
  end
111
141
 
112
- def comment(text)
113
- @delegate.cdata("<!--#{text}-->")
142
+ def close_tag(name, offset)
143
+ @current = @stack.pop
144
+ tag = @current.tag
145
+
146
+ if tag.name != name
147
+ raise UnbalancedTagError.new(@buffer, tag, ParsedTag.new(name, offset))
148
+ end
149
+
150
+ @delegate.tag_end(tag)
114
151
  end
115
-
116
- def begin_tag(tag_name, begin_tag_type)
117
- if begin_tag_type == :opened
118
- @stack << [Tag.new(tag_name, SymbolicHash.new), @scanner.pos]
119
- else
120
- current_tag, current_position = @stack.pop
121
-
122
- if tag_name != current_tag.name
123
- raise UnbalancedTagError.new(@scanner, current_position, current_tag.name, tag_name)
124
- end
125
152
 
126
- @delegate.tag_end(current_tag)
127
- end
153
+ def doctype(string)
154
+ @delegate.write(string)
128
155
  end
129
156
 
130
- def finish_tag(begin_tag_type, end_tag_type)
131
- if begin_tag_type == :opened # <...
132
- if end_tag_type == :closed # <.../>
133
- cur, pos = @stack.pop
134
- cur.closed = true
135
-
136
- @delegate.tag_complete(cur)
137
- elsif end_tag_type == :opened # <...>
138
- cur, pos = @stack.last
157
+ def comment(string)
158
+ @delegate.write(string)
159
+ end
139
160
 
140
- @delegate.tag_begin(cur)
141
- end
142
- end
161
+ def instruction(string)
162
+ @delegate.write(string)
143
163
  end
144
164
 
145
- def attribute(name, value)
146
- @stack.last[0].attributes[name.to_sym] = value
165
+ def cdata(string)
166
+ @delegate.write(string)
147
167
  end
148
168
 
149
- def instruction(content)
150
- cdata("<?#{content}?>")
169
+ def text(string)
170
+ @delegate.text(string)
151
171
  end
152
172
  end
153
173
  end
@@ -18,63 +18,46 @@
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 'trenni/markup'
22
+
21
23
  module Utopia
22
24
  class Content
23
25
  # 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
26
+ Tag = Struct.new(:name, :closed, :attributes) do
27
+ include Trenni::Markup
30
28
 
31
- def self.closed(name, attributes = {})
32
- tag = Tag.new(name, attributes)
33
- tag.closed = true
34
-
35
- return tag
29
+ def self.closed(name, **attributes)
30
+ self.new(name, true, attributes)
36
31
  end
37
32
 
38
- def initialize(name, attributes = {})
39
- @name = name
40
- @attributes = attributes
41
- @closed = false
42
- end
43
-
44
- def freeze
45
- @name.freeze
46
- @attributes.freeze
47
- @closed.freeze
48
-
49
- super
50
- end
51
-
52
- attr_accessor :name
53
- attr_accessor :attributes
54
- attr_accessor :closed
55
-
56
- def [](key)
57
- @attributes[key]
33
+ def self.opened(name, **attributes)
34
+ self.new(name, false, attributes)
58
35
  end
59
36
 
60
- def to_hash
61
- @attributes
37
+ def [] key
38
+ attributes[key]
62
39
  end
63
40
 
41
+ alias to_hash attributes
42
+
64
43
  def to_s(content = nil)
65
- buffer = String.new
44
+ buffer = String.new.force_encoding(name.encoding)
66
45
 
67
- write_full_html(buffer, content)
46
+ write(buffer, content)
68
47
 
69
48
  return buffer
70
49
  end
71
50
 
72
- alias to_html to_s
51
+ alias to_str to_s
52
+
53
+ def self_closed?
54
+ closed
55
+ end
73
56
 
74
- def write_open_html(buffer, terminate = false)
57
+ def write_opening_tag(buffer, self_closing = false)
75
58
  buffer << "<#{name}"
76
59
 
77
- @attributes.each do |key, value|
60
+ attributes.each do |key, value|
78
61
  if value
79
62
  buffer << " #{key}=\"#{value}\""
80
63
  else
@@ -82,24 +65,24 @@ module Utopia
82
65
  end
83
66
  end
84
67
 
85
- if terminate
68
+ if self_closing
86
69
  buffer << "/>"
87
70
  else
88
71
  buffer << ">"
89
72
  end
90
73
  end
91
74
 
92
- def write_close_html(buffer)
75
+ def write_closing_tag(buffer)
93
76
  buffer << "</#{name}>"
94
77
  end
95
78
 
96
- def write_full_html(buffer, content = nil)
97
- if @closed and content.nil?
98
- write_open_html(buffer, true)
79
+ def write(buffer, content = nil)
80
+ if closed and content.nil?
81
+ write_opening_tag(buffer, true)
99
82
  else
100
- write_open_html(buffer)
83
+ write_opening_tag(buffer)
101
84
  buffer << content if content
102
- write_close_html(buffer)
85
+ write_closing_tag(buffer)
103
86
  end
104
87
  end
105
88
  end
@@ -29,7 +29,7 @@ module Utopia
29
29
  def initialize(tag)
30
30
  @tag = tag
31
31
 
32
- super("Unbalanced tag #{tag.name}")
32
+ super "Unbalanced tag #{tag.name}"
33
33
  end
34
34
 
35
35
  attr :tag
@@ -48,12 +48,13 @@ module Utopia
48
48
  @begin_tags = []
49
49
  @end_tags = []
50
50
 
51
+ # TODO: Provide way to set encoding and content type.
52
+ #@encoding = Encoding::UTF_8
53
+ #self.content_type = "text/html; charset=#{@encoding.name}"
54
+
51
55
  super()
52
56
  end
53
57
 
54
- attr :status
55
- attr :headers
56
-
57
58
  # A helper method for accessing controller variables from view:
58
59
  def controller
59
60
  @controller ||= Utopia::Controller[request]
@@ -64,7 +65,7 @@ module Utopia
64
65
  end
65
66
 
66
67
  def parse_markup(markup)
67
- Markup.parse!(markup, self)
68
+ MarkupParser.parse(markup, self)
68
69
  end
69
70
 
70
71
  # The Rack::Request for this transaction.
@@ -83,7 +84,8 @@ module Utopia
83
84
  attr :end_tags
84
85
 
85
86
  def tag(name, attributes = {}, &block)
86
- tag = Tag.new(name, attributes)
87
+ # If we provide a block which can give inner data, we are not self-closing.
88
+ tag = Tag.new(name, !block_given?, attributes)
87
89
 
88
90
  node = tag_begin(tag)
89
91
 
@@ -94,7 +96,7 @@ module Utopia
94
96
 
95
97
  def tag_complete(tag, node = nil)
96
98
  if tag.name == CONTENT_TAG_NAME
97
- current.markup(content)
99
+ current.write(content)
98
100
  else
99
101
  node ||= lookup(tag)
100
102
 
@@ -125,9 +127,15 @@ module Utopia
125
127
 
126
128
  return nil
127
129
  end
130
+
131
+ def write(string)
132
+ current.write(string)
133
+ end
134
+
135
+ alias cdata write
128
136
 
129
- def cdata(text)
130
- current.cdata(text)
137
+ def text(string)
138
+ current.text(string)
131
139
  end
132
140
 
133
141
  def tag_end(tag = nil)
@@ -146,7 +154,7 @@ module Utopia
146
154
  self.end_tags.pop
147
155
 
148
156
  if current
149
- current.markup(buffer)
157
+ current.write(buffer)
150
158
  end
151
159
 
152
160
  return buffer
@@ -188,7 +196,7 @@ module Utopia
188
196
  @begin_tags.last
189
197
  end
190
198
 
191
- # The content of the node
199
+ # The content of the node
192
200
  def content
193
201
  @end_tags.last.content
194
202
  end
@@ -207,14 +215,13 @@ module Utopia
207
215
  def initialize(tag, node, attributes = tag.to_hash)
208
216
  @node = node
209
217
 
210
- # The contents of the output
211
- @buffer = String.new
218
+ @buffer = Trenni::MarkupString.new.force_encoding(Encoding::UTF_8)
212
219
 
213
220
  @overrides = {}
214
-
221
+
215
222
  @tags = []
216
223
  @attributes = attributes
217
-
224
+
218
225
  @content = nil
219
226
  @deferred = []
220
227
  end
@@ -230,7 +237,7 @@ module Utopia
230
237
  def defer(value = nil, &block)
231
238
  @deferred << block
232
239
 
233
- Tag.closed(DEFERRED_TAG_NAME, :id => @deferred.size - 1).to_s
240
+ Tag.closed(DEFERRED_TAG_NAME, :id => @deferred.size - 1)
234
241
  end
235
242
 
236
243
  def [](key)
@@ -253,7 +260,7 @@ module Utopia
253
260
 
254
261
  def call(transaction)
255
262
  @content = @buffer
256
- @buffer = String.new
263
+ @buffer = Trenni::MarkupString.new.force_encoding(Encoding::UTF_8)
257
264
 
258
265
  if node.respond_to? :call
259
266
  node.call(transaction, self)
@@ -264,27 +271,26 @@ module Utopia
264
271
  return @buffer
265
272
  end
266
273
 
267
- def cdata(text)
268
- @buffer << text
274
+ def write(string)
275
+ @buffer << string
269
276
  end
270
277
 
271
- def markup(text)
272
- cdata(text)
278
+ def text(string)
279
+ @buffer << Trenni::MarkupString(string)
273
280
  end
274
281
 
275
282
  def tag_complete(tag)
276
- tag.write_full_html(@buffer)
283
+ tag.write(@buffer)
277
284
  end
278
285
 
279
286
  def tag_begin(tag)
280
287
  @tags << tag
281
- tag.write_open_html(@buffer)
288
+ tag.write_opening_tag(@buffer)
282
289
  end
283
290
 
284
291
  def tag_end(tag)
285
292
  raise UnbalancedTagError(tag) unless @tags.pop.name == tag.name
286
-
287
- tag.write_close_html(@buffer)
293
+ tag.write_closing_tag(@buffer)
288
294
  end
289
295
  end
290
296
  end