erector 0.2.83 → 0.3.105

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.
@@ -1,4 +1,6 @@
1
1
  module Erector
2
+
3
+ # An array to which is written a stream of HTML "parts" -- each part being an open tag, a string, a close tag, etc.
2
4
  class HtmlParts < Array
3
5
  def to_s
4
6
  map do |part|
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'treetop'
3
3
 
4
4
  module Erector
5
- class Indenting < Treetop::Runtime::SyntaxNode
5
+ class Indenting < Treetop::Runtime::SyntaxNode #:nodoc:
6
6
  @@indent = 0
7
7
 
8
8
  def set_indent(x)
@@ -0,0 +1,8 @@
1
+ module Erector
2
+ # A string that has a special type so Erector knows to render it directly, not HTML-escaped
3
+ class RawString < String
4
+ def html_escape
5
+ self
6
+ end
7
+ end
8
+ end
@@ -1,20 +1,42 @@
1
1
  require 'cgi'
2
2
 
3
3
  module Erector
4
+
5
+ # A Widget is the center of the Erector universe.
6
+ #
7
+ # To create a widget, extend Erector::Widget and implement
8
+ # the +render+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
9
+ # tags.
10
+ #
11
+ # You can also define a widget on the fly by passing a block to +new+. This block will get executed when the widget's
12
+ # +render+ method is called.
13
+ #
14
+ # To render a widget from the outside, instantiate it and call its +to_s+ method.
15
+ #
16
+ # 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
18
+ # is used, which gives better performance than using +capture+ or +to_s+.
19
+ #
20
+ # 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
22
+ # like a normal method and leaves it up to the caller to emit that string if it wants.
4
23
  class Widget
5
24
  class << self
6
25
  def all_tags
7
26
  Erector::Widget.full_tags + Erector::Widget.empty_tags
8
27
  end
9
28
 
29
+ # tags which are always self-closing
10
30
  def empty_tags
11
31
  ['area', 'base', 'br', 'hr', 'img', 'input', 'link', 'meta']
12
32
  end
13
33
 
34
+ # tags which can contain other stuff
14
35
  def full_tags
15
36
  [
16
37
  'a', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'body',
17
- 'button', 'caption', 'cite', 'code', 'dd', 'del', 'div', 'dl', 'dt', 'em',
38
+ 'button', 'caption', 'center', 'cite', 'code',
39
+ 'dd', 'del', 'div', 'dl', 'dt', 'em',
18
40
  'fieldset', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
19
41
  'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
20
42
  'noframes', 'noscript', 'ol', 'optgroup', 'option', 'p', 'param', 'pre',
@@ -22,6 +44,7 @@ module Erector
22
44
  'table', 'tbody', 'td', 'textarea', 'th', 'thead', 'title', 'tr', 'tt', 'u', 'ul', 'var'
23
45
  ]
24
46
  end
47
+
25
48
  end
26
49
 
27
50
  include ActionController::UrlWriter
@@ -32,72 +55,151 @@ module Erector
32
55
  attr_reader :block
33
56
  attr_reader :parent
34
57
 
35
- # Each item in @doc is an array containing three values: type, value, attributes
36
58
  def initialize(helpers=nil, assigns={}, doc = HtmlParts.new, &block)
37
59
  @assigns = assigns
38
- assigns.each do |name, value|
60
+ assign_locals(assigns)
61
+ @helpers = helpers
62
+ @parent = block ? eval("self", block.binding) : nil
63
+ @doc = doc
64
+ @block = block
65
+ end
66
+
67
+ #-- methods for other classes to call, left public for ease of testing and documentation
68
+ #++
69
+
70
+ def assign_locals(local_assigns)
71
+ local_assigns.each do |name, value|
39
72
  instance_variable_set("@#{name}", value)
40
73
  metaclass.module_eval do
41
74
  attr_reader name
42
75
  end
43
76
  end
