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
@@ -1,5 +1,6 @@
1
1
  require "erector/widgets/environment_badge"
2
2
  require "erector/widgets/field_table"
3
3
  require "erector/widgets/form"
4
+ require "erector/widgets/external_renderer"
4
5
  require "erector/widgets/page"
5
6
  require "erector/widgets/table"
@@ -0,0 +1,51 @@
1
+ class ExternalRenderer < Erector::Widget
2
+ needs :classes
3
+ needs :included_stylesheets => true, :inline_styles => true, :included_scripts => true, :inline_scripts => true
4
+
5
+ def content
6
+ included_stylesheets if @included_stylesheets
7
+ inline_styles if @inline_styles
8
+ included_scripts if @included_scripts
9
+ inline_scripts if @inline_scripts
10
+ end
11
+
12
+ def rendered_externals(type)
13
+ @classes.map do |klass|
14
+ klass.dependencies(type)
15
+ end.flatten.uniq
16
+ end
17
+
18
+ def included_scripts
19
+ rendered_externals(:js).each do |external|
20
+ script({:type => "text/javascript", :src => external.text}.merge(external.options))
21
+ end
22
+ end
23
+
24
+ def included_stylesheets
25
+ rendered_externals(:css).each do |external|
26
+ link({:rel => "stylesheet", :href => external.text, :type => "text/css", :media => "all"}.merge(external.options))
27
+ end
28
+ end
29
+
30
+ def inline_styles
31
+ rendered_externals(:style).each do |external|
32
+ style({:type => "text/css", 'xml:space' => 'preserve'}.merge(external.options)) do
33
+ rawtext external.text
34
+ end
35
+ end
36
+ end
37
+
38
+ def inline_scripts
39
+ rendered_externals(:script).each do |external|
40
+ javascript external.options do
41
+ rawtext external.text
42
+ end
43
+ end
44
+ # todo: allow :load or :ready per external script
45
+ rendered_externals(:jquery).each do |external|
46
+ jquery :load, external.text, external.options
47
+ end
48
+ end
49
+
50
+ end
51
+
@@ -1,18 +1,24 @@
1
1
  # Erector Page base class.
2
2
  #
3
- # Allows for accumulation of script and style tags (see example below) with either
4
- # external or inline content. External references are 'uniq'd, so it's a good idea to declare
5
- # a js script in all widgets that use it, so you don't accidentally lose the script if you remove
6
- # the one widget that happened to declare it.
7
- #
8
- # The script and style declarations are accumulated at class load time, as 'externals'.
9
- # This technique allows all widgets to add their own requirements to the page header
10
- # without extra logic for declaring which pages include which nested widgets.
11
- # Unfortunately, this means that every page in the application will share the same headers,
12
- # which may lead to conflicts.
13
- #
14
- # If you want something to show up in the headers for just one page type (subclass),
15
- # then override #head_content, call super, and then emit it yourself.
3
+ # Allows for accumulation of script and style tags (see example below) with
4
+ # either external or inline content. External references are 'uniq'd, so it's
5
+ # a good idea to declare a js script in all widgets that use it, so you don't
6
+ # accidentally lose the script if you remove the one widget that happened to
7
+ # declare it.
8
+ #
9
+ # The script and style declarations are accumulated at class load time, as
10
+ # 'dependencies'. This technique allows all widgets to add their own requirements
11
+ # to the page header without extra logic for declaring which pages include
12
+ # which nested widgets. Fortunately, Page is now smart enough to figure out
13
+ # which widgets were actually rendered during the body_content run, so it only
14
+ # emits into its HEAD the dependencies that are relevant. If it misses some, or
15
+ # if you want to add some extra dependencies -- for instance, styles that apply
16
+ # to widgets that are rendered later via AJAX -- then return an array of those
17
+ # widget classes in your subclass by overriding the #extra_widgets method.
18
+ #
19
+ # If you want something to show up in the headers for just one page type
20
+ # (subclass), then override #head_content, call super, and then emit it
21
+ # yourself.
16
22
  #
17
23
  # Body content can be supplied in several ways:
18
24
  #
@@ -40,19 +46,20 @@
40
46
  # text "body content"
41
47
  # end
42
48
  #
43
- # This last trick (passing a block to Page.new) works because Page is an InlineWidget
44
- # so its block is evaluated in the context of the newly instantiated widget object,
45
- # and not in the context of its caller. But this means you can't access instance variables
46
- # of the caller, e.g.
47
- #
49
+ # This last trick (passing a block to Page.new) works because Page is an
50
+ # InlineWidget so its block is evaluated in the context of the newly
51
+ # instantiated widget object, and not in the context of its caller. But this
52
+ # means you can't access instance variables of the caller, e.g.
53
+ #
48
54
  # @name = "fred"
