erector 0.5.1 → 0.6.3

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.
Files changed (34) hide show
  1. data/README.txt +6 -5
  2. data/VERSION.yml +5 -0
  3. data/bin/{erect → erector} +0 -0
  4. data/lib/erector.rb +3 -1
  5. data/lib/erector/erect.rb +1 -1
  6. data/lib/erector/erected.rb +1 -1
  7. data/lib/erector/rails.rb +2 -1
  8. data/lib/erector/rails/extensions/action_controller.rb +25 -7
  9. data/lib/erector/rails/extensions/rails_widget.rb +38 -0
  10. data/lib/erector/rails/extensions/{widget.rb → rails_widget/helpers.rb} +2 -9
  11. data/lib/erector/rails/rails_version.rb +6 -0
  12. data/lib/erector/rails/template_handlers/action_view_template_handler.rb +43 -11
  13. data/lib/erector/version.rb +4 -2
  14. data/lib/erector/widget.rb +221 -74
  15. data/lib/erector/widgets/table.rb +34 -8
  16. data/spec/erector/indentation_spec.rb +39 -24
  17. data/spec/erector/widget_spec.rb +197 -64
  18. data/spec/erector/widgets/table_spec.rb +3 -3
  19. data/spec/spec.opts +1 -0
  20. data/spec/spec_helper.rb +2 -4
  21. data/spec/spec_suite.rb +6 -12
  22. metadata +24 -70
  23. data/lib/erector/rails/extensions/action_controller/1.2.5/action_controller.rb +0 -17
  24. data/lib/erector/rails/extensions/action_controller/2.2.0/action_controller.rb +0 -26
  25. data/lib/erector/rails/extensions/widget/1.2.5/widget.rb +0 -18
  26. data/lib/erector/rails/extensions/widget/2.2.0/widget.rb +0 -23
  27. data/lib/erector/rails/supported_rails_versions.rb +0 -13
  28. data/lib/erector/rails/template_handlers/1.2.5/action_view_template_handler.rb +0 -32
  29. data/lib/erector/rails/template_handlers/2.0.0/action_view_template_handler.rb +0 -36
  30. data/lib/erector/rails/template_handlers/2.1.0/action_view_template_handler.rb +0 -31
  31. data/lib/erector/rails/template_handlers/2.2.0/action_view_template_handler.rb +0 -46
  32. data/spec/erect/erect_spec.rb +0 -145
  33. data/spec/erect/erected_spec.rb +0 -80
  34. data/spec/erect/rhtml_parser_spec.rb +0 -318
data/README.txt CHANGED
@@ -1,7 +1,8 @@
1
1
  = Erector
2
2
 
3
3
  * http://erector.rubyforge.org
4
- * mailto:erector-devel@rubyforge.org
4
+ * mailto:erector@googlegroups.com
5
+ * http://www.pivotaltracker.com/projects/482
5
6
 
6
7
  == DESCRIPTION
7
8
 
@@ -17,20 +18,20 @@ project site at http://erector.rubyforge.org for more documentation.
17
18
  require 'erector'
18
19
 
19
20
  class Hello < Erector::Widget
20
- def render
21
+ def content
21
22
  html do
22
23
  head do
23
24
  title "Hello"
24
25
  end
25
26
  body do
26
27
  text "Hello, "
27
- b "world!", :class => 'big'
28
+ b "#{target}!", :class => 'big'
28
29
  end
29
30
  end
30
31
  end
31
32
  end
32
33
 
33
- Hello.new.to_s
34
+ Hello.new(:target => 'world').to_s
34
35
  => "<html><head><title>Hello</title></head><body>Hello, <b class=\"big\">world!</b></body></html>"
35
36
 
36
37
  == REQUIREMENTS
@@ -58,7 +59,7 @@ When installing this way, erector is automatically available to your Rails code
58
59
 
59
60
  (The MIT License)
60
61
 
61
- Copyright (c) 2007-8 Pivotal Labs
62
+ Copyright (c) 2007-2009 Pivotal Labs
62
63
 
63
64
  Permission is hereby granted, free of charge, to any person obtaining
