erector 0.8.3 → 0.9.0.pre1
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.
- data/Gemfile +21 -0
- data/Rakefile +171 -0
- data/VERSION.yml +3 -2
- data/lib/erector.rb +3 -5
- data/lib/erector/abstract_widget.rb +76 -31
- data/lib/erector/attributes.rb +34 -0
- data/lib/erector/caching.rb +1 -0
- data/lib/erector/convenience.rb +12 -4
- data/lib/erector/dependency.rb +2 -1
- data/lib/erector/element.rb +113 -0
- data/lib/erector/erect/erect.rb +10 -7
- data/lib/erector/externals.rb +2 -1
- data/lib/erector/html.rb +6 -340
- data/lib/erector/html_widget.rb +300 -0
- data/lib/erector/inline.rb +1 -1
- data/lib/erector/output.rb +39 -12
- data/lib/erector/promise.rb +137 -0
- data/lib/erector/rails2/extensions/rails_widget.rb +1 -1
- data/lib/erector/rails3.rb +1 -1
- data/lib/erector/sass.rb +14 -19
- data/lib/erector/tag.rb +65 -0
- data/lib/erector/text.rb +123 -0
- data/lib/erector/version.rb +1 -1
- data/lib/erector/widget.rb +52 -12
- data/lib/erector/widgets/page.rb +12 -10
- data/lib/erector/xml_widget.rb +131 -0
- data/spec/erector/caching_spec.rb +1 -0
- data/spec/erector/externals_spec.rb +0 -1
- data/spec/erector/html_spec.rb +9 -25
- data/spec/erector/output_spec.rb +102 -9
- data/spec/erector/promise_spec.rb +173 -0
- data/spec/erector/sass_spec.rb +1 -1
- data/spec/erector/tag_spec.rb +67 -0
- data/spec/erector/widget_spec.rb +53 -2
- data/spec/erector/xml_widget_spec.rb +74 -0
- data/spec/rails2/rails_app/Gemfile +1 -0
- data/spec/rails2/rails_app/spec/rails_spec_helper.rb +2 -0
- data/spec/rails_root/Gemfile +11 -0
- data/spec/rails_root/README +256 -0
- data/spec/rails_root/Rakefile +7 -0
- data/spec/rails_root/app/controllers/application.rb +6 -0
- data/spec/rails_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_root/app/views/test/_erb.erb +1 -0
- data/spec/rails_root/app/views/test/_erector.rb +5 -0
- data/spec/rails_root/app/views/test/_partial_with_locals.rb +7 -0
- data/spec/rails_root/app/views/test/bare.rb +5 -0
- data/spec/rails_root/app/views/test/erb_from_erector.html.rb +5 -0
- data/spec/rails_root/app/views/test/erector_from_erb.html.erb +1 -0
- data/spec/rails_root/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
- data/spec/rails_root/app/views/test/implicit_assigns.html.rb +5 -0
- data/spec/rails_root/app/views/test/needs.html.rb +7 -0
- data/spec/rails_root/app/views/test/needs_subclass.html.rb +5 -0
- data/spec/rails_root/app/views/test/protected_instance_variable.html.rb +5 -0
- data/spec/rails_root/app/views/test/render_default.html.rb +5 -0
- data/spec/rails_root/app/views/test/render_partial.html.rb +5 -0
- data/spec/rails_root/config.ru +4 -0
- data/spec/rails_root/config/application.rb +42 -0
- data/spec/rails_root/config/boot.rb +13 -0
- data/spec/rails_root/config/database.yml +22 -0
- data/spec/rails_root/config/environment.rb +5 -0
- data/spec/rails_root/config/environments/development.rb +22 -0
- data/spec/rails_root/config/environments/production.rb +49 -0
- data/spec/rails_root/config/environments/test.rb +35 -0
- data/spec/rails_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_root/config/initializers/inflections.rb +10 -0
- data/spec/rails_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails_root/config/initializers/session_store.rb +8 -0
- data/spec/rails_root/config/locales/en.yml +5 -0
- data/spec/rails_root/config/routes.rb +58 -0
- data/spec/rails_root/db/seeds.rb +7 -0
- data/spec/rails_root/doc/README_FOR_APP +2 -0
- data/spec/rails_root/log/development.log +17 -0
- data/spec/rails_root/log/test.log +3750 -0
- data/spec/rails_root/public/404.html +26 -0
- data/spec/rails_root/public/422.html +26 -0
- data/spec/rails_root/public/500.html +26 -0
- data/spec/rails_root/public/dispatch.cgi +10 -0
- data/spec/rails_root/public/dispatch.fcgi +24 -0
- data/spec/rails_root/public/dispatch.rb +10 -0
- data/spec/rails_root/public/favicon.ico +0 -0
- data/spec/rails_root/public/images/rails.png +0 -0
- data/spec/rails_root/public/index.html +262 -0
- data/spec/rails_root/public/javascripts/application.js +2 -0
- data/spec/rails_root/public/javascripts/controls.js +965 -0
- data/spec/rails_root/public/javascripts/dragdrop.js +974 -0
- data/spec/rails_root/public/javascripts/effects.js +1123 -0
- data/spec/rails_root/public/javascripts/prototype.js +6001 -0
- data/spec/rails_root/public/javascripts/rails.js +175 -0
- data/spec/rails_root/public/robots.txt +5 -0
- data/spec/rails_root/script/about +3 -0
- data/spec/rails_root/script/console +3 -0
- data/spec/rails_root/script/destroy +3 -0
- data/spec/rails_root/script/generate +3 -0
- data/spec/rails_root/script/performance/benchmarker +3 -0
- data/spec/rails_root/script/performance/profiler +3 -0
- data/spec/rails_root/script/performance/request +3 -0
- data/spec/rails_root/script/plugin +3 -0
- data/spec/rails_root/script/process/inspector +3 -0
- data/spec/rails_root/script/process/reaper +3 -0
- data/spec/rails_root/script/process/spawner +3 -0
- data/spec/rails_root/script/rails +6 -0
- data/spec/rails_root/script/runner +3 -0
- data/spec/rails_root/script/server +3 -0
- data/spec/rails_root/spec/form_builder_spec.rb +21 -0
- data/spec/rails_root/spec/rails_helpers_spec.rb +220 -0
- data/spec/rails_root/spec/rails_spec_helper.rb +10 -0
- data/spec/rails_root/spec/rails_widget_spec.rb +83 -0
- data/spec/rails_root/spec/render_spec.rb +298 -0
- data/spec/rails_root/test/performance/browsing_test.rb +9 -0
- data/spec/rails_root/test/test_helper.rb +13 -0
- data/spec/spec_helper.rb +3 -1
- metadata +202 -66
@@ -0,0 +1,300 @@
|
|
1
|
+
require "erector/xml_widget"
|
2
|
+
|
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 the +content+
|
8
|
+
# method. Inside this method you may call any of the tag methods like +span+
|
9
|
+
# or +p+ to emit HTML/XML tags.
|
10
|
+
#
|
11
|
+
# You can also define a widget on the fly by passing a block to +new+. This
|
12
|
+
# block will get executed when the widget's +content+ method is called. See
|
13
|
+
# the userguide for important details about the scope of this block when run --
|
14
|
+
# http://erector.rubyforge.org/userguide.html#blocks
|
15
|
+
#
|
16
|
+
# To render a widget from the outside, instantiate it and call its +to_html+
|
17
|
+
# method.
|
18
|
+
#
|
19
|
+
# A widget's +new+ method optionally accepts an options hash. Entries in
|
20
|
+
# this hash are converted to instance variables.
|
21
|
+
#
|
22
|
+
# You can add runtime input checking via the +needs+ macro. See #needs.
|
23
|
+
# This mechanism is meant to ameliorate development-time confusion about
|
24
|
+
# exactly what parameters are supported by a given widget, avoiding
|
25
|
+
# confusing runtime NilClass errors.
|
26
|
+
#
|
27
|
+
# To call one widget from another, inside the parent widget's +content+
|
28
|
+
# method, instantiate the child widget and call the +widget+ method. This
|
29
|
+
# assures that the same output stream is used, which gives better
|
30
|
+
# performance than using +capture+ or +to_html+. It also preserves the
|
31
|
+
# indentation and helpers of the enclosing class.
|
32
|
+
#
|
33
|
+
# In this documentation we've tried to keep the distinction clear between
|
34
|
+
# methods that *emit* text and those that *return* text. "Emit" means that
|
35
|
+
# it writes to the output stream; "return" means that it returns a string
|
36
|
+
# like a normal method and leaves it up to the caller to emit that string if
|
37
|
+
# it wants.
|
38
|
+
#
|
39
|
+
# This class extends AbstractWidget and includes several modules,
|
40
|
+
# so be sure to check all of those places for API documentation for the
|
41
|
+
# various methods of Widget:
|
42
|
+
#
|
43
|
+
# * AbstractWidget
|
44
|
+
# * Element
|
45
|
+
# * Attributes
|
46
|
+
# * Text
|
47
|
+
# * Needs
|
48
|
+
# * Caching
|
49
|
+
# * Externals
|
50
|
+
# * AfterInitialize
|
51
|
+
#
|
52
|
+
# * HTML
|
53
|
+
# * Convenience
|
54
|
+
# * JQuery
|
55
|
+
# * Sass
|
56
|
+
#
|
57
|
+
# Also read the API Cheatsheet in the user guide
|
58
|
+
# at http://erector.rubyforge.org/userguide#apicheatsheet
|
59
|
+
class HTMLWidget < Erector::XMLWidget
|
60
|
+
|
61
|
+
include Erector::HTML
|
62
|
+
include Erector::Convenience
|
63
|
+
include Erector::JQuery
|
64
|
+
include Erector::Sass if Object.const_defined?(:Sass)
|
65
|
+
|
66
|
+
tag 'area', :self_closing
|
67
|
+
tag 'base', :self_closing
|
68
|
+
tag 'br', :self_closing
|
69
|
+
tag 'col', :self_closing
|
70
|
+
tag 'embed', :self_closing
|
71
|
+
tag 'frame', :self_closing
|
72
|
+
tag 'hr', :self_closing
|
73
|
+
tag 'img', :self_closing, :inline
|
74
|
+
tag 'input', :self_closing, :inline
|
75
|
+
tag 'link', :self_closing
|
76
|
+
tag 'meta', :self_closing
|
77
|
+
tag 'param', :self_closing
|
78
|
+
|
79
|
+
tag 'a', :inline
|
80
|
+
tag 'abbr'
|
81
|
+
tag 'acronym'
|
82
|
+
tag 'address'
|
83
|
+
tag 'article'
|
84
|
+
tag 'aside'
|
85
|
+
tag 'audio'
|
86
|
+
|
87
|
+
tag 'b', :inline
|
88
|
+
tag 'bdo'
|
89
|
+
tag 'big'
|
90
|
+
tag 'blockquote'
|
91
|
+
tag 'body'
|
92
|
+
tag 'button', :inline
|
93
|
+
|
94
|
+
tag 'canvas'
|
95
|
+
tag 'caption'
|
96
|
+
tag 'center'
|
97
|
+
tag 'cite'
|
98
|
+
tag 'code'
|
99
|
+
tag 'colgroup'
|
100
|
+
tag 'command'
|
101
|
+
|
102
|
+
tag 'datalist'
|
103
|
+
tag 'dd'
|
104
|
+
tag 'del'
|
105
|
+
tag 'details'
|
106
|
+
tag 'dfn'
|
107
|
+
tag 'dialog'
|
108
|
+
tag 'div'
|
109
|
+
tag 'dl'
|
110
|
+
tag 'dt'
|
111
|
+
|
112
|
+
tag 'em'
|
113
|
+
|
114
|
+
tag 'fieldset'
|
115
|
+
tag 'figure'
|
116
|
+
tag 'footer'
|
117
|
+
tag 'form'
|
118
|
+
tag 'frameset'
|
119
|
+
|
120
|
+
tag 'h1'
|
121
|
+
tag 'h2'
|
122
|
+
tag 'h3'
|
123
|
+
tag 'h4'
|
124
|
+
tag 'h5'
|
125
|
+
tag 'h6'
|
126
|
+
tag 'head'
|
127
|
+
tag 'header'
|
128
|
+
tag 'hgroup'
|
129
|
+
tag 'html'
|
130
|
+
tag 'i', :inline
|
131
|
+
|
132
|
+
tag 'iframe'
|
133
|
+
tag 'ins'
|
134
|
+
tag 'keygen'
|
135
|
+
tag 'kbd'
|
136
|
+
tag 'label'
|
137
|
+
tag 'legend'
|
138
|
+
tag 'li'
|
139
|
+
|
140
|
+
tag 'map'
|
141
|
+
tag 'mark'
|
142
|
+
tag 'meter'
|
143
|
+
|
144
|
+
tag 'nav'
|
145
|
+
tag 'noframes'
|
146
|
+
tag 'noscript'
|
147
|
+
|
148
|
+
tag 'object'
|
149
|
+
tag 'ol'
|
150
|
+
tag 'optgroup'
|
151
|
+
tag 'option'
|
152
|
+
|
153
|
+
tag 'p'
|
154
|
+
tag 'pre'
|
155
|
+
tag 'progress'
|
156
|
+
|
157
|
+
tag 'q'
|
158
|
+
tag 'ruby'
|
159
|
+
tag 'rt'
|
160
|
+
tag 'rp'
|
161
|
+
tag 's'
|
162
|
+
|
163
|
+
tag 'samp'
|
164
|
+
tag 'script'
|
165
|
+
tag 'section'
|
166
|
+
tag 'select', :inline
|
167
|
+
tag 'small', :inline
|
168
|
+
tag 'source'
|
169
|
+
tag 'span', :inline
|
170
|
+
tag 'strike'
|
171
|
+
|
172
|
+
tag 'strong'
|
173
|
+
tag 'style'
|
174
|
+
tag 'sub'
|
175
|
+
tag 'sup'
|
176
|
+
|
177
|
+
tag 'table'
|
178
|
+
tag 'tbody'
|
179
|
+
tag 'td'
|
180
|
+
tag 'textarea', :inline
|
181
|
+
tag 'tfoot'
|
182
|
+
|
183
|
+
tag 'th'
|
184
|
+
tag 'thead'
|
185
|
+
tag 'time'
|
186
|
+
tag 'title'
|
187
|
+
tag 'tr'
|
188
|
+
tag 'tt'
|
189
|
+
|
190
|
+
tag 'u'
|
191
|
+
tag 'ul'
|
192
|
+
|
193
|
+
tag 'var'
|
194
|
+
tag 'video'
|
195
|
+
|
196
|
+
|
197
|
+
# Emits a javascript block inside a +script+ tag, wrapped in CDATA
|
198
|
+
# doohickeys like all the cool JS kids do.
|
199
|
+
def javascript(value = nil, attributes = {})
|
200
|
+
if value.is_a?(Hash)
|
201
|
+
attributes = value
|
202
|
+
value = nil
|
203
|
+
elsif block_given? && value
|
204
|
+
raise ArgumentError, "You can't pass both a block and a value to javascript -- please choose one."
|
205
|
+
end
|
206
|
+
|
207
|
+
script(attributes.merge(:type => "text/javascript")) do
|
208
|
+
# Shouldn't this be a "cdata" HtmlPart?
|
209
|
+
# (maybe, but the syntax is specific to javascript; it isn't
|
210
|
+
# really a generic XML CDATA section. Specifically,
|
211
|
+
# ]]> within value is not treated as ending the
|
212
|
+
# CDATA section by Firefox2 when parsing text/html,
|
213
|
+
# although I guess we could refuse to generate ]]>
|
214
|
+
# there, for the benefit of XML/XHTML parsers).
|
215
|
+
output << raw("\n// <![CDATA[\n")
|
216
|
+
if block_given?
|
217
|
+
yield
|
218
|
+
else
|
219
|
+
output << raw(value)
|
220
|
+
end
|
221
|
+
output << raw("\n// ]]>")
|
222
|
+
output.append_newline # this forces a newline even if we're not in pretty mode
|
223
|
+
end
|
224
|
+
|
225
|
+
output << raw("\n")
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
|
230
|
+
# alias for AbstractWidget#render
|
231
|
+
def to_html(options = {})
|
232
|
+
raise "Erector::Widget#to_html takes an options hash, not a symbol. Try calling \"to_html(:content_method_name=> :#{options})\"" if options.is_a? Symbol
|
233
|
+
_render(options).to_s
|
234
|
+
end
|
235
|
+
|
236
|
+
# alias for #to_html
|
237
|
+
# @deprecated Please use {#to_html} instead
|
238
|
+
def to_s(*args)
|
239
|
+
unless defined? @@already_warned_to_s
|
240
|
+
$stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
|
241
|
+
@@already_warned_to_s = true
|
242
|
+
end
|
243
|
+
to_html(*args)
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
# Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
248
|
+
def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
|
249
|
+
output << raw("<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>")
|
250
|
+
end
|
251
|
+
|
252
|
+
# Emits an XML/HTML comment (<!-- ... -->) surrounding +text+ and/or
|
253
|
+
# the output of +block+. see
|
254
|
+
# http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
|
255
|
+
#
|
256
|
+
# If +text+ is an Internet Explorer conditional comment condition such as
|
257
|
+
# "[if IE]", the output includes the opening condition and closing
|
258
|
+
# "[endif]". See http://www.quirksmode.org/css/condcom.html
|
259
|
+
#
|
260
|
+
# Since "Authors should avoid putting two or more adjacent hyphens inside
|
261
|
+
# comments," we emit a warning if you do that.
|
262
|
+
def comment(text = '')
|
263
|
+
puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if text =~ /--/
|
264
|
+
|
265
|
+
conditional = text =~ /\[if .*\]/
|
266
|
+
|
267
|
+
rawtext "<!--"
|
268
|
+
rawtext text
|
269
|
+
rawtext ">" if conditional
|
270
|
+
|
271
|
+
if block_given?
|
272
|
+
rawtext "\n"
|
273
|
+
yield
|
274
|
+
rawtext "\n"
|
275
|
+
end
|
276
|
+
|
277
|
+
rawtext "<![endif]" if conditional
|
278
|
+
rawtext "-->\n"
|
279
|
+
end
|
280
|
+
|
281
|
+
protected
|
282
|
+
|
283
|
+
def sort_for_xml_declaration(attributes)
|
284
|
+
# correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
|
285
|
+
# But we only try to put version before encoding for now.
|
286
|
+
stringized = []
|
287
|
+
attributes.each do |key, value|
|
288
|
+
stringized << [key.to_s, value]
|
289
|
+
end
|
290
|
+
stringized.sort{|a, b| b <=> a}
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
public
|
296
|
+
|
297
|
+
# Alias to make it easier to spell
|
298
|
+
HtmlWidget = HTMLWidget
|
299
|
+
|
300
|
+
end
|
data/lib/erector/inline.rb
CHANGED
data/lib/erector/output.rb
CHANGED
@@ -1,22 +1,25 @@
|
|
1
|
+
require 'erector/abstract_widget'
|
2
|
+
|
1
3
|
module Erector
|
2
4
|
class Output
|
3
5
|
SPACES_PER_INDENT = 2
|
4
6
|
|
5
7
|
attr_reader :prettyprint, :widgets, :indentation, :max_length
|
6
8
|
|
7
|
-
def initialize(options = {}
|
8
|
-
@prettyprint = options.fetch(:prettyprint,
|
9
|
+
def initialize(options = {})
|
10
|
+
@prettyprint = options.fetch(:prettyprint, AbstractWidget.prettyprint_default)
|
9
11
|
@indentation = options.fetch(:indentation, 0)
|
10
12
|
@current_line_length = 0
|
11
13
|
@max_length = options[:max_length]
|
12
14
|
@widgets = []
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
|
16
|
+
@get_buffer = if options[:buffer] and options[:buffer].respond_to? :call
|
17
|
+
options[:buffer]
|
18
|
+
elsif options[:buffer]
|
19
|
+
lambda { options[:buffer] }
|
17
20
|
else
|
18
21
|
buffer = []
|
19
|
-
|
22
|
+
lambda { buffer }
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
@@ -25,11 +28,13 @@ module Erector
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def <<(s)
|
31
|
+
# raise s.inspect unless s.is_a? String
|
32
|
+
#
|
28
33
|
s = s.to_s unless s.is_a? String
|
29
34
|
append_indentation
|
30
35
|
if @max_length && s.length + @current_line_length > @max_length
|
31
36
|
leading_spaces = s =~ /^( +)/ ? $1.size : 0
|
32
|
-
trailing_spaces = s =~ /( +)$/ ? $1.size : 0
|
37
|
+
trailing_spaces = s =~ /( +)$/ ? $1.size : 0
|
33
38
|
|
34
39
|
append(" " * leading_spaces)
|
35
40
|
need_space = false
|
@@ -51,10 +56,11 @@ module Erector
|
|
51
56
|
self
|
52
57
|
end
|
53
58
|
|
54
|
-
# Inserts a blank string into the output stream and returns a pointer to
|
55
|
-
# If the caller holds on to this pointer, she can later go back and
|
56
|
-
# earlier in the stream. This is used for, e.g., inserting
|
57
|
-
# HEAD element that is not known until after the entire
|
59
|
+
# Inserts a blank string into the output stream and returns a pointer to
|
60
|
+
# it. If the caller holds on to this pointer, she can later go back and
|
61
|
+
# insert text earlier in the stream. This is used for, e.g., inserting
|
62
|
+
# stuff inside the HEAD element that is not known until after the entire
|
63
|
+
# page renders.
|
58
64
|
def placeholder
|
59
65
|
s = ""
|
60
66
|
buffer << s
|
@@ -94,6 +100,27 @@ module Erector
|
|
94
100
|
@current_line_length = 0
|
95
101
|
end
|
96
102
|
|
103
|
+
def mark
|
104
|
+
@mark = buffer.size
|
105
|
+
end
|
106
|
+
|
107
|
+
def rewind pos = @mark
|
108
|
+
if buffer.kind_of?(Array)
|
109
|
+
buffer.slice!(pos..-1)
|
110
|
+
elsif (Object.const_defined?(:ActiveSupport) and
|
111
|
+
buffer.kind_of?(ActiveSupport::SafeBuffer))
|
112
|
+
# monkey patch to get around SafeBuffer's well-meaning paranoia
|
113
|
+
# see http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/
|
114
|
+
# and http://weblog.rubyonrails.org/2011/6/8/potential-xss-vulnerability-in-ruby-on-rails-applications
|
115
|
+
String.instance_method(:slice!).bind(buffer).call(pos..-1)
|
116
|
+
elsif buffer.kind_of?(String)
|
117
|
+
buffer.slice!(pos..-1)
|
118
|
+
else
|
119
|
+
raise "Don't know how to rewind a #{buffer.class}"
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
97
124
|
protected
|
98
125
|
|
99
126
|
def append(s)
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "erector/attributes"
|
2
|
+
require "erector/text"
|
3
|
+
|
4
|
+
module Erector
|
5
|
+
class Promise
|
6
|
+
extend Attributes
|
7
|
+
extend Text
|
8
|
+
|
9
|
+
def initialize(output, tag_name, attributes = {}, self_closing = false, newliney = true, &inside_renderer)
|
10
|
+
raise "bad output: #{output.inspect}" unless output.is_a? Output
|
11
|
+
raise "forgot self-closing" unless [false, true].include? self_closing
|
12
|
+
|
13
|
+
@output = output
|
14
|
+
|
15
|
+
# todo: pointer to Tag object?
|
16
|
+
@tag_name = tag_name
|
17
|
+
@self_closing = self_closing
|
18
|
+
@newliney = newliney
|
19
|
+
|
20
|
+
@attributes = {}
|
21
|
+
_set_attributes attributes
|
22
|
+
@text = nil
|
23
|
+
@inside_renderer = inside_renderer
|
24
|
+
_mark
|
25
|
+
end
|
26
|
+
|
27
|
+
def _set_attributes attributes
|
28
|
+
attributes.each_pair do |k,v|
|
29
|
+
@attributes[k.to_s] = v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def _mark
|
34
|
+
@mark = @output.mark
|
35
|
+
end
|
36
|
+
|
37
|
+
def _rewind
|
38
|
+
@output.rewind @mark
|
39
|
+
end
|
40
|
+
|
41
|
+
def _render
|
42
|
+
_rewind
|
43
|
+
_render_open_tag
|
44
|
+
begin
|
45
|
+
_render_inside_tag
|
46
|
+
ensure
|
47
|
+
_render_close_tag
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def _render_open_tag
|
52
|
+
|
53
|
+
@output.newline if !@self_closing and @newliney and !@output.at_line_start?
|
54
|
+
|
55
|
+
@output << RawString.new( "<#{@tag_name}#{Promise.format_attributes(@attributes)}")
|
56
|
+
if @self_closing
|
57
|
+
@output << RawString.new( " />")
|
58
|
+
@output.newline if @newliney
|
59
|
+
else
|
60
|
+
@output << RawString.new( ">")
|
61
|
+
@output.indent
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def _render_inside_tag
|
66
|
+
return if @self_closing
|
67
|
+
if @text
|
68
|
+
@output << @text
|
69
|
+
end
|
70
|
+
if @inside_renderer
|
71
|
+
@inside_renderer.call
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def _render_close_tag
|
76
|
+
return if @self_closing
|
77
|
+
|
78
|
+
@output.undent
|
79
|
+
@output<< RawString.new("</#{@tag_name}>")
|
80
|
+
if @newliney
|
81
|
+
@output.newline
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def method_missing(method_name, *args, &block)
|
86
|
+
method_name = method_name.to_s
|
87
|
+
if method_name =~ /\!$/
|
88
|
+
id_str = method_name[0...-1]
|
89
|
+
raise ArgumentError, "setting id #{id_str} but id #{@attributes["id"]} already present" if @attributes["id"]
|
90
|
+
@attributes["id"] = id_str
|
91
|
+
else
|
92
|
+
if @attributes["class"]
|
93
|
+
@attributes["class"] += " "
|
94
|
+
else
|
95
|
+
@attributes["class"] = ""
|
96
|
+
end
|
97
|
+
@attributes["class"] += method_name.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
if block_given?
|
101
|
+
@inside_renderer = block
|
102
|
+
end
|
103
|
+
|
104
|
+
if args.last.is_a? Hash
|
105
|
+
attributes = args.pop
|
106
|
+
_set_attributes attributes
|
107
|
+
end
|
108
|
+
|
109
|
+
# todo: allow multiple args
|
110
|
+
# todo: allow promise args
|
111
|
+
@text = args.first
|
112
|
+
|
113
|
+
_render
|
114
|
+
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# are these accessors necessary?
|
119
|
+
|
120
|
+
def _tag_name
|
121
|
+
@tag_name
|
122
|
+
end
|
123
|
+
|
124
|
+
def _attributes
|
125
|
+
@attributes
|
126
|
+
end
|
127
|
+
|
128
|
+
def _open_tag
|
129
|
+
@open_tag
|
130
|
+
end
|
131
|
+
|
132
|
+
def _close_tag
|
133
|
+
@close_tag
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|