erector 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/README.txt +17 -3
  2. data/VERSION.yml +2 -2
  3. data/bin/erector +1 -1
  4. data/lib/erector.rb +22 -2
  5. data/lib/erector/after_initialize.rb +34 -0
  6. data/lib/erector/caching.rb +93 -0
  7. data/lib/erector/convenience.rb +58 -0
  8. data/lib/erector/dependencies.rb +24 -0
  9. data/lib/erector/dependency.rb +21 -0
  10. data/lib/erector/{erect.rb → erect/erect.rb} +14 -4
  11. data/lib/erector/{erected.rb → erect/erected.rb} +6 -4
  12. data/lib/erector/{indenting.rb → erect/indenting.rb} +0 -0
  13. data/lib/erector/{rhtml.treetop → erect/rhtml.treetop} +51 -11
  14. data/lib/erector/errors.rb +12 -0
  15. data/lib/erector/extensions/hash.rb +21 -0
  16. data/lib/erector/externals.rb +88 -24
  17. data/lib/erector/html.rb +352 -0
  18. data/lib/erector/inline.rb +5 -5
  19. data/lib/erector/jquery.rb +36 -0
  20. data/lib/erector/mixin.rb +3 -5
  21. data/lib/erector/needs.rb +94 -0
  22. data/lib/erector/output.rb +117 -0
  23. data/lib/erector/rails.rb +2 -2
  24. data/lib/erector/rails/extensions/action_controller.rb +5 -3
  25. data/lib/erector/rails/extensions/rails_helpers.rb +159 -0
  26. data/lib/erector/rails/extensions/rails_widget.rb +98 -56
  27. data/lib/erector/rails/rails_form_builder.rb +8 -4
  28. data/lib/erector/rails/rails_version.rb +2 -2
  29. data/lib/erector/rails/template_handlers/ert_handler.rb +1 -1
  30. data/lib/erector/rails/template_handlers/rb_handler.rb +42 -1
  31. data/lib/erector/raw_string.rb +2 -2
  32. data/lib/erector/sass.rb +22 -0
  33. data/lib/erector/widget.rb +100 -653
  34. data/lib/erector/widgets.rb +1 -0
  35. data/lib/erector/widgets/external_renderer.rb +51 -0
  36. data/lib/erector/widgets/page.rb +45 -63
  37. data/lib/erector/widgets/table.rb +9 -1
  38. data/spec/erect/erect_rails_spec.rb +19 -17
  39. data/spec/erect/erect_spec.rb +11 -1
  40. data/spec/erect/erected_spec.rb +76 -5
  41. data/spec/erect/rhtml_parser_spec.rb +11 -1
  42. data/spec/erector/caching_spec.rb +267 -0
  43. data/spec/erector/convenience_spec.rb +258 -0
  44. data/spec/erector/dependency_spec.rb +46 -0
  45. data/spec/erector/externals_spec.rb +233 -0
  46. data/spec/erector/html_spec.rb +508 -0
  47. data/spec/erector/indentation_spec.rb +84 -24
  48. data/spec/erector/inline_spec.rb +19 -8
  49. data/spec/erector/jquery_spec.rb +35 -0
  50. data/spec/erector/mixin_spec.rb +1 -1
  51. data/spec/erector/needs_spec.rb +120 -0
  52. data/spec/erector/output_spec.rb +199 -0
  53. data/spec/erector/sample-file.txt +1 -0
  54. data/spec/erector/sass_spec.rb +33 -0
  55. data/spec/erector/widget_spec.rb +113 -932
  56. data/spec/erector/widgets/field_table_spec.rb +6 -6
  57. data/spec/erector/widgets/form_spec.rb +3 -3
  58. data/spec/erector/widgets/page_spec.rb +52 -6
  59. data/spec/erector/widgets/table_spec.rb +4 -4
  60. data/spec/spec_helper.rb +70 -29
  61. metadata +56 -19
  62. data/lib/erector/rails/extensions/rails_widget/rails_helpers.rb +0 -137
  63. data/spec/core_spec_suite.rb +0 -3
  64. data/spec/erector/external_spec.rb +0 -110
  65. data/spec/rails_spec_suite.rb +0 -3
  66. data/spec/spec.opts +0 -1
  67. data/spec/spec_suite.rb +0 -40
