erector 0.8.3 → 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/Gemfile +21 -0
  2. data/Rakefile +171 -0
  3. data/VERSION.yml +3 -2
  4. data/lib/erector.rb +3 -5
  5. data/lib/erector/abstract_widget.rb +76 -31
  6. data/lib/erector/attributes.rb +34 -0
  7. data/lib/erector/caching.rb +1 -0
  8. data/lib/erector/convenience.rb +12 -4
  9. data/lib/erector/dependency.rb +2 -1
  10. data/lib/erector/element.rb +113 -0
  11. data/lib/erector/erect/erect.rb +10 -7
  12. data/lib/erector/externals.rb +2 -1
  13. data/lib/erector/html.rb +6 -340
  14. data/lib/erector/html_widget.rb +300 -0
  15. data/lib/erector/inline.rb +1 -1
  16. data/lib/erector/output.rb +39 -12
  17. data/lib/erector/promise.rb +137 -0
  18. data/lib/erector/rails2/extensions/rails_widget.rb +1 -1
  19. data/lib/erector/rails3.rb +1 -1
  20. data/lib/erector/sass.rb +14 -19
  21. data/lib/erector/tag.rb +65 -0
  22. data/lib/erector/text.rb +123 -0
  23. data/lib/erector/version.rb +1 -1
  24. data/lib/erector/widget.rb +52 -12
  25. data/lib/erector/widgets/page.rb +12 -10
  26. data/lib/erector/xml_widget.rb +131 -0
  27. data/spec/erector/caching_spec.rb +1 -0
  28. data/spec/erector/externals_spec.rb +0 -1
  29. data/spec/erector/html_spec.rb +9 -25
  30. data/spec/erector/output_spec.rb +102 -9
  31. data/spec/erector/promise_spec.rb +173 -0
  32. data/spec/erector/sass_spec.rb +1 -1
  33. data/spec/erector/tag_spec.rb +67 -0
  34. data/spec/erector/widget_spec.rb +53 -2
  35. data/spec/erector/xml_widget_spec.rb +74 -0
  36. data/spec/rails2/rails_app/Gemfile +1 -0
  37. data/spec/rails2/rails_app/spec/rails_spec_helper.rb +2 -0
  38. data/spec/rails_root/Gemfile +11 -0
  39. data/spec/rails_root/README +256 -0
  40. data/spec/rails_root/Rakefile +7 -0
  41. data/spec/rails_root/app/controllers/application.rb +6 -0
  42. data/spec/rails_root/app/controllers/application_controller.rb +3 -0
  43. data/spec/rails_root/app/helpers/application_helper.rb +2 -0
  44. data/spec/rails_root/app/views/layouts/application.html.erb +14 -0
  45. data/spec/rails_root/app/views/test/_erb.erb +1 -0
  46. data/spec/rails_root/app/views/test/_erector.rb +5 -0
  47. data/spec/rails_root/app/views/test/_partial_with_locals.rb +7 -0
  48. data/spec/rails_root/app/views/test/bare.rb +5 -0
  49. data/spec/rails_root/app/views/test/erb_from_erector.html.rb +5 -0
  50. data/spec/rails_root/app/views/test/erector_from_erb.html.erb +1 -0
  51. data/spec/rails_root/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
  52. data/spec/rails_root/app/views/test/implicit_assigns.html.rb +5 -0
  53. data/spec/rails_root/app/views/test/needs.html.rb +7 -0
  54. data/spec/rails_root/app/views/test/needs_subclass.html.rb +5 -0
  55. data/spec/rails_root/app/views/test/protected_instance_variable.html.rb +5 -0
  56. data/spec/rails_root/app/views/test/render_default.html.rb +5 -0
  57. data/spec/rails_root/app/views/test/render_partial.html.rb +5 -0
  58. data/spec/rails_root/config.ru +4 -0
  59. data/spec/rails_root/config/application.rb +42 -0
  60. data/spec/rails_root/config/boot.rb +13 -0
  61. data/spec/rails_root/config/database.yml +22 -0
  62. data/spec/rails_root/config/environment.rb +5 -0
  63. data/spec/rails_root/config/environments/development.rb +22 -0
  64. data/spec/rails_root/config/environments/production.rb +49 -0
  65. data/spec/rails_root/config/environments/test.rb +35 -0
  66. data/spec/rails_root/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/rails_root/config/initializers/inflections.rb +10 -0
  68. data/spec/rails_root/config/initializers/mime_types.rb +5 -0
  69. data/spec/rails_root/config/initializers/secret_token.rb +7 -0
  70. data/spec/rails_root/config/initializers/session_store.rb +8 -0
  71. data/spec/rails_root/config/locales/en.yml +5 -0
  72. data/spec/rails_root/config/routes.rb +58 -0
  73. data/spec/rails_root/db/seeds.rb +7 -0
  74. data/spec/rails_root/doc/README_FOR_APP +2 -0
  75. data/spec/rails_root/log/development.log +17 -0
  76. data/spec/rails_root/log/test.log +3750 -0
  77. data/spec/rails_root/public/404.html +26 -0
  78. data/spec/rails_root/public/422.html +26 -0
  79. data/spec/rails_root/public/500.html +26 -0
  80. data/spec/rails_root/public/dispatch.cgi +10 -0
  81. data/spec/rails_root/public/dispatch.fcgi +24 -0
  82. data/spec/rails_root/public/dispatch.rb +10 -0
  83. data/spec/rails_root/public/favicon.ico +0 -0
  84. data/spec/rails_root/public/images/rails.png +0 -0
  85. data/spec/rails_root/public/index.html +262 -0
  86. data/spec/rails_root/public/javascripts/application.js +2 -0
  87. data/spec/rails_root/public/javascripts/controls.js +965 -0
  88. data/spec/rails_root/public/javascripts/dragdrop.js +974 -0
  89. data/spec/rails_root/public/javascripts/effects.js +1123 -0
  90. data/spec/rails_root/public/javascripts/prototype.js +6001 -0
  91. data/spec/rails_root/public/javascripts/rails.js +175 -0
  92. data/spec/rails_root/public/robots.txt +5 -0
  93. data/spec/rails_root/script/about +3 -0
  94. data/spec/rails_root/script/console +3 -0
  95. data/spec/rails_root/script/destroy +3 -0
  96. data/spec/rails_root/script/generate +3 -0
  97. data/spec/rails_root/script/performance/benchmarker +3 -0
  98. data/spec/rails_root/script/performance/profiler +3 -0
  99. data/spec/rails_root/script/performance/request +3 -0
  100. data/spec/rails_root/script/plugin +3 -0
  101. data/spec/rails_root/script/process/inspector +3 -0
  102. data/spec/rails_root/script/process/reaper +3 -0
  103. data/spec/rails_root/script/process/spawner +3 -0
  104. data/spec/rails_root/script/rails +6 -0
  105. data/spec/rails_root/script/runner +3 -0
  106. data/spec/rails_root/script/server +3 -0
  107. data/spec/rails_root/spec/form_builder_spec.rb +21 -0
  108. data/spec/rails_root/spec/rails_helpers_spec.rb +220 -0
  109. data/spec/rails_root/spec/rails_spec_helper.rb +10 -0
  110. data/spec/rails_root/spec/rails_widget_spec.rb +83 -0
  111. data/spec/rails_root/spec/render_spec.rb +298 -0
  112. data/spec/rails_root/test/performance/browsing_test.rb +9 -0
  113. data/spec/rails_root/test/test_helper.rb +13 -0
  114. data/spec/spec_helper.rb +3 -1
  115. 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 (&lt;!-- ... --&gt;) 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
@@ -30,7 +30,7 @@ module Erector
30
30
  end
31
31
  end
32
32
 
33
- class InlineWidget < Erector::Widget
33
+ class InlineWidget < Widget
34
34
  include Inline
35
35
  end
36
36
 
@@ -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 = {}, & get_buffer)
8
- @prettyprint = options.fetch(:prettyprint, Widget.prettyprint_default)
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
- if get_buffer
14
- @get_buffer = get_buffer
15
- elsif buffer = options[:output]
16
- @get_buffer = lambda { buffer }
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
- @get_buffer = lambda { buffer }
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 it.
55
- # If the caller holds on to this pointer, she can later go back and insert text
56
- # earlier in the stream. This is used for, e.g., inserting stuff inside the
57
- # HEAD element that is not known until after the entire page renders.
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