pivotal-erector 0.6.3 → 0.6.4

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.
@@ -2,45 +2,49 @@ module Erector
2
2
 
3
3
  # A Widget is the center of the Erector universe.
4
4
  #
5
- # To create a widget, extend Erector::Widget and implement
6
- # the +content+ method. Inside this method you may call any of the tag methods like +span+ or +p+ to emit HTML/XML
7
- # tags.
8
- #
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
- # +content+ method is called.
5
+ # To create a widget, extend Erector::Widget and implement the +content+
6
+ # method. Inside this method you may call any of the tag methods like +span+
7
+ # or +p+ to emit HTML/XML tags.
8
+ #
9
+ # You can also define a widget on the fly by passing a block to +new+. This
10
+ # block will get executed when the widget's +content+ method is called.
11
11
  #
12
- # To render a widget from the outside, instantiate it and call its +to_s+ method.
12
+ # To render a widget from the outside, instantiate it and call its +to_s+
13
+ # method.
13
14
  #
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.
15
+ # A widget's +new+ method optionally accepts an options hash. Entries in
16
+ # this hash are converted to instance variables, and +attr_reader+ accessors
17
+ # are defined for each.
16
18
  #
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.
22
- #
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.
27
- #
28
- # In this documentation we've tried to keep the distinction clear between methods that *emit* text and those that
29
- # *return* text. "Emit" means that it writes to the output stream; "return" means that it returns a string
30
- # like a normal method and leaves it up to the caller to emit that string if it wants.
19
+ # You can add runtime input checking via the +needs+ macro. See #needs.
20
+ # This mechanism is meant to ameliorate development-time confusion about
21
+ # exactly what parameters are supported by a given widget, avoiding
22
+ # confusing runtime NilClass errors.
23
+ #
24
+ # To call one widget from another, inside the parent widget's +content+
25
+ # method, instantiate the child widget and call the +widget+ method. This
26
+ # assures that the same output stream is used, which gives better
27
+ # performance than using +capture+ or +to_s+. It also preserves the
28
+ # indentation and helpers of the enclosing class.
29
+ #
30
+ # In this documentation we've tried to keep the distinction clear between
31
+ # methods that *emit* text and those that *return* text. "Emit" means that
32
+ # it writes to the output stream; "return" means that it returns a string
33
+ # like a normal method and leaves it up to the caller to emit that string if
34
+ # it wants.
31
35
  class Widget
32
36
  class << self
33
37
  def all_tags
34
38
  Erector::Widget.full_tags + Erector::Widget.empty_tags
35
39
  end
36
40
 
37
- # tags which are always self-closing
41
+ # Tags which are always self-closing. Click "[Source]" to see the full list.
38
42
  def empty_tags
39
43
  ['area', 'base', 'br', 'col', 'frame',
40
44
  'hr', 'img', 'input', 'link', 'meta']
41
45
  end
42
46
 
43
- # tags which can contain other stuff
47
+ # Tags which can contain other stuff. Click "[Source]" to see the full list.
44
48
  def full_tags
45
49
  [
46
50
  'a', 'abbr', 'acronym', 'address',
@@ -81,36 +85,37 @@ module Erector
81
85
  end
82
86
  end
83
87
 
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.
88
+ # Class method by which widget classes can declare that they need certain
89
+ # parameters. If needed parameters are not passed in to #new, then an
90
+ # exception will be thrown (with a hopefully useful message about which
91
+ # parameters are missing). This is intended to catch silly bugs like
92
+ # passing in a parameter called 'name' to a widget that expects a
93
+ # parameter called 'title'. Every variable declared in 'needs' will get an
94
+ # attr_reader accessor declared for it.
90
95
  #
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).
96
+ # You can also declare default values for parameters using hash syntax.
97
+ # You can put #needs declarations on multiple lines or on the same line;
98
+ # the only caveat is that if there are default values, they all have to be
99
+ # at the end of the line (so they go into the magic hash parameter).
95
100
  #
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).
101
+ # If a widget has no #needs declaration then it will accept any
102
+ # combination of parameters (and make accessors for them) just like
103
+ # normal. In that case there will be no 'attr_reader's declared. If a
104
+ # widget wants to declare that it takes no parameters, use the special
105
+ # incantation "needs nil" (and don't declare any other needs, or kittens
106
+ # will cry).
102
107
  #