@@ -15,21 +15,21 @@ module Erector
15
15
  # to smuggle in instance variables.
16
16
  def call_block
17
17
  # note that instance_eval seems to pass in self as a parameter to the block
18
- instance_eval(&@block) if @block
18
+ instance_eval(&block) if block
19
19
  end
20
20
 
21
21
  private
22
22
  # This is part of the sub-widget/parent feature (see #widget method).
23
23
  def method_missing(name, *args, &block)
24
- block ||= lambda {} # captures self HERE
25
- if @parent
26
- @parent.send(name, *args, &block)
24
+ if parent && parent.respond_to?(name)
25
+ block ||= lambda {} # captures self HERE
26
+ parent.send(name, *args, &block)
27
27
  else
28
28
  super
29
29
  end
30
30
  end
31
31
  end
32
-
32
+
33
33
  class InlineWidget < Erector::Widget
34
34
  include Inline
35
35
  end
@@ -0,0 +1,36 @@
1
+ module Erector
2
+ module JQuery
3
+ # Emits a jQuery script, inside its own script tag, that is to be run on document ready or load.
4
+ #
5
+ # Usage (from inside a widget method):
6
+ # jquery "alert('hi')" :: a jquery ready handler
7
+ # jquery "alert('hi')", :id => 'foo' :: a jquery ready handler, with attributes in the script tag
8
+ # jquery :load, "alert('hi')" :: a jquery load handler
9
+ #
10
+ def jquery(*args)
11
+ event = if args.first.is_a? Symbol
12
+ args.shift
13
+ else
14
+ :ready
15
+ end
16
+ txt = args.shift
17
+ attributes = args.shift || {}
18
+
19
+ javascript attributes do
20
+ rawtext "\n"
21
+ rawtext "jQuery(document).#{event}(function($){\n"
22
+ rawtext txt
23
+ rawtext "\n});"
24
+ end
25
+ end
26
+
27
+ def jquery_load(text) #:nodoc:
28
+ $stderr.puts "jquery_load is deprecated; use jquery(:load, text) instead"
29
+ end
30
+
31
+ def jquery_ready(text) #:nodoc:
32
+ $stderr.puts "jquery_ready is deprecated; use jquery(:ready, text) instead"
33
+ end
34
+
35
+ end
36
+ end
@@ -1,14 +1,12 @@
1
1
  module Erector
2
2
  module Mixin
3
3
  # Executes the block as if it were the content body of a fresh Erector::Inline,
4
- # and returns the #to_s value. Since it executes inside the new widget it does not
4
+ # and returns the #to_html value. Since it executes inside the new widget it does not
5
5
  # have access to instance variables of the caller, although it does
6
6
  # have access to bound variables. Funnily enough, the options are passed in to both
7
- # to_s *and* to the widget itself, so they show up as instance variables.
7
+ # to_html *and* to the widget itself, so they show up as instance variables.
8
8
  def erector(options = {}, &block)
9
- vars = options.dup
10
- vars.delete_if {|key, value| Erector::Widget::RESERVED_INSTANCE_VARS.include?(key)}
11
- Erector.inline(vars, &block).to_s(options)
9
+ Erector.inline(options, &block).to_html(options)
12
10
  end
13
11
  end
14
12
  end