44
- @helpers = helpers
45
- @parent = block ? eval("self", block.binding) : nil
46
- @doc = doc
47
- @block = block
48
77
  end
49
78
 
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.
81
+ #
82
+ # If it's called again later
83
+ # then it returns the earlier rendered string, which leads to higher performance, but may have confusing
84
+ # effects if some underlying state has changed. In general we recommend you create a new instance of every
85
+ # widget for each render, unless you know what you're doing.
86
+ def to_s(&blk)
87
+ # The @__to_s variable is used as a cache.
88
+ # If it's useful we should add a test for it. -ac
89
+ return @__to_s if @__to_s
90
+ render(&blk)
91
+ @__to_s = @doc.to_s
92
+ end
93
+
94
+ alias_method :inspect, :to_s
95
+
96
+ # 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.
50
98
  def render
51
99
  if @block
52
100
  instance_eval(&@block)
53
101
  end
54
102
  end
55
103
 
56
- def render_to(doc)
57
- @doc = doc
58
- render
59
- end
60
-
61
- def render_for(parent)
62
- @parent = parent
63
- @doc = parent.doc
104
+ # 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
106
+ # is used, which gives better performance than using +capture+ or +to_s+.
107
+ def render_to(doc_or_widget)
108
+ if doc_or_widget.is_a?(Widget)
109
+ @parent = doc_or_widget
110
+ @doc = @parent.doc
111
+ else
112
+ @doc = doc_or_widget
113
+ end
64
114
  render
65
115
  end
66
116
 
117
+ # Convenience method for on-the-fly widgets. (Should we make this hidden? How is it used?)
67
118
  def widget(widget_class, assigns={}, &block)
68
119
  child = widget_class.new(helpers, assigns, doc, &block)
69
120
  child.render
70
121
  end
71
122
 
123
+ # (Should we make this hidden?)
124
+ def html_escape
125
+ return to_s
126
+ end
127
+
128
+ #-- methods for subclasses to call
129
+ #++
130
+
131
+ # Internal method used to emit an HTML/XML element, including an open tag, attributes (optional, via the default hash),
132
+ # contents (also optional), and close tag.
133
+ #
134
+ # Using the arcane powers of Ruby, there are magic methods that call +element+ for all the standard
135
+ # HTML tags, like +a+, +body+, +p+, and so forth. Look at the source of #full_tags for the full list.
136
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
137
+ # us, they're there.
138
+ #
139
+ # When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
140
+ # then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
141
+ # 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
143
+ # elegant it is? Not confusing at all if you don't think about it.
144
+ #
145
+ def element(*args, &block)
146
+ __element__(*args, &block)
147
+ end
148
+
149
+ # Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes
150
+ # (passed in via the default hash).
151
+ #
152
+ # Using the arcane powers of Ruby, there are magic methods that call +empty_element+ for all the standard
153
+ # HTML tags, like +img+, +br+, and so forth. Look at the source of #empty_tags for the full list.
154
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
155
+ # us, they're there.
156
+ #
157
+ def empty_element(*args, &block)
158
+ __empty_element__(*args, &block)
159
+ end
160
+
161
+ # Returns an HTML-escaped version of its parameter. Leaves the HtmlParts stream untouched. Note that
162
+ # the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
163
+ # text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&amp;lt;4" instead of
164
+ # "2&lt;4").
72
165
  def h(content)
73
166
  content.html_escape
74
167
  end
75
168
 
169
+ # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
76
170
  def open_tag(tag_name, attributes={})
77
171
  @doc << {:type => :open, :tagName => tag_name, :attributes => attributes}
78
172
  end
79
173
 
174
+ # Emits text which will be HTML-escaped.
80
175
  def text(value)
81
176
  @doc << {:type => :text, :value => value}
82
177
  nil
83
178
  end
84
179
 
180
+ # Returns text which will *not* be HTML-escaped.
85
181
  def raw(value)