49
55
  # Erector::Widgets::Page.new do
50
56
  # text "my name is #{@name}"
51
57
  # end
52
58
  #
53
- # will emit "my name is " because @name is nil inside the new Page. However, you *can*
54
- # call methods in the parent class, thanks to some method_missing magic. Confused? You
55
- # should be. See Erector::Inline#content for more documentation.
59
+ # will emit "my name is " because @name is nil inside the new Page. However,
60
+ # you *can* call methods in the parent class, thanks to some method_missing
61
+ # magic. Confused? You should be. See Erector::Inline#content for more
62
+ # documentation.
56
63
  #
57
64
  # Author:: Alex Chaffee, alex@stinky.com
58
65
  #
@@ -63,21 +70,21 @@
63
70
  # external :script, "$(document).ready(function(){...});"
64
71
  # external :css, "stuff.css"
65
72
  # external :style, "li.foo { color: red; }"
66
- #
73
+ #
67
74
  # def page_title
68
75
  # "my app"
69
76
  # end
70
- #
77
+ #
71
78
  # def body_content
72
79
  # h1 "My App"
73
80
  # p "welcome to my app"
74
81
  # widget WidgetWithExternalStyle
75
82
  # end
76
83
  # end
77
- #
84
+ #
78
85
  # class WidgetWithExternalStyle < Erector::Widget
79
86
  # external :style, "div.custom { border: 2px solid green; }"
80
- #
87
+ #
81
88
  # def content
82
89
  # div :class => "custom" do
83
90
  # text "green is good"
@@ -99,21 +106,24 @@ class Erector::Widgets::Page < Erector::InlineWidget
99
106
  end
100
107
 
101
108
  def content
109
+ extra_head_slot = nil
102
110
  rawtext doctype
103
111
  html(html_attributes) do
104
112
  head do
105
113
  head_content
114
+ extra_head_slot = output.placeholder
106
115
  end
107
116
  body(body_attributes) do
108
117
  if block_given?
109
118
  yield
110
- elsif @block
111
- super
112
119
  else
113
120
  body_content
114
121
  end
115
122
  end
116
123
  end
124
+ # after everything's been rendered, use the placeholder to
125
+ # insert all the head's dependencies
126
+ extra_head_slot << included_head_content
117
127
  end
118
128
 
119
129
  # override me to provide a page title (default = name of the Page subclass)
@@ -133,51 +143,23 @@ class Erector::Widgets::Page < Erector::InlineWidget
133
143
 
134
144
  # override me (or instantiate Page with a block)
135
145
  def body_content
146
+ call_block
136
147
  end
137
148
 
138
149
  # emit the contents of the head element. Override and call super if you want to put more stuff in there.
139
150
  def head_content
140
151
  meta 'http-equiv' => 'content-type', :content => 'text/html;charset=UTF-8'
141
152
  title page_title
142
-
143
- included_stylesheets
144
- inline_styles
145
-
146
- included_scripts
147
- inline_scripts
148
- end
149
-
150
- def included_scripts
151
- self.class.externals(:js).each do |external|
152
- script({:type => "text/javascript", :src => external.text}.merge(external.options))
153
- end
154
153
  end
155
154
 
156
- def included_stylesheets
157
- self.class.externals(:css).each do |external|
158
- link({:rel => "stylesheet", :href => external.text, :type => "text/css", :media => "all"}.merge(external.options))
159
- end
160
- end
161
-
162
- def inline_styles
163
- self.class.externals(:style).each do |external|
164
- style({:type => "text/css", 'xml:space' => 'preserve'}.merge(external.options)) do
165
- rawtext external.text
166
- end
167
- end
155
+ def included_head_content
156
+ # now that we've rendered the whole page, it's the right time
157
+ # to ask what all widgets were rendered to the output stream
158
+ included_widgets = [self.class] + output.widgets.to_a + extra_widgets
159
+ ExternalRenderer.new(:classes => included_widgets).to_html
168
160
  end
169
161
 
170
- def inline_scripts
171
- self.class.externals(:script).each do |external|
172
- javascript external.options do
173
- rawtext external.text
174
- end
175
- end
176
- self.class.externals(:jquery).each do |external|
177
- javascript external.options do
178
- jquery_ready external.text
179
- end
180
- end
162
+ def extra_widgets
163
+ []
181
164
  end
182
-
183
165
  end
