erector 0.3.110 → 0.4.191

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/README.txt +1 -1
  2. data/bin/erect +0 -0
  3. data/lib/erector.rb +13 -10
  4. data/lib/erector/doc.rb +138 -0
  5. data/lib/erector/erected.rb +1 -1
  6. data/lib/erector/rails.rb +6 -0
  7. data/lib/erector/rails/extensions/action_controller.rb +17 -0
  8. data/lib/erector/rails/extensions/action_view.rb +21 -0
  9. data/lib/erector/{helpers.rb → rails/extensions/widget.rb} +21 -14
  10. data/lib/erector/rails/supported_rails_versions.rb +11 -0
  11. data/lib/erector/rails/template_handlers/action_view_template_handler.rb +92 -0
  12. data/lib/erector/rhtml.treetop +6 -1
  13. data/lib/erector/unicode.rb +18185 -0
  14. data/lib/erector/unicode_builder.rb +67 -0
  15. data/lib/erector/widget.rb +86 -82
  16. data/spec/core_spec_suite.rb +3 -0
  17. data/spec/erect/erect_spec.rb +1 -4
  18. data/spec/erect/erected_spec.rb +1 -4
  19. data/spec/erect/rhtml_parser_spec.rb +9 -7
  20. data/spec/erector/doc_spec.rb +55 -0
  21. data/spec/erector/indentation_spec.rb +127 -0
  22. data/spec/erector/unicode_builder_spec.rb +75 -0
  23. data/spec/erector/widget_spec.rb +339 -175
  24. data/spec/erector/widgets/table_spec.rb +26 -37
  25. data/spec/rails_spec_suite.rb +3 -0
  26. data/spec/spec_helper.rb +11 -6
  27. data/spec/spec_suite.rb +44 -3
  28. metadata +54 -28
  29. data/lib/erector/extensions/action_controller.rb +0 -24
  30. data/lib/erector/extensions/action_view_template_handler.rb +0 -66
  31. data/lib/erector/html_parts.rb +0 -63
  32. data/spec/erector/extensions/render_widget_spec.rb +0 -68
  33. data/spec/erector/extensions/template_handler_spec.rb +0 -29
  34. data/spec/erector/rails_helpers_spec.rb +0 -149
  35. data/spec/rails/standard_helpers_spec.rb +0 -102
  36. data/spec/rails/view_spec.rb +0 -33
  37. data/spec/view_caching.rb +0 -30
  38. data/test/rails_root/app/views/template_handler_spec/test_page.rb +0 -8
@@ -0,0 +1,67 @@
1
+ # Note that this class is only used in building erector itself
2
+ # (and even then, only needs to be run when there is a new
3
+ # UnicodeData.txt file from unicode.org).
4
+ class Erector::UnicodeBuilder
5
+
6
+ def initialize(input, output)
7
+ @input = input
8
+ @output = output
9
+ @first = true
10
+ end
11
+
12
+ def generate()
13
+ @output.puts "Erector::CHARACTERS = {"
14
+ process_file
15
+ @output.puts "}"
16
+ end
17
+
18
+ def process_file()
19
+ while !@input.eof
20
+ line = @input.gets.strip
21
+ if (line == "")
22
+ next;
23
+ end
24
+
25
+ process_line(line)
26
+ end
27
+ if (!@first)
28
+ @output.puts
29
+ end
30
+ end
31
+
32
+ def output_line(line)
33
+ if (!@first)
34
+ @output.puts(',')
35
+ end
36
+
37
+ @output.print(line)
38
+
39
+ @first = false
40
+ end
41
+
42
+ def process_line(line)
43
+ fields = line.split(';')
44
+ code_point = fields[0]
45
+ name = fields[1]
46
+ alternate_name = fields[10]
47
+
48
+ if /^</.match(name)
49
+ return ""
50
+ end
51
+
52
+ output name, code_point
53
+ if (!alternate_name.nil? && alternate_name != "")
54
+ output alternate_name, code_point
55
+ end
56
+ end
57
+
58
+ def output(name, code_point)
59
+ output_line " :#{namify(name)} => 0x#{code_point.downcase}"
60
+ end
61
+
62
+ def namify(name)
63
+ name.downcase.gsub(/[- ]/, '_')
64
+ end
65
+
66
+ end
67
+
@@ -1,5 +1,3 @@
1
- require 'cgi'
2
-
3
1
  module Erector