86
182
  RawString.new(value.to_s)
87
183
  end
88
184
 
185
+ # Returns text which will *not* be HTML-escaped. Same effect as text(raw(s))
89
186
  def rawtext(value)
90
187
  text raw(value)
91
188
  end
92
189
 
190
+ # 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
192
+ # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
93
193
  def nbsp(value)
94
194
  raw(value.html_escape.gsub(/ /,'&#160;'))
95
195
  end
96
196
 
197
+ # Emits a close tag, consisting of '<', tag name, and '>'
97
198
  def close_tag(tag_name)
98
199
  @doc << {:type => :close, :tagName => tag_name}
99
200
  end
100
201
 
202
+ # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
101
203
  def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
102
204
  @doc << {:type => :instruct, :attributes => attributes}
103
205
  end
@@ -107,6 +209,41 @@ module Erector
107
209
  @doc << {:type => :instruct, :attributes => attributes}
108
210
  end
109
211
 
212
+ # Creates a whole new doc stream, executes the block, then converts the doc stream to a string and
213
+ # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
214
+ # and use #render_to instead.
215
+ def capture(&block)
216
+ begin
217
+ original_doc = @doc
218
+ @doc = HtmlParts.new
219
+ yield
220
+ raw(@doc.to_s)
221
+ ensure
222
+ @doc = original_doc
223
+ end
224
+ end
225
+
226
+ full_tags.each do |tag_name|
227
+ self.class_eval(
228
+ "def #{tag_name}(*args, &block)\n" <<
229
+ " __element__('#{tag_name}', *args, &block)\n" <<
230
+ "end",
231
+ __FILE__,
232
+ __LINE__ - 4
233
+ )
234
+ end
235
+
236
+ empty_tags.each do |tag_name|
237
+ self.class_eval(
238
+ "def #{tag_name}(*args, &block)\n" <<
239
+ " __empty_element__('#{tag_name}', *args, &block)\n" <<
240
+ "end",
241
+ __FILE__,
242
+ __LINE__ - 4
243
+ )
244
+ end
245
+
246
+ # Emits a javascript block inside a +script+ tag, wrapped in CDATA doohickeys like all the cool JS kids do.
110
247
  def javascript(*args, &block)
111
248
  if args.length > 2
112
249
  raise ArgumentError, "Cannot accept more than two arguments"
@@ -142,8 +279,79 @@ module Erector
142
279
  rawtext "\n// ]]>\n"
143
280
 
144
281
  close_tag 'script'
145
- text "\n"
282
+ rawtext "\n"
146
283
  end
284
+
285
+ # Convenience method to emit a css file link, which looks like this: <link href="erector.css" rel="stylesheet" type="text/css" />
286
+ # The parameter is the full contents of the href attribute, including any ".css" extension.
287
+ #
288
+ # If you want to emit raw CSS inline, use the #script method instead.
289
+ def css(href)
290
+ link :rel => 'stylesheet', :type => 'text/css', :href => href
291
+ end
292
+
293
+ # Convenience method to emit an anchor tag whose href and text are the same, e.g. <a href="http://example.com">http://example.com</a>
294
+ def url(href)
295
+ a href, :href => href
296
+ end
297
+
298
+ ### internal utility methods
299
+
300
+ protected
301
+
302
+ def method_missing(name, *args, &block)
303
+ block ||= lambda {} # captures self HERE
304
+ if @parent
305
+ @parent.send(name, *args, &block)
306
+ else
307
+ super
308
+ end
309
+ end
310
+
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
+ # define_method :capture do |*args|
317
+ # result = nil
318
+ # widget = @erector_widget_stack.first
319
+ # begin
320
+ # original_doc = widget.doc
321
+ # widget.doc = HtmlParts.new
322
+ # capture_without_erector(*args)
323
+ # result = raw(widget.doc.to_s)
324
+ # ensure
325
+ # widget.doc = original_doc
326
+ # end
327
+ # result
328
+ # end
329
+
330
+ alias_method :concat_without_erector, :concat
331
+ define_method :concat do |*args|
332
+ some_text, binding = args
333
+ raise "widget stack too big" if @erector_widget_stack.size > 10
334
+ if @erector_widget_stack.empty?
335
+ concat_without_erector(some_text, binding)
336
+ else
337
+ @erector_widget_stack.first.rawtext(some_text)
338
+ end
339
+ end
340
+ define_method :erector_widget_stack do
341
+ @erector_widget_stack ||= []
342
+ end
343
+ end
344
+ end
345
+
346
+ @helpers.erector_widget_stack.unshift(self)
347
+ yield
348
+ ensure
349
+ if @helpers.respond_to?(:concat_without_erector)
350
+ @helpers.erector_widget_stack.shift
351
+ end
352
+ end
353
+
354
+ private
147
355
 