103
108
  # Usage:
104
109
  # class FancyForm < Erector::Widget
105
110
  # needs :title, :show_okay => true, :show_cancel => false
106
111
  # ...
107
112
  # end
108
- #
109
- # That means that
113
+ #
114
+ # That means that
110
115
  # FancyForm.new(:title => 'Login')
111
- # will succeed, as will
116
+ # will succeed, as will
112
117
  # FancyForm.new(:title => 'Login', :show_cancel => true)
113
- # but
118
+ # but
114
119
  # FancyForm.new(:name => 'Login')
115
120
  # will fail.
116
121
  #
@@ -237,25 +242,38 @@ module Erector
237
242
  to_s(:prettyprint => true)
238
243
  end
239
244
 
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.
245
+ # Entry point for rendering a widget (and all its children). This method
246
+ # creates a new output string (if necessary), calls this widget's #content
247
+ # method and returns the string.
242
248
  #
243
249
  # Options:
244
250
  # 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).
251
+ # prettyprint:: whether Erector should add newlines and indentation.
252
+ # Default: the value of prettyprint_default (which is false
253
+ # by default).
254
+ # indentation:: the amount of spaces to indent. Ignored unless prettyprint
255
+ # is true.
256
+ # helpers:: a helpers object containing utility methods. Usually this is a
257
+ # Rails view object.
258
+ # content_method_name:: in case you want to call a method other than
259
+ # #content, pass its name in here.
253
260
  def to_s(options = {}, &blk)
254
-
255
261
  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
-
262
+ _render(options, &blk).to_s
263
+ end
264
+
265
+ # Entry point for rendering a widget (and all its children). Same as #to_s
266
+ # only it returns an array, for theoretical performance improvements when using a
267
+ # Rack server (like Sinatra or Rails Metal).
268
+ #
269
+ # # Options: see #to_s
270
+ def to_a(options = {}, &blk)
271
+ _render(options, &blk).to_a
272
+ end
273
+
274
+ def _render(options = {}, &blk)
257
275
  options = {
258
- :output => "",
276
+ :output => "", # "" is apparently faster than [] in a long-running process
259
277
  :prettyprint => prettyprint_default,
260
278
  :indentation => 0,
261
279
  :helpers => nil,
@@ -263,24 +281,29 @@ module Erector
263
281
  }.merge(options)
264
282
  context(options[:output], options[:prettyprint], options[:indentation], options[:helpers]) do
265
283
  send(options[:content_method_name], &blk)
266
- output.to_s
284
+ output
267
285
  end
268
286
  end
269
287
 
270
288
  alias_method :inspect, :to_s
271
289
 
272
- # Template method which must be overridden by all widget subclasses. Inside this method you call the magic
273
- # #element methods which emit HTML and text to the output string.
290
+ # Template method which must be overridden by all widget subclasses.
291
+ # Inside this method you call the magic #element methods which emit HTML
292
+ # and text to the output string. If you call "super" (or don't override
293
+ # +content+) then your widget will render any block that was passed into
294
+ # its constructor (in the current instance context so it can get access
295
+ # to parent widget methods via method_missing).
274
296
  def content
275
297
  if @block
276
298
  instance_eval(&@block)
277
299
  end
278
300
  end
279
301
 
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
282
- # is used, which gives better performance than using +capture+ or +to_s+.
283
- # You can also use the +widget+ method.
302
+ # To call one widget from another, inside the parent widget's +content+
303
+ # method, instantiate the child widget and call its +write_via+ method,
304
+ # passing in +self+. This assures that the same output string is used,
305
+ # which gives better performance than using +capture+ or +to_s+. You can
306
+ # also use the +widget+ method.
284
307
  def write_via(parent)
285
308
  @parent = parent
286
309
  context(parent.output, parent.prettyprint, parent.indentation, parent.helpers) do
@@ -288,13 +311,14 @@ module Erector
288
311
  end
289
312
  end
290
313
 
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).
314
+ # Emits a (nested) widget onto the current widget's output stream. Accepts
315
+ # either a class or an instance. If the first argument is a class, then
316
+ # the second argument is a hash used to populate its instance variables.
317
+ # If the first argument is an instance then the hash must be unspecified
318
+ # (or empty).
295
319
  #