4
2
 
5
3
  # A Widget is the center of the Erector universe.
@@ -14,11 +12,11 @@ module Erector
14
12
  # To render a widget from the outside, instantiate it and call its +to_s+ method.
15
13
  #
16
14
  # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
17
- # its +render_to+ method, passing in +self+ (or self.doc if you prefer). This assures that the same HtmlParts stream
15
+ # its +render_to+ method, passing in +self+ (or self.doc if you prefer). This assures that the same Doc stream
18
16
  # is used, which gives better performance than using +capture+ or +to_s+.
19
17
  #
20
18
  # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
21
- # *return* text. "Emit" means that it writes HtmlParts to the doc stream; "return" means that it returns a string
19
+ # *return* text. "Emit" means that it writes Doc to the doc stream; "return" means that it returns a string
22
20
  # like a normal method and leaves it up to the caller to emit that string if it wants.
23
21
  class Widget
24
22
  class << self
@@ -45,23 +43,41 @@ module Erector
45
43
  ]
46
44
  end
47
45
 
46
+ def after_initialize(instance=nil, &blk)
47
+ if blk
48
+ after_initialize_parts << blk
49
+ elsif instance
50
+ if superclass.respond_to?(:after_initialize)
51
+ superclass.after_initialize instance
52
+ end
53
+ after_initialize_parts.each do |part|
54
+ instance.instance_eval &part
55
+ end
56
+ else
57
+ raise ArgumentError, "You must provide either an instance or a block"
58
+ end
59
+ end
60
+
61
+ protected
62
+ def after_initialize_parts
63
+ @after_initialize_parts ||= []
64
+ end
48
65
  end
49
66
 
50
- include ActionController::UrlWriter
51
- include Helpers
52
67
  attr_reader :helpers
53
68
  attr_reader :assigns
54
69
  attr_reader :doc
55
70
  attr_reader :block
56
71
  attr_reader :parent
57
72
 
58
- def initialize(helpers=nil, assigns={}, doc = HtmlParts.new, &block)
73
+ def initialize(helpers=nil, assigns={}, io = StringIO.new(""), &block)
59
74
  @assigns = assigns
60
75
  assign_locals(assigns)
61
76
  @helpers = helpers
62
77
  @parent = block ? eval("self", block.binding) : nil
63
- @doc = doc
78
+ @doc = Doc.new(io)
64
79
  @block = block
80
+ self.class.after_initialize self
65
81
  end
66
82
 
67
83
  #-- methods for other classes to call, left public for ease of testing and documentation
@@ -75,26 +91,42 @@ module Erector
75
91
  end
76
92
  end
77
93
  end
94
+
95
+ # Set whether Erector should add newlines and indentation in to_s.
96
+ # This is an experimental feature and is subject to change
97
+ # (either in terms of how it is enabled, or in terms of
98
+ # what decisions Erector makes about where to add whitespace).
99
+ # This flag should be set prior to any rendering being done
100
+ # (for example, calls to to_s or to_pretty).
101
+ def enable_prettyprint(enable)
102
+ @doc.enable_prettyprint = enable
103
+ self
104
+ end
105
+
106
+ # Render (like to_s) but adding newlines and indentation.
107
+ def to_pretty
108
+ enable_prettyprint(true).to_s
109
+ end
78
110
 
79
- # Entry point for rendering a widget (and all its children). This method creates a new HtmlParts doc stream,
80
- # calls this widget's #render method, converts the HtmlParts to a string, and returns the string.
111
+ # Entry point for rendering a widget (and all its children). This method creates a new Doc doc stream,
112
+ # calls this widget's #render method, converts the Doc to a string, and returns the string.
81
113
  #
82
114
  # If it's called again later
83
115
  # then it returns the earlier rendered string, which leads to higher performance, but may have confusing
84
116
  # effects if some underlying state has changed. In general we recommend you create a new instance of every
85
117
  # widget for each render, unless you know what you're doing.
86
- def to_s(&blk)
118
+ def to_s(render_method_name=:render, &blk)
87
119
  # The @__to_s variable is used as a cache.
88
120
  # If it's useful we should add a test for it. -ac
89
121
  return @__to_s if @__to_s