@@ -0,0 +1,94 @@
1
+ module Erector
2
+ module Needs
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ # Class method by which widget classes can declare that they need certain
9
+ # parameters. If needed parameters are not passed in to #new, then an
10
+ # exception will be thrown (with a hopefully useful message about which
11
+ # parameters are missing). This is intended to catch silly bugs like
12
+ # passing in a parameter called 'name' to a widget that expects a
13
+ # parameter called 'title'.
14
+ #
15
+ # You can also declare default values for parameters using hash syntax.
16
+ # You can put #needs declarations on multiple lines or on the same line;
17
+ # the only caveat is that if there are default values, they all have to be
18
+ # at the end of the line (so they go into the magic hash parameter).
19
+ #
20
+ # If a widget has no #needs declaration then it will accept any
21
+ # combination of parameters just like normal. If a widget wants to declare
22
+ # that it takes no parameters, use the special incantation "needs nil"
23
+ # (and don't declare any other needs, or kittens will cry).
24
+ #
25
+ # Usage:
26
+ # class FancyForm < Erector::Widget
27
+ # needs :title, :show_okay => true, :show_cancel => false
28
+ # ...
29
+ # end
30
+ #
31
+ # That means that
32
+ # FancyForm.new(:title => 'Login')
33
+ # will succeed, as will
34
+ # FancyForm.new(:title => 'Login', :show_cancel => true)
35
+ # but
36
+ # FancyForm.new(:name => 'Login')
37
+ # will fail.
38
+ #
39
+ def needs(*args)
40
+ args.each do |arg|
41
+ (@needs ||= []) << (arg.nil? ? nil : (arg.is_a? Hash) ? arg : arg.to_sym)
42
+ end
43
+ end
44
+
45
+ def get_needs
46
+ @needs ||= []
47
+
48
+ ancestors[1..-1].inject(@needs.dup) do |needs, ancestor|
49
+ needs.push(*ancestor.get_needs) if ancestor.respond_to?(:get_needs)
50
+ needs
51
+ end
52
+ end
53
+
54
+ def needed_variables
55
+ @needed_variables ||= get_needs.map{|need| need.is_a?(Hash) ? need.keys : need}.flatten
56
+ end
57
+
58
+ def needed_defaults
59
+ @needed_defaults ||= get_needs.inject({}) do |defaults, need|
60
+ defaults = need.merge(defaults) if need.is_a? Hash
61
+ defaults
62
+ end
63
+ end
64
+
65
+ def needs?(name)
66
+ needed_variables.empty? || needed_variables.include?(name)
67
+ end
68
+ end
69
+
70
+ def initialize(assigns = {})
71
+ super
72
+
73
+ assigned = assigns.keys
74
+
75
+ # set variables with default values
76
+ self.class.needed_defaults.each do |name, value|
77
+ unless assigned.include?(name)
78
+ instance_variable_set("@#{name}", value)
79
+ assigned << name
80
+ end
81
+ end
82
+
83
+ missing = self.class.needed_variables - assigned
84
+ unless missing.empty? || missing == [nil]
85
+ raise "Missing parameter#{missing.size == 1 ? '' : 's'}: #{missing.join(', ')}"
86
+ end
87
+
88
+ excess = assigned - self.class.needed_variables
89
+ unless self.class.needed_variables.empty? || excess.empty?
90
+ raise("Excess parameter#{excess.size == 1 ? '' : 's'}: #{excess.join(', ')}")
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,117 @@
1
+ module Erector
2
+ class Output
3
+ SPACES_PER_INDENT = 2
4
+
5
+ attr_reader :prettyprint, :widgets, :indentation, :max_length
6
+
7
+ def initialize(options = {}, & get_buffer)
8
+ @prettyprint = options.fetch(:prettyprint, Widget.prettyprint_default)
9
+ @indentation = options.fetch(:indentation, 0)
10
+ @current_line_length = 0
11
+ @max_length = options[:max_length]
12
+ @widgets = []
13
+ if get_buffer
14
+ @get_buffer = get_buffer
15
+ elsif buffer = options[:output]
16
+ @get_buffer = lambda { buffer }
17
+ else
18
+ buffer = []
19
+ @get_buffer = lambda { buffer }
20
+ end
21
+ end
22
+
23
+ def buffer
24
+ @get_buffer.call
25
+ end
26
+
27
+ def <<(s)
28
+ s = s.to_s unless s.is_a? String
29
+ append_indentation
30
+ if @max_length && s.length + @current_line_length > @max_length
31
+ leading_spaces = s =~ /^( +)/ ? $1.size : 0
32
+ trailing_spaces = s =~ /( +)$/ ? $1.size : 0
33
+
34
+ append(" " * leading_spaces)
35
+ need_space = false
36
+ words = s.split(/ /)
37
+ words.each do |word|
38
+ if (need_space ? 1 : 0) + word.length > space_left
39
+ append_newline
40
+ append_indentation
41
+ need_space = false
42
+ end
43
+ append(" ") if need_space
44
+ append(word)
45
+ need_space = true
46
+ end
47
+ append(" " * trailing_spaces)
48
+ else
49
+ append(s)
50
+ end
51
+ self
52
+ end
53
+
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.
58
+ def placeholder
59
+ s = ""
60
+ buffer << s
61
+ s
62
+ end
63
+
64
+ def to_s
65
+ RawString.new(buffer.to_s)
66
+ end
67
+
68
+ def to_a
69
+ buffer.to_a
70
+ end
71
+
72
+ def newline
73
+ if prettyprint
74
+ append_newline
75
+ end
76
+ end
77
+
78
+ def at_line_start?
79
+ @current_line_length == 0
80
+ end
81
+
82
+ def indent
83
+ @indentation += 1 if prettyprint
84
+ end
85
+
86
+ def undent
87
+ @indentation -= 1 if prettyprint
88
+ end
89
+
90
+ # always append a newline, regardless of prettyprint setting
91
+ #todo: test
92
+ def append_newline
93
+ buffer << "\n"
94
+ @current_line_length = 0
95
+ end
96
+
97
+ protected
98
+
99
+ def append(s)
100
+ buffer << s
101
+ @current_line_length += s.length
102
+ end
103
+
104
+ def space_left
105
+ @max_length - @current_line_length
106
+ end
107
+
108
+ def append_indentation
109
+ if prettyprint and at_line_start?
110
+ spaces = " " * ([@indentation, 0].max * SPACES_PER_INDENT)
111
+ buffer << spaces
112
+ @current_line_length += spaces.length
113
+ end
114
+ end
115
+
116
+ end
117
+ end
@@ -1,9 +1,9 @@
1
1
  require "action_controller"