296
- # The sub-widget will have access to the methods of the parent class, via some method_missing
297
- # magic and a "parent" pointer.
320
+ # The sub-widget will have access to the methods of the parent class, via
321
+ # some method_missing magic and a "parent" pointer.
298
322
  def widget(target, assigns={}, &block)
299
323
  child = if target.is_a? Class
300
324
  target.new(assigns, &block)
@@ -315,40 +339,46 @@ module Erector
315
339
  #-- methods for subclasses to call
316
340
  #++
317
341
 
318
- # Internal method used to emit an HTML/XML element, including an open tag, attributes (optional, via the default hash),
319
- # contents (also optional), and close tag.
342
+ # Internal method used to emit an HTML/XML element, including an open tag,
343
+ # attributes (optional, via the default hash), contents (also optional),
344
+ # and close tag.
320
345
  #
321
- # Using the arcane powers of Ruby, there are magic methods that call +element+ for all the standard
322
- # HTML tags, like +a+, +body+, +p+, and so forth. Look at the source of #full_tags for the full list.
323
- # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
324
- # us, they're there.
346
+ # Using the arcane powers of Ruby, there are magic methods that call
347
+ # +element+ for all the standard HTML tags, like +a+, +body+, +p+, and so
348
+ # forth. Look at the source of #full_tags for the full list.
349
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method
350
+ # in this rdoc page, but trust us, they're there.
325
351
  #
326
- # When calling one of these magic methods, put attributes in the default hash. If there is a string parameter,
327
- # then it is used as the contents. If there is a block, then it is executed (yielded), and the string parameter is ignored.
328
- # The block will usually be in the scope of the child widget, which means it has access to all the
329
- # methods of Widget, which will eventually end up appending text to the +output+ string. See how
330
- # elegant it is? Not confusing at all if you don't think about it.
352
+ # When calling one of these magic methods, put attributes in the default
353
+ # hash. If there is a string parameter, then it is used as the contents.
354
+ # If there is a block, then it is executed (yielded), and the string
355
+ # parameter is ignored. The block will usually be in the scope of the
356
+ # child widget, which means it has access to all the methods of Widget,
357
+ # which will eventually end up appending text to the +output+ string. See
358
+ # how elegant it is? Not confusing at all if you don't think about it.
331
359
  #
332
360
  def element(*args, &block)
333
361
  __element__(*args, &block)
334
362
  end
335
363
 
336
- # Internal method used to emit a self-closing HTML/XML element, including a tag name and optional attributes
337
- # (passed in via the default hash).
338
- #
339
- # Using the arcane powers of Ruby, there are magic methods that call +empty_element+ for all the standard
340
- # HTML tags, like +img+, +br+, and so forth. Look at the source of #empty_tags for the full list.
341
- # Unfortunately, this big mojo confuses rdoc, so we can't see each method in this rdoc page, but trust
342
- # us, they're there.
364
+ # Internal method used to emit a self-closing HTML/XML element, including
365
+ # a tag name and optional attributes (passed in via the default hash).
366
+ #
367
+ # Using the arcane powers of Ruby, there are magic methods that call
368
+ # +empty_element+ for all the standard HTML tags, like +img+, +br+, and so
369
+ # forth. Look at the source of #empty_tags for the full list.
370
+ # Unfortunately, this big mojo confuses rdoc, so we can't see each method
371
+ # in this rdoc page, but trust us, they're there.
343
372
  #
344
373
  def empty_element(*args, &block)
345
374
  __empty_element__(*args, &block)
346
375
  end
347
376
 
348
- # Returns an HTML-escaped version of its parameter. Leaves the output string untouched. Note that
349
- # the #text method automatically HTML-escapes its parameter, so be careful *not* to do something like
350
- # text(h("2<4")) since that will double-escape the less-than sign (you'll get "2&amp;lt;4" instead of
351
- # "2&lt;4").
377
+ # Returns an HTML-escaped version of its parameter. Leaves the output
378
+ # string untouched. Note that the #text method automatically HTML-escapes
379
+ # its parameter, so be careful *not* to do something like text(h("2<4"))
380
+ # since that will double-escape the less-than sign (you'll get
381
+ # "2&amp;lt;4" instead of "2&lt;4").
352
382
  def h(content)