90
- render(&blk)
122
+ send(render_method_name, &blk)
91
123
  @__to_s = @doc.to_s
92
124
  end
93
-
125
+
94
126
  alias_method :inspect, :to_s
95
127
 
96
128
  # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
97
- # #element methods which emit HTML and text to the HtmlParts stream.
129
+ # #element methods which emit HTML and text to the Doc stream.
98
130
  def render
99
131
  if @block
100
132
  instance_eval(&@block)
@@ -102,7 +134,7 @@ module Erector
102
134
  end
103
135
 
104
136
  # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
105
- # its +render_to+ method, passing in +self+ (or self.doc if you prefer). This assures that the same HtmlParts stream
137
+ # its +render_to+ method, passing in +self+ (or self.doc if you prefer). This assures that the same Doc stream
106
138
  # is used, which gives better performance than using +capture+ or +to_s+.
107
139
  def render_to(doc_or_widget)
108
140
  if doc_or_widget.is_a?(Widget)
@@ -114,9 +146,12 @@ module Erector
114
146
  render
115
147
  end
116
148
 
117
- # Convenience method for on-the-fly widgets. (Should we make this hidden? How is it used?)
149
+ # Convenience method for on-the-fly widgets. This is a way of making
150
+ # a sub-widget which still has access to the methods of the parent class.
151
+ # This is an experimental erector feature which may disappear in future
152
+ # versions of erector (see #widget in widget_spec in the Erector tests).
118
153
  def widget(widget_class, assigns={}, &block)
119
- child = widget_class.new(helpers, assigns, doc, &block)
154
+ child = widget_class.new(helpers, assigns, doc.output, &block)
120
155
  child.render
121
156
  end
122
157
 
@@ -139,7 +174,7 @@ module Erector
139
174
  # When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
140
175
  # then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
141
176
  # The block will usually be in the scope of the child widget, which means it has access to all the
142
- # methods of Widget, which will eventually end up appending text to the +doc+ HtmlParts stream. See how
177
+ # methods of Widget, which will eventually end up appending text to the +doc+ Doc stream. See how
143
178
  # elegant it is? Not confusing at all if you don't think about it.
144
179
  #
145
180
  def element(*args, &block)
@@ -158,7 +193,7 @@ module Erector
158
193
  __empty_element__(*args, &block)
159
194
  end
160
195
 
161
- # Returns an HTML-escaped version of its parameter. Leaves the HtmlParts stream untouched. Note that
196
+ # Returns an HTML-escaped version of its parameter. Leaves the Doc stream untouched. Note that
162
197
  # the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
163
198
  # text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&amp;lt;4" instead of
164
199
  # "2&lt;4").
@@ -168,13 +203,16 @@ module Erector
168
203
 
169
204
  # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
170
205
  def open_tag(tag_name, attributes={})
171
- @doc << {:type => :open, :tagName => tag_name, :attributes => attributes}
206
+ @doc.open_tag tag_name, attributes
172
207
  end
173
208
 
174
- # Emits text which will be HTML-escaped.
209
+ # Emits text. If a string is passed in, it will be HTML-escaped.
210
+ # If a widget or the result of calling methods such as raw
211
+ # is passed in, the HTML will not be HTML-escaped again.
212
+ # If another kind of object is passed in, the result of calling
213
+ # its to_s method will be treated as a string would be.
175
214
  def text(value)
176
- @doc << {:type => :text, :value => value}
177
- nil
215
+ @doc.text value
178
216
  end
179
217
 
180
218
  # Returns text which will *not* be HTML-escaped.
@@ -182,32 +220,46 @@ module Erector
182
220
  RawString.new(value.to_s)
183
221
  end
184
222
 
185
- # Returns text which will *not* be HTML-escaped. Same effect as text(raw(s))
223
+ # Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
186
224
  def rawtext(value)
187
225
  text raw(value)
188
226
  end
189
227
 
190
228
  # Returns a copy of value with spaces replaced by non-breaking space characters.
191
- # The output uses the entity-escaping format '&#160;' since that works
229
+ # With no arguments, return a single non-breaking space.
230
+ # The output uses the escaping format '&#160;' since that works
192
231
  # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
193
- def nbsp(value)
232
+ def nbsp(value = " ")
194
233
  raw(value.html_escape.gsub(/ /,'&#160;'))
195
234
  end
