erector 0.7.2 → 0.8.0

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 (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