353
383
  content.html_escape
354
384
  end
@@ -358,20 +388,20 @@ module Erector
358
388
  indent_for_open_tag(tag_name)
359
389
  @indentation += SPACES_PER_INDENT
360
390
 
361
- output.concat "<#{tag_name}#{format_attributes(attributes)}>"
391
+ output << "<#{tag_name}#{format_attributes(attributes)}>"
362
392
  @at_start_of_line = false
363
393
  end
364
394
 
365
- # Emits text. If a string is passed in, it will be HTML-escaped.
366
- # If a widget or the result of calling methods such as raw
367
- # is passed in, the HTML will not be HTML-escaped again.
368
- # If another kind of object is passed in, the result of calling
369
- # its to_s method will be treated as a string would be.
395
+ # Emits text. If a string is passed in, it will be HTML-escaped. If a
396
+ # widget or the result of calling methods such as raw is passed in, the
397
+ # HTML will not be HTML-escaped again. If another kind of object is passed
398
+ # in, the result of calling its to_s method will be treated as a string
399
+ # would be.
370
400
  def text(value)
371
401
  if value.is_a? Widget
372
402
  widget value
373
403
  else
374
- output.concat(value.html_escape)
404
+ output <<(value.html_escape)
375
405
  end
376
406
  @at_start_of_line = false
377
407
  nil
@@ -410,12 +440,12 @@ module Erector
410
440
  end
411
441
  end
412
442
 
413
- # Emits a close tag, consisting of '<', tag name, and '>'
443
+ # Emits a close tag, consisting of '<', '/', tag name, and '>'
414
444
  def close_tag(tag_name)
415
445
  @indentation -= SPACES_PER_INDENT
416
446
  indent()
417
447
 
418
- output.concat("</#{tag_name}>")
448
+ output <<("</#{tag_name}>")
419
449
 
420
450
  if newliney(tag_name)
421
451
  _newline
@@ -438,12 +468,13 @@ module Erector
438
468
 
439
469
  # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\"?>
440
470
  def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
441
- output.concat "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
471
+ output << "<?xml#{format_sorted(sort_for_xml_declaration(attributes))}?>"
442
472
  end
443
473
 
444
- # Creates a whole new output string, executes the block, then converts the output string to a string and
445
- # emits it as raw text. If at all possible you should avoid this method since it hurts performance,
446
- # and use +content+ or +write_via+ instead.
474
+ # Creates a whole new output string, executes the block, then converts the
475
+ # output string to a string and emits it as raw text. If at all possible
476
+ # you should avoid this method since it hurts performance, and use
477
+ # +widget+ or +write_via+ instead.
447
478
  def capture(&block)
448
479
  begin
449
480
  original_output = output
@@ -475,7 +506,8 @@ module Erector
475
506
  )
476
507
  end
477
508
 
478
- # Emits a javascript block inside a +script+ tag, wrapped in CDATA doohickeys like all the cool JS kids do.
509
+ # Emits a javascript block inside a +script+ tag, wrapped in CDATA
510
+ # doohickeys like all the cool JS kids do.
479
511
  def javascript(*args, &block)
480
512
  if args.length > 2
481
513
  raise ArgumentError, "Cannot accept more than two arguments"
@@ -514,16 +546,17 @@ module Erector
514
546
  rawtext "\n"
515
547
  end
516
548
 
517
- # Convenience method to emit a css file link, which looks like this:
549
+ # Convenience method to emit a css file link, which looks like this:
518
550
  # <link href="erector.css" rel="stylesheet" type="text/css" />
519
- # The parameter is the full contents of the href attribute, including any ".css" extension.
551
+ # The parameter is the full contents of the href attribute, including any ".css" extension.
520
552
  #
521
553
  # If you want to emit raw CSS inline, use the #style method instead.
522
554
  def css(href)
523
555
  link :rel => 'stylesheet', :type => 'text/css', :href => href
524
556
  end
525
557
 