2
2
  require "erector/rails/rails_version"
3
3
  require "erector/rails/rails_form_builder"
4
- require "erector/rails/extensions/rails_widget"
5
4
  require "erector/rails/extensions/action_controller"
6
- require "erector/rails/extensions/rails_widget/rails_helpers"
5
+ require "erector/rails/extensions/rails_helpers"
6
+ require "erector/rails/extensions/rails_widget"
7
7
  require "erector/rails/template_handlers/rb_handler"
8
8
  require "erector/rails/template_handlers/ert_handler"
9
9
 
@@ -1,11 +1,13 @@
1
1
  ActionController::Base.class_eval do
2
- def render_widget(widget_class, assigns=nil)
3
- render :text => Erector::Rails.render(widget_class, self, assigns)
2
+ class_inheritable_accessor :ert_template_base_class
3
+
4
+ def render_widget(widget_class, assigns=nil, options={})
5
+ render options.merge(:text => Erector::Rails.render(widget_class, response.template, assigns, options))
4
6
  end
5
7
 
6
8
  def render_with_erector_widget(*options, &block)
7
9
  if options.first.is_a?(Hash) && widget = options.first.delete(:widget)
8
- render_widget widget, @assigns
10
+ render_widget widget, @assigns, options.first
9
11
  else
10
12
  render_without_erector_widget *options, &block
11
13
  end