@@ -1,5 +1,10 @@
1
- module Erector
1
+ begin
2
+ require "active_support/inflector"
3
+
4
+ module Erector
2
5
  module Widgets #:nodoc:
6
+
7
+
3
8
  # The Table widget provides the ability to render a table from a
4
9
  # list of objects (one for each row).
5
10
  #
@@ -93,4 +98,7 @@ module Erector
93
98
  end
94
99
  end
95
100
  end
101
+ end
102
+ rescue LoadError => e
103
+ $stderr.puts "Erector::Widgets::Table requires active_support"
96
104
  end
@@ -1,5 +1,6 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
2
  require "erector/rails"
3
+ require "rails/version"
3
4
 
4
5
  # backport mktmpdir so this test will work on Ruby 1.8.6
5
6
  unless Dir.respond_to?(:mktmpdir)
@@ -49,7 +50,6 @@ end
49
50
  # user guide).
50
51
  #
51
52
  module Erector
52
-
53
53
  describe "the Rails version" do
54
54
  it "should be #{Erector::Rails::RAILS_VERSION}" do
55
55
  ::Rails::VERSION::STRING.should == Erector::Rails::RAILS_VERSION
@@ -57,9 +57,7 @@ module Erector
57
57
  end
58
58
 
59
59
  describe "Erect in a Rails app" do
60
-
61
60
  def run(cmd)
62
- # puts "Running #{cmd}"
63
61
  stderr_file = Dir.tmpdir + "/stderr.txt"
64
62
  stdout = IO.popen(cmd + " 2>#{stderr_file}") do |pipe|
65
63
  pipe.read
@@ -76,37 +74,41 @@ module Erector
76
74
  def indent(s)
77
75
  s.gsub(/^/, ' ')
78
76
  end
79
-
80
- def run_rails(app_dir)
81
- # To ensure we're working with the right version of Rails we use "gem 'rails', 1.2.3"
82
- # in a "ruby -e" command line invocation of the rails executable to generate an
83
- # app called explode.
84
- #
85
- # puts "Generating fresh rails #{Erector::Rails::RAILS_VERSION} app in #{app_dir}"
86
- run "ruby -e \"require 'rubygems'; gem 'rails', '#{Erector::Rails::RAILS_VERSION}'; load 'rails'\" #{app_dir}"
87
- end
88
-
77
+
89
78
  it "works like we say it does in the user guide" do
90
79
  erector_dir = File.expand_path("#{File.dirname(__FILE__)}/../..")
91
80
 
81
+ # We add the paths to our vendored copy of rails to the load paths, so
82
+ # that this spec can be run without having a version of Rails (which may
83
+ # not match the version we wish to test against) installed.
84
+ rails_libs_argument = "-I'#{RAILS_LOAD_PATHS.join("':'")}'"
85
+
92
86
  Dir.mktmpdir do |app_dir|
93
- run_rails app_dir
87
+ run "ruby #{rails_libs_argument} '#{VENDOR_RAILS}/railties/bin/rails' '#{app_dir}'"
88
+
89
+ FileUtils.cp_r(VENDOR_RAILS, "#{app_dir}/vendor/rails")
94
90
 
95
91
  FileUtils.mkdir_p(app_dir + "/vendor/gems")
96
92
  FileUtils.cp_r(erector_dir, "#{app_dir}/vendor/gems/erector")
97
93
 
98
94
  FileUtils.cd(app_dir) do
99
95
  run "script/generate scaffold post title:string body:text published:boolean"
100
- run "ruby -I#{erector_dir}/lib #{erector_dir}/bin/erector app/views/posts"
96
+
97
+ # The 'erector' binary would normally have been installed through rubygems,
98
+ # providing it with a wrapper script which requires rubygems. But here we
99
+ # run it directly, so we need to require rubygems explicitly before running
100
+ # the main script.
101
+ run "ruby #{rails_libs_argument} -I'#{erector_dir}/lib' " +
102
+ "-e \"require 'rubygems'; load '#{erector_dir}/bin/erector'\" app/views/posts"
103
+
101
104
  FileUtils.rm_f("app/views/posts/*.erb")
102
105
  run "rake --trace db:migrate"
106
+
103
107
  # run "script/server" # todo: launch in background; use mechanize or something to crawl it; then kill it
104
108
  # perhaps use open4?
105
109
  # open http://localhost:3000/posts
106
110
  end
107
111
  end
108
112
  end
109
-
110
113
  end
111
-
112
114
  end
@@ -1,6 +1,6 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
2
 
3
- require "erector/erect"
3
+ require "erector/erect/erect"
4
4
 
5
5
  module Erector
6
6
  describe Erect do