526
- # 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>
558
+ # Convenience method to emit an anchor tag whose href and text are the same,
559
+ # e.g. <a href="http://example.com">http://example.com</a>
527
560
  def url(href)
528
561
  a href, :href => href
529
562
  end
@@ -581,7 +614,7 @@ protected
581
614
  def __empty_element__(tag_name, attributes={})
582
615
  indent_for_open_tag(tag_name)
583
616
 
584
- output.concat "<#{tag_name}#{format_attributes(attributes)} />"
617
+ output << "<#{tag_name}#{format_attributes(attributes)} />"
585
618
 
586
619
  if newliney(tag_name)
587
620
  _newline
@@ -590,7 +623,7 @@ protected
590
623
 
591
624
  def _newline
592
625
  return unless @prettyprint
593
- output.concat "\n"
626
+ output << "\n"
594
627
  @at_start_of_line = true
595
628
  end
596
629
 
@@ -604,7 +637,7 @@ protected
604
637
 
605
638
  def indent()
606
639
  if @at_start_of_line
607
- output.concat " " * @indentation
640
+ output << " " * [@indentation, 0].max
608
641
  end
609
642
  end
610
643
 
data/lib/erector.rb CHANGED
@@ -10,6 +10,7 @@ require "#{dir}/erector/widget"
10
10
  require "#{dir}/erector/unicode"
11
11
  require "#{dir}/erector/widgets"
12
12
  require "#{dir}/erector/version"
13
+ require "#{dir}/erector/mixin"
13
14
  if Object.const_defined?(:RAILS_ROOT)
14
15
  require "#{dir}/erector/rails"
15
16
  end
@@ -0,0 +1,62 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
+
3
+ require "erector/rails"
4
+
5
+ # Note: this is *not* inside the rails_root since we're not testing
6
+ # Erector inside a rails app. We're testing that we can use the command-line
7
+ # converter tool on a newly generated scaffold app (like we brag about in the
8
+ # user guide).
9
+ #
10
+ module Erector
11
+
12
+ describe "the user running this spec" do
13
+ it "should have the correct Rails gem (version #{Erector::Rails::RAILS_VERSION}) installed" do
14
+ target_version = Gem::Version.new(Erector::Rails::RAILS_VERSION)
15
+ dep = Gem::Dependency.new "rails", target_version
16
+ specs = Gem.source_index.search dep
17
+ specs.size.should == 1
18
+ end
19
+ end
20
+
21
+ describe "Erect in a Rails app" do
22
+
23
+ def run(cmd)
24
+ puts cmd
25
+ stdout = `#{cmd}`
26
+ if $? != 0
27
+ raise "Command #{cmd} failed, returning '#{stdout}', current dir '#{Dir.getwd}'"
28
+ else
29
+ return stdout
30
+ end
31
+ end
32
+
33
+ def run_rails(app_dir)
34
+ # To ensure we're working with the right version of Rails we use "gem 'rails', 1.2.3"
35
+ # in a "ruby -e" command line invocation of the rails executable to generate an
36
+ # app called explode.
37
+ #
38
+ puts "Generating fresh rails #{Erector::Rails::RAILS_VERSION} app"
39
+ run "ruby -e \"require 'rubygems'; gem 'rails', '#{Erector::Rails::RAILS_VERSION}'; load 'rails'\" #{app_dir}"
40
+ end
41
+
42
+ it "works like we say it does in the user guide" do
43
+ app_dir = Dir.tmpdir + "/#{Time.now.to_i}" + "/explode"
44
+ erector_bin = File.expand_path("#{File.dirname(__FILE__)}/../../bin")
45
+
46
+ FileUtils.mkdir_p(app_dir)
47
+ run_rails app_dir
48
+ FileUtils.cd(app_dir, :verbose => true) do
49
+ run "script/generate scaffold post title:string body:text published:boolean"
50
+ run "#{erector_bin}/erector app/views/posts"
51
+ FileUtils.rm_f("app/views/posts/*.erb")
52
+ run "(echo ''; echo \"require 'erector'\") >> config/environment.rb"
53
+ run "rake db:migrate"
54
+ # run "script/server" # todo: launch in background; use mechanize or something to crawl it; then kill it
55
+ # perhaps use open4?
56
+ # open http://localhost:3000/posts
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end