utopia 1.8.3 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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