@@ -23,6 +23,16 @@ module Erector
23
23
  Erect.new([]).verbose.should be_true
24
24
  Erect.new(["-q"]).verbose.should be_false
25
25
  end
26
+
27
+ it "sets the superclass to what you tell it" do
28
+ Erect.new([]).superklass.should == 'Erector::Widget'
29
+ Erect.new(['--superclass', 'Foo::Bar']).superklass.should == 'Foo::Bar'
30
+ end
31
+
32
+ it "sets the method name to what you tell it" do
33
+ Erect.new([]).method_name.should == 'content'
34
+ Erect.new(['--method', 'my_content']).method_name.should == 'my_content'
35
+ end
26
36
 
27
37
  it "parses a command line with several filenames and an option on it" do
28
38
  erect = Erect.new(["-q", "foo.html", "bar/baz.html"])
@@ -1,6 +1,6 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
2
 
3
- require "erector/erect"
3
+ require "erector/erect/erect"
4
4
 
5
5
  module Erector
6
6
  describe Erected do
@@ -37,7 +37,7 @@ module Erector
37
37
  Erected.new("views/stuff/foo_bar.html.erb").parent_class.should == "Erector::Widget"
38
38
  end
39
39
 
40
- def convert(dir, input, output)
40
+ def convert(dir, input, output, superklass = nil, method_name = nil)
41
41
  dir = Dir.tmpdir + "/#{Time.now.to_i}" + "/#{dir}"
42
42
 
43
43
  FileUtils.mkdir_p(dir)
@@ -47,8 +47,9 @@ module Erector
47
47
  File.open(html, "w") do |f|
48
48
  f.puts(input)
49
49
  end
50
-
51
- @e = Erected.new(html)
50
+
51
+ args = [ html, superklass || 'Erector::Widget', method_name || 'content' ]
52
+ @e = Erected.new(*args)
52
53
  @e.convert
53
54
 
54
55
  File.read(rb).should == output
@@ -79,13 +80,83 @@ module Erector
79
80
  "end\n"
80
81
  )
81
82
  end
83
+
84
+ it "converts a normal file with a different superclass" do
85
+ convert(".",
86
+ "<div>hello</div>",
87
+ "class Dummy < Foo::Bar\n" +
88
+ " def content\n" +
89
+ " div do\n" +
90
+ " text 'hello'\n" +
91
+ " end\n" +
92
+ " end\n" +
93
+ "end\n",
94
+ "Foo::Bar"
95
+ )
96
+ end
97
+
98
+ it "converts a normal file with a different superclass and method name" do
99
+ convert(".",
100
+ "<div>hello</div>",
101
+ "class Dummy < Foo::Bar\n" +
102
+ " def my_content\n" +
103
+ " div do\n" +
104
+ " text 'hello'\n" +
105
+ " end\n" +
106
+ " end\n" +
107
+ "end\n",
108
+ "Foo::Bar",
109
+ 'my_content'
110
+ )
111
+ end
112
+
113
+ it "ignores ERb trim markers" do
114
+ convert(".",
115
+ %{<div>
116
+ <%= 1 + 3 -%>
117
+ </div>},
118
+ %{class Dummy < Erector::Widget
119
+ def content
120
+ div do
121
+ rawtext 1 + 3
122
+ end
123
+ end
124
+ end
125
+ })
126
+ end
127
+
128
+ it "converts ERb escapes in attributes" do
129
+ convert(".",
130
+ "<div id=\"foo_<%= bar %>_baz_<%= quux %>_marph\">hello</div>",
131
+ %{class Dummy < Erector::Widget
132
+ def content
133
+ div(:id => ('foo_' + bar + '_baz_' + quux + '_marph')) do
134
+ text 'hello'
135
+ end
136
+ end
137
+ end
138
+ })
139
+ end
140
+
141
+ it "only parenthesizes ERb escapes in attributes if necessary" do
142
+ convert(".",
143
+ "<div id=\'<%= bar %>\'>hello</div>",
144
+ %{class Dummy < Erector::Widget
145
+ def content
146
+ div :id => bar do
147
+ text 'hello'
148
+ end
149
+ end
150
+ end
151
+ })
152
+ end
82
153
 
83
154
  # todo: figure out if there is any such thing as unparsable HTML anymore
84
155
  # it "raises an exception if given unparsable HTML" do
85
156
  # begin
86
157
  # convert(".", "<", "")
87
158
  # rescue => e
88
- # e.to_s.should include("Could not parse")
159
+ # e.to_html.should include("Could not parse")
89
160
  # end
90
161
  # end
91
162