64
65
  a copy of this software and associated documentation files (the
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 6
4
+ :patch: 3
5
+
File without changes
data/lib/erector.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require "rubygems"
2
2
  dir = File.dirname(__FILE__)
3
3
  require 'cgi'
4
- require "activesupport"
4
+ require 'yaml'
5
+ require "active_support/inflector"
6
+ require "active_support/inflections"
5
7
  require "#{dir}/erector/extensions/object"
6
8
  require "#{dir}/erector/raw_string"
7
9
  require "#{dir}/erector/widget"
data/lib/erector/erect.rb CHANGED
@@ -11,7 +11,7 @@ module Erector
11
11
  @output_dir = nil
12
12
 
13
13
  opts = OptionParser.new do |opts|
14
- opts.banner = "Usage: erect [options] [file|dir]*"
14
+ opts.banner = "Usage: erector [options] [file|dir]*"
15
15
 
16
16
  opts.separator "Converts from html/rhtml files to erector widgets, or from erector widgets to html files"
17
17
  opts.separator ""
@@ -37,7 +37,7 @@ module Erector
37
37
  else
38
38
  File.open(filename, "w") do |f|
39
39
  f.puts("class #{classname} < Erector::Widget")
40
- f.puts(" def render")
40
+ f.puts(" def content")
41
41
  f.puts(parsed.set_indent(2).convert)
42
42
  f.puts(" end")
43
43
  f.puts("end")
data/lib/erector/rails.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  dir = File.dirname(__FILE__)
2
2
  require "action_controller"
3
- require "#{dir}/rails/extensions/widget"
3
+ require "#{dir}/rails/rails_version"
4
+ require "#{dir}/rails/extensions/rails_widget"
4
5
  require "#{dir}/rails/extensions/action_controller"
5
6
  require "#{dir}/rails/extensions/action_view"
6
7
  require "#{dir}/rails/template_handlers/action_view_template_handler"
@@ -1,8 +1,26 @@
1
- dir = File.dirname(__FILE__)
2
- if (
3
- ActionController::Base.instance_methods + ActionController::Base.private_instance_methods).
4
- include?("add_variables_to_assigns")
5
- require File.expand_path("#{dir}/action_controller/1.2.5/action_controller")
6
- else
7
- require File.expand_path("#{dir}/action_controller/2.2.0/action_controller")
1
+ ActionController::Base.class_eval do
2
+ def render_widget(widget_class, assigns=nil)
3
+ @__widget_class = widget_class
4
+ if assigns
5
+ @__widget_assigns = assigns
6
+ else
7
+ @__widget_assigns = {}
8
+ variables = instance_variable_names
9
+ variables -= protected_instance_variables
10
+ variables.each do |name|
11
+ @__widget_assigns[name.sub('@', "")] = instance_variable_get(name)
12
+ end
13
+ end
14
+ response.template.send(:_evaluate_assigns_and_ivars)
15
+ render :inline => "<% @__widget_class.new(@__widget_assigns).to_s(:output => output_buffer, :helpers => self) %>"
16
+ end
17
+
18
+ def render_with_erector_widget(*options, &block)
19
+ if options.first.is_a?(Hash) && widget = options.first.delete(:widget)
20
+ render_widget widget, @assigns, &block
21
+ else
22
+ render_without_erector_widget *options, &block
23
+ end
24
+ end
25
+ alias_method_chain :render, :erector_widget
8
26
  end
@@ -0,0 +1,38 @@
1
+ module Erector
2
+ class RailsWidget < Widget
3
+ def output
4
+ process_output_buffer || @output
5
+ end
6
+
7
+ def capture_with_helpers(&block)
8
+ helpers ? helpers.capture(&block) : capture_without_helpers(&block)
9
+ end
10
+
11
+ alias_method_chain :capture, :helpers
12
+
13
+ # This is here to force #helpers.capture to return the output
14
+ def __in_erb_template; end
15
+
16
+ private
17
+
18
+ def process_output_buffer
19
+ if helpers.respond_to?(:output_buffer)
20
+ buffer = helpers.output_buffer
21
+ buffer.is_a?(String) ? buffer : handle_rjs_buffer
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ def handle_rjs_buffer
28
+ returning buffer = helpers.output_buffer.dup.to_s do
29
+ helpers.output_buffer.clear
30
+ helpers.with_output_buffer(buffer) do
31
+ buffer << helpers.output_buffer.to_s
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ require "#{File.dirname(__FILE__)}/rails_widget/helpers"
@@ -1,5 +1,5 @@
1
1
  module Erector
2
- Widget.class_eval do
2
+ class RailsWidget < Widget
3
3
  include ActionController::UrlWriter
4
4
 
5
5
  # helpers returning raw text
@@ -105,13 +105,6 @@ module Erector
105
105
 
106
106
  def pluralize(*args)
107
107
  helpers.pluralize(*args)
108
- end
108
+ end
109
109
  end
110
110
  end
111
-
112
- dir = File.dirname(__FILE__)
113
- if ActionView::Base.instance_methods.include?("output_buffer")
114
- require "#{dir}/widget/2.2.0/widget"
115
- else
116
- require "#{dir}/widget/1.2.5/widget"
117
- end
@@ -0,0 +1,6 @@
1
+ module Erector
2
+ module Rails
3
+ RAILS_VERSION = "2.3.2"
4
+ RAILS_VERSION_TAG = "v2.3.2"
5
+ end
6
+ end
@@ -1,14 +1,46 @@
1
- dir = File.dirname(__FILE__)
2
- if ActionView.const_defined?(:TemplateHandlers)
3
- if ::ActionView::TemplateHandlers::const_defined?(:Compilable)
4
- if ActionView.const_defined?(:TemplateHandlers) && ::ActionView::TemplateHandlers::ERB.respond_to?(:erb_trim_mode)
5
- require File.expand_path("#{dir}/2.2.0/action_view_template_handler")
6
- else
7
- require File.expand_path("#{dir}/2.1.0/action_view_template_handler")
1
+ module ActionView #:nodoc:
2
+ module TemplateHandlers #:nodoc:
3
+ class Erector < TemplateHandler
4
+ include Compilable
5
+ def self.line_offset
6
+ 2
7
+ end
8
+
9
+ ActionView::Template.instance_eval do
10
+ register_template_handler :rb, ActionView::TemplateHandlers::Erector
11
+ end
12
+
13
+ def compile(template)
14
+ relative_path_parts = template.path.split('/')
15
+
16
+ is_partial = relative_path_parts.last =~ /^_/
17
+ require_dependency File.expand_path(template.filename)
18
+
19
+ widget_class_parts = relative_path_parts.inject(['Views']) do |class_parts, node|
20
+ class_parts << node.gsub(/^_/, "").gsub(/(\.html)?\.rb$/, '').camelize
21
+ class_parts
22
+ end
23
+ widget_class_name = widget_class_parts.join("::")
24
+ render_method = is_partial ? 'render_partial' : 'content'
25
+
26
+ erb_template = <<-ERB
27
+ <%
28
+ assigns = instance_variables.inject({}) do |hash, name|
29
+ hash[name.sub('@', "")] = instance_variable_get(name)
30
+ hash
31
+ end
32
+
33
+ widget = #{widget_class_name}.new(assigns)
34
+ widget.to_s(:output => output_buffer, :helpers => self, :content_method_name => :#{render_method})
35
+ %>
36
+ ERB
37
+ ::ERB.new(
38
+ erb_template,
39
+ nil,
40
+ ::ActionView::TemplateHandlers::ERB.erb_trim_mode,
41
+ "@output_buffer"
42
+ ).src
43
+ end
8
44
  end
9
- else
10
- require File.expand_path("#{dir}/2.0.0/action_view_template_handler")
11
45
  end
12
- else
13
- require File.expand_path("#{dir}/1.2.5/action_view_template_handler")
14
46
  end
@@ -1,8 +1,10 @@
1
- ##
1
+ ##
2
2
  # Erector view framework
3
3
  module Erector
4
4
  if !Erector.const_defined?(:VERSION)
5
- VERSION = "0.5.1"
5
+ dir = File.dirname(__FILE__)
6
+ version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
7
+ VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
6
8
  end
7
9
  end
8
10
 
@@ -3,17 +3,27 @@ module Erector
3
3
  # A Widget is the center of the Erector universe.
4
4
  #
5
5
  # To create a widget, extend Erector::Widget and implement
6
- # the +render+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
6
+ # the +content+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
7
7
  # tags.
8
8
  #
9
9
  # You can also define a widget on the fly by passing a block to +new+. This block will get executed when the widget's
10
- # +render+ method is called.
10
+ # +content+ method is called.
11
11
  #
12
12
  # To render a widget from the outside, instantiate it and call its +to_s+ method.
13
+ #
14
+ # A widget's +new+ method optionally accepts an options hash. Entries in this hash are converted to instance
15
+ # variables, and +attr_reader+ accessors are defined for each.
16
+ #
17
+ # TODO: You can add runtime input checking via the +needs+ macro. If any of the variables named via
18
+ # +needs+ are absent, an exception is thrown. Optional variables are specified with +wants+. If a variable appears
19
+ # in the options hash that is in neither the +needs+ nor +wants+ lists, then that too provokes an exception.
20
+ # This mechanism is meant to ameliorate development-time confusion about exactly what parameters are supported
21
+ # by a given widget, avoiding confusing runtime NilClass errors.
13
22
  #
14
- # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
15
- # its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output
16
- # is used, which gives better performance than using +capture+ or +to_s+.
23
+ # To call one widget from another, inside the parent widget's +content+ method, instantiate the child widget and call
24
+ # the +widget+ method. This assures that the same output stream
25
+ # is used, which gives better performance than using +capture+ or +to_s+. It also preserves the indentation and
26
+ # helpers of the enclosing class.
17
27
  #
18
28
  # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
19
29
  # *return* text. "Emit" means that it writes to the output stream; "return" means that it returns a string
@@ -64,14 +74,72 @@ module Erector
64
74
  raise ArgumentError, "You must provide either an instance or a block"
65
75
  end
66
76
  end
67
-
77
+
68
78
  protected
69
79
  def after_initialize_parts
70
80
  @after_initialize_parts ||= []
71
81
  end
72
82
  end
73
83
 
74
- cattr_accessor :prettyprint_default
84
+ # Class method by which widget classes can declare that they need certain parameters.
85
+ # If needed parameters are not passed in to #new, then an exception will be thrown
86
+ # (with a hopefully useful message about which parameters are missing). This is intended
87
+ # to catch silly bugs like passing in a parameter called 'name' to a widget that expects
88
+ # a parameter called 'title'. Every variable declared in 'needs' will get an attr_reader
89
+ # accessor declared for it.
90
+ #
91
+ # You can also declare default values for parameters using hash syntax. You can put #needs
92
+ # declarations on multiple lines or on the same line; the only caveat is that if there are
93
+ # default values, they all have to be at the end of the line (so they go into the magic
94
+ # hash parameter).
95
+ #
96
+ # If a widget has no #needs declaration then it will accept any combination of parameters
97
+ # (and make accessors for them) just like normal. In that case there will be no 'attr_reader's
98
+ # declared.
99
+ # If a widget wants to declare that it
100
+ # takes no parameters, use the special incantation "needs nil" (and don't declare any other
101
+ # needs, or kittens will cry).
102
+ #
103
+ # Usage:
104
+ # class FancyForm < Erector::Widget
105
+ # needs :title, :show_okay => true, :show_cancel => false
106
+ # ...
107
+ # end
108
+ #
109
+ # That means that
110
+ # FancyForm.new(:title => 'Login')
111
+ # will succeed, as will
112
+ # FancyForm.new(:title => 'Login', :show_cancel => true)
113
+ # but
114
+ # FancyForm.new(:name => 'Login')
115
+ # will fail.
116
+ #
117
+ def self.needs(*args)
118
+ args.each do |arg|
119
+ (@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym)
120
+ end
121
+ end
122
+
123
+ protected
124
+ def self.get_needs
125
+ @needs ||= []
126
+ parent = self.ancestors[1]
127
+ if parent.respond_to? :get_needs
128
+ parent.get_needs + @needs
129
+ else
130
+ @needs
131
+ end
132
+ end
133
+
134
+ public
135
+ @@prettyprint_default = false
136
+ def prettyprint_default
137
+ @@prettyprint_default
138
+ end
139
+
140
+ def self.prettyprint_default=(enabled)
141
+ @@prettyprint_default = enabled
142
+ end
75
143
 
76
144
  NON_NEWLINEY = {'i' => true, 'b' => true, 'small' => true,
77
145
  'img' => true, 'span' => true, 'a' => true,
@@ -80,95 +148,163 @@ module Erector
80
148
 
81
149
  SPACES_PER_INDENT = 2
82
150
 
83
- attr_reader :helpers, :assigns, :block, :parent, :output
84
- attr_accessor :enable_prettyprint
151
+ attr_reader :helpers, :assigns, :block, :parent, :output, :prettyprint, :indentation
85
152
 
86
- def initialize(helpers=nil, assigns={}, output = "", &block)
153
+ def initialize(assigns={}, &block)
154
+ unless assigns.is_a? Hash
155
+ raise "Erector's API has changed. Now you should pass only an options hash into Widget.new; the rest come in via to_s, or by using #widget."
156
+ end
157
+ if (respond_to? :render) &&
158
+ !self.method(:render).to_s.include?("(RailsWidget)")
159
+ raise "Erector's API has changed. You should rename #{self.class}#render to #content."
160
+ end
87
161
  @assigns = assigns
88
162
  assign_locals(assigns)
89
- @helpers = helpers
90
163
  @parent = block ? eval("self", block.binding) : nil
91
- @output = output
92
164
  @block = block
93
- @at_start_of_line = true
94
- @indent = 0
95
- @enable_prettyprint = prettyprint_default
96
165
  self.class.after_initialize self
97
166
  end
98
167
 
99
168
  #-- methods for other classes to call, left public for ease of testing and documentation
100
169
  #++
101
170
 
171
+ protected
172
+ def context(output, prettyprint = false, indentation = 0, helpers = nil)
173
+ #TODO: pass in options hash, maybe, instead of parameters
174
+ original_output = @output
175
+ original_indendation = @indentation
176
+ original_helpers = @helpers
177
+ original_prettyprint = @prettyprint
178
+ @output = output
179
+ @at_start_of_line = true
180
+ raise "indentation must be a number, not #{indentation.inspect}" unless indentation.is_a? Fixnum
181
+ @indentation = indentation
182
+ @helpers = helpers
183
+ @prettyprint = prettyprint
184
+ yield
185
+ ensure
186
+ @output = original_output
187
+ @indentation = original_indendation
188
+ @helpers = original_helpers
189
+ @prettyprint = original_prettyprint
190
+ end
191
+
192
+ public
102
193
  def assign_locals(local_assigns)
103
- local_assigns.each do |name, value|
104
- instance_variable_set("@#{name}", value)
194
+ needed = self.class.get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten
195
+ assigned = []
196
+ local_assigns.each do |name, value|
197
+ unless needed.empty? || needed.include?(name)
198
+ raise "Unknown parameter '#{name}'. #{self.class.name} only accepts #{needed.join(', ')}"
199
+ end
200
+ assign_local(name, value)
201
+ assigned << name
202
+ end
203
+
204
+ # set variables with default values
205
+ self.class.get_needs.select{|var| var.is_a? Hash}.each do |hash|
206
+ hash.each_pair do |name, value|
207
+ unless assigned.include?(name)
208
+ assign_local(name, value)
209
+ assigned << name
210
+ end
211
+ end
212
+ end
213
+
214
+ missing = needed - assigned
215
+ unless missing.empty? || missing == [nil]
216
+ raise "Missing parameter#{missing.size == 1 ? '' : 's'}: #{missing.join(', ')}"
217
+ end
218
+ end
219
+
220
+ def assign_local(name, value)
221
+ instance_variable_set("@#{name}", value)
222
+ if any_are_needed?
105
223
  metaclass.module_eval do
106
224
  attr_reader name
107
225
  end
108
226
  end
109
227
  end
110
228
 
111
- # Set whether Erector should add newlines and indentation in to_s.
112
- # This is an experimental feature and is subject to change
113
- # (either in terms of how it is enabled, or in terms of
114
- # what decisions Erector makes about where to add whitespace).
115
- # This flag should be set prior to any rendering being done
116
- # (for example, calls to to_s or to_pretty).
117
- def enable_prettyprint(enable)
118
- self.enable_prettyprint = enable
119
- self
229
+ def any_are_needed?
230
+ !self.class.get_needs.empty?
120
231
  end
121
-
232
+
122
233
  # Render (like to_s) but adding newlines and indentation.
234
+ # This is a convenience method; you may just want to call to_s(:prettyprint => true)
235
+ # so you can pass in other rendering options as well.
123
236
  def to_pretty
124
- enable_prettyprint(true).to_s
237
+ to_s(:prettyprint => true)
125
238
  end
126
239
 
127
- # Entry point for rendering a widget (and all its children). This method creates a new output string,
128
- # calls this widget's #render method and returns the string.
240
+ # Entry point for rendering a widget (and all its children). This method creates a new output string (if necessary),
241
+ # calls this widget's #content method and returns the string.
129
242
  #
130
- # If it's called again later
131
- # then it returns the earlier rendered string, which may lead to higher performance, but may have confusing
132
- # effects if some underlying state has changed. In general we recommend you create a new instance of every
133
- # widget for each render, unless you know what you're doing.
134
- def to_s(render_method_name=:render, &blk)
135
- # The @__to_s variable is used as a cache.
136
- # If it's useful we should add a test for it. -ac
137
- return @__to_s if @__to_s
138
- send(render_method_name, &blk)
139
- @__to_s = output.to_s
243
+ # Options:
244
+ # output:: the string to output to. Default: a new empty string
245
+ # prettyprint:: whether Erector should add newlines and indentation. Default: the value of prettyprint_default (which is false by default).
246
+ # indentation:: the amount of spaces to indent. Ignored unless prettyprint is true.
247
+ # helpers:: a helpers object containing utility methods. Usually this is a Rails view object.
248
+ # content_method_name:: in case you want to call a method other than #content, pass its name in here.
249
+ #
250
+ # Note: Prettyprinting is an experimental feature and is subject to change
251
+ # (either in terms of how it is enabled, or in terms of
252
+ # what decisions Erector makes about where to add whitespace).
253
+ def to_s(options = {}, &blk)
254
+
255
+ raise "Erector::Widget#to_s now takes an options hash, not a symbol. Try calling \"to_s(:content_method_name=> :#{options})\"" if options.is_a? Symbol
256
+
257
+ options = {
258
+ :output => "",
259
+ :prettyprint => prettyprint_default,
260
+ :indentation => 0,
261
+ :helpers => nil,
262
+ :content_method_name => :content,
263
+ }.merge(options)
264
+ context(options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
265
+ send(options[:content_method_name], &blk)
266
+ output.to_s
267
+ end
140
268
  end
141
269
 
142
270
  alias_method :inspect, :to_s
143
-
271
+
144
272
  # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
145
273
  # #element methods which emit HTML and text to the output string.
146
- def render
274
+ def content
147
275
  if @block
148
276
  instance_eval(&@block)
149
277
  end
150
278
  end
151
279
 
152
- # To call one widget from another, inside the parent widget's render method, instantiate the child widget and call
153
- # its +render_to+ method, passing in +self+ (or self.output if you prefer). This assures that the same output string
280
+ # To call one widget from another, inside the parent widget's +content+ method, instantiate the child widget and call
281
+ # its +write_via+ method, passing in +self+. This assures that the same output string
154
282
  # is used, which gives better performance than using +capture+ or +to_s+.
155
- def render_to(output_or_widget)
156
- if output_or_widget.is_a?(Widget)
157
- @parent = output_or_widget
158
- @output = @parent.output
159
- else
160
- @output = output_or_widget
283
+ # You can also use the +widget+ method.
284
+ def write_via(parent)
285
+ @parent = parent
286
+ context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
287
+ content
161
288
  end
162
- render
163
289
  end
164
290
 
165
- # Convenience method for on-the-fly widgets. This is a way of making
166
- # a sub-widget which still has access to the methods of the parent class.
167
- # This is an experimental erector feature which may disappear in future
168
- # versions of erector (see #widget in widget_spec in the Erector tests).
169
- def widget(widget_class, assigns={}, &block)
170
- child = widget_class.new(helpers, assigns, output, &block)
171
- child.render
291
+ # Emits a (nested) widget onto the current widget's output stream. Accepts either
292
+ # a class or an instance. If the first argument is a class, then the second argument
293
+ # is a hash used to populate its instance variables. If the first argument is an
294
+ # instance then the hash must be unspecified (or empty).
295
+ #
296
+ # The sub-widget will have access to the methods of the parent class, via some method_missing
297
+ # magic and a "parent" pointer.
298
+ def widget(target, assigns={}, &block)
299
+ child = if target.is_a? Class
300
+ target.new(assigns, &block)
301
+ else
302
+ unless assigns.empty?
303
+ raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?"
304
+ end
305
+ target
306
+ end
307
+ child.write_via(self)
172
308
  end
173
309
 
174
310
  # (Should we make this hidden?)
@@ -220,7 +356,7 @@ module Erector
220
356
  # Emits an open tag, comprising '<', tag name, optional attributes, and '>'
221
357
  def open_tag(tag_name, attributes={})
222
358
  indent_for_open_tag(tag_name)
223
- @indent += SPACES_PER_INDENT
359
+ @indentation += SPACES_PER_INDENT
224
360
 
225
361
  output.concat "<#{tag_name}#{format_attributes(attributes)}>"
226
362
  @at_start_of_line = false
@@ -232,7 +368,11 @@ module Erector
232
368
  # If another kind of object is passed in, the result of calling
233
369
  # its to_s method will be treated as a string would be.
234
370
  def text(value)
235
- output.concat(value.html_escape)
371
+ if value.is_a? Widget
372
+ widget value
373
+ else
374
+ output.concat(value.html_escape)
375
+ end
236
376
  @at_start_of_line = false
237
377
  nil
238
378
  end
@@ -272,20 +412,19 @@ module Erector
272
412
 
273
413
  # Emits a close tag, consisting of '<', tag name, and '>'
274
414
  def close_tag(tag_name)
275
- @indent -= SPACES_PER_INDENT
415
+ @indentation -= SPACES_PER_INDENT
276
416
  indent()
277
417
 
278
418
  output.concat("</#{tag_name}>")
279
419
 
280
420
  if newliney(tag_name)
281
- output.concat "\n"
282
- @at_start_of_line = true
421
+ _newline
283
422
  end
284
423
  end
285
424
 
286
425
  # Emits the result of joining the elements in array with the separator.
287
426
  # The array elements and separator can be Erector::Widget objects,
288
- # which are rendered, or strings, which are quoted and output.
427
+ # which are rendered, or strings, which are html-escaped and output.
289
428
  def join(array, separator)
290
429
  first = true
291
430
  array.each do |widget_or_text|
@@ -304,7 +443,7 @@ module Erector
304
443
 
305
444
  # Creates a whole new output string, executes the block, then converts the output string to a string and
306
445
  # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
307
- # and use #render_to instead.
446
+ # and use +content+ or +write_via+ instead.
308
447
  def capture(&block)
309
448
  begin
310
449
  original_output = output
@@ -375,10 +514,11 @@ module Erector
375
514
  rawtext "\n"
376
515
  end
377
516
 
378
- # Convenience method to emit a css file link, which looks like this: <link href="erector.css" rel="stylesheet" type="text/css" />
517
+ # Convenience method to emit a css file link, which looks like this:
518
+ # <link href="erector.css" rel="stylesheet" type="text/css" />
379
519
  # The parameter is the full contents of the href attribute, including any ".css" extension.
380
520
  #
381
- # If you want to emit raw CSS inline, use the #script method instead.
521
+ # If you want to emit raw CSS inline, use the #style method instead.
382
522
  def css(href)
383
523
  link :rel => 'stylesheet', :type => 'text/css', :href => href
384
524
  end
@@ -389,7 +529,7 @@ module Erector
389
529
  end
390
530
 
391
531
  def newliney(tag_name)
392
- if @enable_prettyprint
532
+ if @prettyprint
393
533
  !NON_NEWLINEY.include?(tag_name)
394
534
  else
395
535
  false
@@ -427,6 +567,9 @@ protected
427
567
  end
428
568
  attributes ||= {}
429
569
  open_tag tag_name, attributes
570
+ if block && value
571
+ raise ArgumentError, "You can't pass both a block and a value to #{tag_name} -- please choose one."
572
+ end
430
573
  if block
431
574
  instance_eval(&block)
432
575
  else
@@ -441,23 +584,27 @@ protected
441
584
  output.concat "<#{tag_name}#{format_attributes(attributes)} />"
442
585
 
443
586
  if newliney(tag_name)
444
- output.concat "\n"
445
- @at_start_of_line = true
587
+ _newline
446
588
  end
447
589
  end
590
+
591
+ def _newline
592
+ return unless @prettyprint
593
+ output.concat "\n"
594
+ @at_start_of_line = true
595
+ end
448
596
 
449
597
  def indent_for_open_tag(tag_name)
598
+ return unless @prettyprint
450
599
  if !@at_start_of_line && newliney(tag_name)
451
- output.concat "\n"
452
- @at_start_of_line = true
600
+ _newline
453
601
  end
454
-
455
602
  indent()
456
603
  end
457
604
 
458
605
  def indent()
459
606
  if @at_start_of_line
460
- output.concat " " * @indent
607
+ output.concat " " * @indentation
461
608
  end
462
609
  end
463
610