pivotal-erector 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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