235
+
236
+ # Return a character given its unicode code point or unicode name.
237
+ def character(code_point_or_name)
238
+ if code_point_or_name.is_a?(Symbol)
239
+ found = Erector::CHARACTERS[code_point_or_name]
240
+ if found.nil?
241
+ raise "Unrecognized character #{code_point_or_name}"
242
+ end
243
+ raw("&#x#{sprintf '%x', found};")
244
+ elsif code_point_or_name.is_a?(Integer)
245
+ raw("&#x#{sprintf '%x', code_point_or_name};")
246
+ else
247
+ raise "Unrecognized argument to character: #{code_point_or_name}"
248
+ end
249
+ end
196
250
 
197
251
  # Emits a close tag, consisting of '<', tag name, and '>'
198
252
  def close_tag(tag_name)
199
- @doc << {:type => :close, :tagName => tag_name}
253
+ @doc.close_tag tag_name
200
254
  end
201
255
 
202
256
  # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
203
257
  def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
204
- @doc << {:type => :instruct, :attributes => attributes}
258
+ @doc.instruct attributes
205
259
  end
206
260
 
207
261
  # Deprecated synonym of instruct
208
- def instruct!(attributes={:version => "1.0", :encoding => "UTF-8"})
209
- @doc << {:type => :instruct, :attributes => attributes}
210
- end
262
+ alias_method :instruct!, :instruct
211
263
 
212
264
  # Creates a whole new doc stream, executes the block, then converts the doc stream to a string and
213
265
  # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
@@ -215,7 +267,7 @@ module Erector
215
267
  def capture(&block)
216
268
  begin
217
269
  original_doc = @doc
218
- @doc = HtmlParts.new
270
+ @doc = Doc.new(StringIO.new)
219
271
  yield
220
272
  raw(@doc.to_s)
221
273
  ensure
@@ -299,6 +351,7 @@ module Erector
299
351
 
300
352
  protected
301
353
 
354
+ # This is part of the sub-widget/parent feature (see #widget method).
302
355
  def method_missing(name, *args, &block)
303
356
  block ||= lambda {} # captures self HERE
304
357
  if @parent
@@ -308,55 +361,6 @@ protected
308
361
  end
309
362
  end
310
363
 
311
- def fake_erbout(&blk)
312
- # override concat on the helpers object (which is usually a Rails view object)
313
- unless @helpers.respond_to?(:concat_without_erector)
314
- @helpers.metaclass.class_eval do
315
- alias_method :capture_without_erector, :capture
316
- def capture(*args, &block)
317
- result = nil
318
- widget = @erector_widget_stack.first
319
- begin
320
- original_doc = widget.doc
321
- widget.instance_eval do
322
- @doc = HtmlParts.new
323
- end
324
- captured = capture_without_erector(*args, &block)
325
- result = widget.raw(widget.doc.to_s)
326
- ensure
327
- widget.instance_eval do
328
- @doc = original_doc
329
- end
330
- end
331
- result
332
- end
333
-
334
- alias_method :concat_without_erector, :concat
335
- define_method :concat do |*args|
336
- some_text, binding = args
337
- raise "widget stack too big" if @erector_widget_stack.size > 10
338
- if @erector_widget_stack.empty?
339
- concat_without_erector(some_text, binding)
340
- else
341
- @erector_widget_stack.first.rawtext(some_text)
342
- end
343
- end
344
- define_method :erector_widget_stack do
345
- @erector_widget_stack ||= []
346
- end
347
- end
348
- end
349
-
350
- @helpers.erector_widget_stack.unshift(self)
351
- yield
352
- ensure
353
- if @helpers.respond_to?(:concat_without_erector)
354
- @helpers.erector_widget_stack.shift
355
- end
356
- end
357
-
358
- private
359
-
360
364
  def __element__(tag_name, *args, &block)
361
365
  if args.length > 2
362
366
  raise ArgumentError, "Cannot accept more than three arguments"
@@ -383,7 +387,7 @@ private
383
387
  end
384
388
 
385
389
  def __empty_element__(tag_name, attributes={})
386
- @doc << {:type => :empty, :tagName => tag_name, :attributes => attributes}
390
+ @doc.empty_element tag_name, attributes
387
391
  end
388
392
 
389
393
  end