@@ -0,0 +1,159 @@
1
+ module Erector
2
+ module Rails
3
+ module Helpers
4
+ # Set up URL helpers so that both helpers.users_url and users_url can be called.
5
+ include ActionController::UrlWriter
6
+
7
+ def url_for(*args)
8
+ parent.url_for(*args)
9
+ end
10
+
11
+ # Wrappers for rails helpers that produce markup. Erector needs to
12
+ # manually emit their result.
13
+ def self.def_simple_rails_helper(method_name)
14
+ module_eval(<<-METHOD_DEF, __FILE__, __LINE__+1)
15
+ def #{method_name}(*args, &block)
16
+ text parent.#{method_name}(*args, &block)
17
+ end
18
+ METHOD_DEF
19
+ end
20
+
21
+ [
22
+ # UrlHelper
23
+ :link_to,
24
+ :button_to,
25
+ :link_to_unless_current,
26
+ :link_to_unless,
27
+ :link_to_if,
28
+ :mail_to,
29
+
30
+ # JavaScriptHelper
31
+ :link_to_function,
32
+ :button_to_function,
33
+
34
+ # FormTagHelper
35
+ :select_tag,
36
+ :text_field_tag,
37
+ :label_tag,
38
+ :hidden_field_tag,
39
+ :file_field_tag,
40
+ :password_field_tag,
41
+ :text_area_tag,
42
+ :check_box_tag,
43
+ :radio_button_tag,
44
+ :submit_tag,
45
+ :image_submit_tag,
46
+ :field_set_tag,
47
+
48
+ # FormHelper
49
+ :form_for,
50
+ :text_field,
51
+ :password_field,
52
+ :hidden_field,
53
+ :file_field,
54
+ :text_area,
55
+ :check_box,
56
+ :radio_button,
57
+
58
+ # ActiveRecordHelper
59
+ :error_message_on,
60
+ :error_messages_for,
61
+
62
+ # AssetTagHelper
63
+ :auto_discovery_link_tag,
64
+ :javascript_include_tag,
65
+ :stylesheet_link_tag,
66
+ :image_tag,
67
+
68
+ # ScriptaculousHelper
69
+ :sortable_element,
70
+ :sortable_element_js,
71
+ :text_field_with_auto_complete,
72
+ :draggable_element,
73
+ :drop_receiving_element,
74
+
75
+ # PrototypeHelper
76
+ :link_to_remote,
77
+ :button_to_remote,
78
+ :periodically_call_remote,
79
+ :form_remote_tag,
80
+ :submit_to_remote,
81
+ :update_page_tag
82
+ ].each do |method_name|
83
+ def_simple_rails_helper(method_name)
84
+ end
85
+
86
+ # Wrappers for rails helpers that produce markup, concatenating
87
+ # directly to the output buffer if given a block, returning a
88
+ # string otherwise. In the latter case, Erector needs to manually
89
+ # output their result.
90
+ def self.def_block_rails_helper(method_name)
91
+ module_eval(<<-METHOD_DEF, __FILE__, __LINE__+1)
92
+ def #{method_name}(*args, &block)
93
+ if block_given?
94
+ parent.#{method_name}(*args, &block)
95
+ else
96
+ text parent.#{method_name}(*args, &block)
97
+ end
98
+ end
99
+ METHOD_DEF
100
+ end
101
+
102
+ [:link_to,
103
+ :form_tag,
104
+ :field_set_tag,
105
+ :form_remote_tag,
106
+ :javascript_tag].each do |method_name|
107
+ def_block_rails_helper(method_name)
108
+ end
109
+
110
+ # Delegate to non-markup producing helpers via method_missing,
111
+ # returning their result directly.
112
+ def method_missing(name, *args, &block)
113
+ if parent.respond_to?(name)
114
+ parent.send(name, *args, &block)
115
+ else
116
+ super
117
+ end
118
+ end
119
+
120
+ # Since we delegate method_missing to parent, we need to delegate
121
+ # respond_to? as well.
122
+ def respond_to?(name)
123
+ super || parent.respond_to?(name)
124
+ end
125
+
126
+ def render(*args, &block)
127
+ captured = parent.capture do
128
+ parent.concat(parent.render(*args, &block))
129
+ parent.output_buffer.to_s
130
+ end
131
+ rawtext(captured)
132
+ end
133
+
134
+ def form_for(record_or_name_or_array, *args, &proc)
135
+ options = args.extract_options!
136
+ options[:builder] ||= ::Erector::RailsFormBuilder
137
+ args.push(options)
138
+ parent.form_for(record_or_name_or_array, *args, &proc)
139
+ end
140
+
141
+ def fields_for(record_or_name_or_array, *args, &proc)
142
+ options = args.extract_options!
143
+ options[:builder] ||= ::Erector::RailsFormBuilder
144
+ args.push(options)
145
+ parent.fields_for(record_or_name_or_array, *args, &proc)
146
+ end
147
+
148
+ def flash
149
+ parent.controller.send(:flash)
150
+ end
151
+
152
+ def session
153
+ parent.controller.session
154
+ end
155
+ end
156
+
157
+ Erector::Widget.send :include, Helpers
158
+ end
159
+ end