148
356
  def __element__(tag_name, *args, &block)
149
357
  if args.length > 2
@@ -169,102 +377,10 @@ module Erector
169
377
  end
170
378
  close_tag tag_name
171
379
  end
172
- alias_method :element, :__element__
173
380
 
174
381
  def __empty_element__(tag_name, attributes={})
175
382
  @doc << {:type => :empty, :tagName => tag_name, :attributes => attributes}
176
383
  end
177
- alias_method :empty_element, :__empty_element__
178
-
179
- def capture(&block)
180
- begin
181
- original_doc = @doc
182
- @doc = HtmlParts.new
183
- yield
184
- raw(@doc.to_s)
185
- ensure
186
- @doc = original_doc
187
- end
188
- end
189
-
190
- def to_s(&blk)
191
- return @__to_s if @__to_s
192
- render(&blk)
193
- @__to_s = @doc.to_s
194
- end
195
-
196
- def html_escape
197
- return to_s
198
- end
199
-
200
- alias_method :inspect, :to_s
201
-
202
- full_tags.each do |tag_name|
203
- self.class_eval(
204
- "def #{tag_name}(*args, &block)\n" <<
205
- " __element__('#{tag_name}', *args, &block)\n" <<
206
- "end",
207
- __FILE__,
208
- __LINE__ - 4
209
- )
210
- end
211
-
212
- empty_tags.each do |tag_name|
213
- self.class_eval(
214
- "def #{tag_name}(*args, &block)\n" <<
215
- " __empty_element__('#{tag_name}', *args, &block)\n" <<
216
- "end",
217
- __FILE__,
218
- __LINE__ - 4
219
- )
220
- end
221
-
222
- protected
223
- def method_missing(name, *args, &block)
224
- block ||= lambda {} # captures self HERE
225
- if @parent
226
- @parent.send(name, *args, &block)
227
- else
228
- super
229
- end
230
- end
231
-
232
- def fake_erbout(&blk)
233
- widget = self
234
- @helpers.metaclass.class_eval do
235
- raise "Cannot nest fake_erbout" if instance_methods.include?('concat_without_erector')
236
- alias_method :concat_without_erector, :concat
237
- define_method :concat do |some_text, binding|
238
- widget.rawtext(some_text)
239
- end
240
- end
241
- yield
242
- ensure
243
- @helpers.metaclass.class_eval do
244
- alias_method :concat, :concat_without_erector
245
- remove_method :concat_without_erector
246
- end
247
- end
248
- end
249
- end
250
-
251
- class RawString < String
252
- def html_escape
253
- self
254
- end
255
- end
256
-
257
- class Object
258
- def html_escape
259
- return CGI.escapeHTML(to_s)
260
- end
261
-
262
- def html_unescape
263
- CGI.unescapeHTML(to_s)
264
- end
265
-
266
- # OMGWTF
267
- def escape_single_quotes
268
- self.gsub(/[']/, '\\\\\'')
384
+
269
385
  end
270
386
  end