@@ -0,0 +1,3 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/spec_suite"
3
+ SpecSuite.core
@@ -1,7 +1,4 @@
1
- dir = File.dirname(__FILE__)
2
- require "#{dir}/../spec_helper"
3
- require "#{dir}/../../lib/erector/erect"
4
- require 'tempfile'
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
5
2
 
6
3
  module Erector
7
4
  describe Erect do
@@ -1,7 +1,4 @@
1
- dir = File.dirname(__FILE__)
2
- require "#{dir}/../spec_helper"
3
- require "#{dir}/../../lib/erector/erected"
4
- require 'tempfile'
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
5
2
 
6
3
  module Erector
7
4
  describe Erected do
@@ -1,10 +1,4 @@
1
- dir = File.dirname(__FILE__)
2
- require "#{dir}/../spec_helper"
3
-
4
- # require 'test/unit'
5
- require 'rubygems'
6
- require 'treetop'
7
- require "erector/erected" # pull this out so we don't recreate the grammar every time
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
8
2
 
9
3
  module ParserTestHelper
10
4
  def assert_evals_to_self(input)
@@ -189,6 +183,7 @@ describe RhtmlParser do
189
183
  parse("<%= h hi \"mom\" %>").convert.should == "text hi(\"mom\")\n"
190
184
 
191
185
  parse("<%= \"mom\" %>").convert.should == "rawtext \"mom\"\n"
186
+ parse("<%= \"hi mom\" %>").convert.should == "rawtext \"hi mom\"\n"
192
187
  parse("<%= hi \"mom\" %>").convert.should == "rawtext hi(\"mom\")\n"
193
188
 
194
189
  parse("<%= link_to blah %>").convert.should == "rawtext link_to(blah)\n"
@@ -202,6 +197,13 @@ describe RhtmlParser do
202
197
  parse("<%= h foo / bar %>").convert.should == "text foo / bar\n"
203
198
  end
204
199
 
200
+ it "converts yield so layouts work" do
201
+ pending("easy enough to make this pass, but the result doesn't seem to work as a layout")
202
+ parse("<%= yield %>").convert.should == "rawtext @content\n"
203
+ parse("<%= \"yield\" %>").convert.should == "rawtext \"yield\"\n"
204
+ parse("<%= \"the yield is good\" %>").convert.should == "rawtext \"the yield is good\"\n"
205
+ end
206
+
205
207
  it "parses quoted strings" do
206
208
  @parser.root = :quoted
207
209
  parse("'foo'").value.should == "foo"
@@ -0,0 +1,55 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+
3
+ module Erector
4
+ describe Doc do
5
+ describe "#output" do
6
+ it "seeks to the end of the buffer" do
7
+ string = "Hello"
8
+ io = StringIO.new(string)
9
+ doc = Doc.new(io)
10
+
11
+ string.concat(" World")
12
+ doc.text " Again"
13
+
14
+ string.should == "Hello World Again"
15
+ end
16
+ end
17
+
18
+ describe "#method_missing" do
19
+ context "when passed in io object raises a NoMethodError" do
20
+ context "when the passed in io object respond_to? method is false" do
21
+ attr_reader :io
22
+ before do
23
+ @io = StringIO.new
24
+ io.should_not respond_to(:foo)
25
+ lambda {io.foo}.should raise_error(NoMethodError, /undefined method `foo' for #<StringIO/)
26
+ end
27
+
28
+ it "raises a NoMethodError that originates from within Doc#method_missing" do
29
+ doc = Doc.new(io)
30
+ lambda do
31
+ doc.foo
32
+ end.should raise_error(NoMethodError, /undefined method `foo' for #<Erector::Doc/)
33
+ end
34
+ end
35
+
36
+ context "when the passed in io object respond_to? method is true" do
37
+ attr_reader :io
38
+ before do
39
+ @io = StringIO.new
40
+ stub(io).foo {raise NoMethodError, "Stubbed NoMethodError"}
41
+ io.should respond_to(:foo)
42
+ lambda {io.foo}.should raise_error(NoMethodError, "Stubbed NoMethodError")
43
+ end
44
+
45
+ it "raises a NoMethodError that originates from within Doc#method_missing" do
46
+ doc = Doc.new(io)
47
+ lambda do
48
+ doc.foo
49
+ end.should raise_error(NoMethodError, /undefined method `foo' for #<Erector::Doc/)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end