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