erector 0.7.1 → 0.7.2

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.
@@ -1,4 +1,5 @@
1
- require "#{File.dirname(__FILE__)}/widgets/table"
2
- require "#{File.dirname(__FILE__)}/widgets/field_table"
3
- require "#{File.dirname(__FILE__)}/widgets/page"
4
- require "#{File.dirname(__FILE__)}/widgets/environment_badge"
1
+ require "erector/widgets/environment_badge"
2
+ require "erector/widgets/field_table"
3
+ require "erector/widgets/form"
4
+ require "erector/widgets/page"
5
+ require "erector/widgets/table"
@@ -0,0 +1,30 @@
1
+ # todo: make more like http://github.com/justinfrench/formtastic
2
+
3
+ class Form < Erector::Widget
4
+ needs :action, :method => "post"
5
+
6
+ def content
7
+ form :method => form_method, :action => @action do
8
+ unless rest_method == form_method
9
+ input :type => "hidden", :name => "_method", :value => rest_method
10
+ end
11
+ super
12
+ end
13
+ end
14
+
15
+ def method
16
+ @method.to_s.downcase
17
+ end
18
+
19
+ def form_method
20
+ if method == "get"
21
+ "get"
22
+ else
23
+ "post"
24
+ end
25
+ end
26
+
27
+ def rest_method
28
+ method
29
+ end
30
+ end
@@ -5,18 +5,55 @@
5
5
  # a js script in all widgets that use it, so you don't accidentally lose the script if you remove
6
6
  # the one widget that happened to declare it.
7
7
  #
8
- # At minimum, child classes must override #body_content. You can also get a "quick and dirty"
9
- # page by passing a block to Page.new but that doesn't really buy you much.
10
- #
11
8
  # The script and style declarations are accumulated at class load time, as 'externals'.
12
9
  # This technique allows all widgets to add their own requirements to the page header
13
10
  # without extra logic for declaring which pages include which nested widgets.
14
11
  # Unfortunately, this means that every page in the application will share the same headers,
15
- # which may lead to conflicts.
12
+ # which may lead to conflicts.
16
13
  #
17
- # If you want something to show up in the headers for just one page type (subclass),
14
+ # If you want something to show up in the headers for just one page type (subclass),
18
15
  # then override #head_content, call super, and then emit it yourself.
19
16
  #
17
+ # Body content can be supplied in several ways:
18
+ #
19
+ # * In a Page subclass, by overriding the #body_content method:
20
+ #
21
+ # class MyPage < Erector::Widgets::Page
22
+ # def body_content
23
+ # text "body content"
24
+ # end
25
+ # end
26
+ #
27
+ # * Or by overriding #content and passing a block to super:
28
+ #
29
+ # class MyPage < Erector::Widgets::Page
30
+ # def content
31
+ # super do
32
+ # text "body content"
33
+ # end
34
+ # end
35
+ # end
36
+ #
37
+ # * Or by passing a block to Page.new:
38
+ #
39
+ # Erector::Widgets::Page.new do
40
+ # text "body content"
41
+ # end
42
+ #
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
+ #
48
+ # @name = "fred"
49
+ # Erector::Widgets::Page.new do
50
+ # text "my name is #{@name}"
51
+ # end
52
+ #
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.
56
+ #
20
57
  # Author:: Alex Chaffee, alex@stinky.com
21
58
  #
22
59
  # = Example Usage:
@@ -52,9 +89,7 @@
52
89
  # * It may be desirable to unify #js and #script, and #css and #style, and have the routine be
53
90
  # smart enough to analyze its parameter to decide whether to make it a file or a script.
54
91
  #
55
- class Erector::Widgets::Page < Erector::Widget
56
-
57
- needs :basic_styles => true
92
+ class Erector::Widgets::Page < Erector::InlineWidget
58
93
 
59
94
  # Emit the Transitional doctype.
60
95
  # TODO: allow selection from among different standard doctypes
@@ -65,13 +100,18 @@ class Erector::Widgets::Page < Erector::Widget
65
100
 
66
101
  def content
67
102
  rawtext doctype
68
- # todo: allow customization of xmlns and xml:lang
69
- html :xmlns => 'http://www.w3.org/1999/xhtml', 'xml:lang' => 'en', :lang => 'en' do
103
+ html(html_attributes) do
70
104
  head do
71
105
  head_content
72
106
  end
73
- body :class => body_class do
74
- body_content
107
+ body(body_attributes) do
108
+ if block_given?
109
+ yield
110
+ elsif @block
111
+ super
112
+ else
113
+ body_content
114
+ end
75
115
  end
76
116
  end
77
117
  end
@@ -81,13 +121,18 @@ class Erector::Widgets::Page < Erector::Widget
81
121
  self.class.name
82
122
  end
83
123
 
84
- # override me to add a css class to the body
85
- def body_class
124
+ # override me to change the attributes of the HTML element
125
+ def html_attributes
126
+ {:xmlns => 'http://www.w3.org/1999/xhtml', 'xml:lang' => 'en', :lang => 'en'}
127
+ end
128
+
129
+ # override me to add attributes (e.g. a css class) to the body
130
+ def body_attributes
131
+ {}
86
132
  end
87
133
 
88
134
  # override me (or instantiate Page with a block)
89
135
  def body_content
90
- instance_eval(&@block) if @block
91
136
  end
92
137
 
93
138
  # emit the contents of the head element. Override and call super if you want to put more stuff in there.
@@ -95,7 +140,6 @@ class Erector::Widgets::Page < Erector::Widget
95
140
  meta 'http-equiv' => 'content-type', :content => 'text/html;charset=UTF-8'
96
141
  title page_title
97
142
 
98
- basic_styles if @basic_styles
99
143
  included_stylesheets
100
144
  inline_styles
101
145
 
@@ -104,53 +148,34 @@ class Erector::Widgets::Page < Erector::Widget
104
148
  end
105
149
 
106
150
  def included_scripts
107
- self.class.externals(:js).each do |file|
108
- script :type => "text/javascript", :src => file
151
+ self.class.externals(:js).each do |external|
152
+ script({:type => "text/javascript", :src => external.text}.merge(external.options))
109
153
  end
110
154
  end
111
155
 
112
156
  def included_stylesheets
113
- self.class.externals(:css).each do |file|
114
- # todo: allow different media
115
- link :rel => "stylesheet", :href => file, :type => "text/css", :media => "all"
157
+ self.class.externals(:css).each do |external|
158
+ link({:rel => "stylesheet", :href => external.text, :type => "text/css", :media => "all"}.merge(external.options))
116
159
  end
117
160
  end
118
-
119
- # Emit some *very* basic styles, hopefully not too controversial. Suppress
120
- # them by setting :basic_styles => false when you construct your Page, or just
121
- # override the basic_styles method in your subclass and make it do nothing.
122
- # You can also redefine them since they're defined above any other styles in the HEAD.
123
- #
124
- # Class "right" floats right, class "left" floats left, and class "clear" clears
125
- # any floats on both sides while being as small as possible to minimize impact
126
- # on your layout. And images have no border.
127
- def basic_styles
128
- style <<-STYLE
129
- img {border: none}
130
- .right {float: right;}
131
- .left {float: left;}
132
- .clear {background: none;border: 0;clear: both;display: block;float: none;font-size: 0;margin: 0;padding: 0;position: static;overflow: hidden;visibility: hidden;width: 0;height: 0;}
133
- STYLE
134
- end
135
161
 
136
162
  def inline_styles
137
- style :type => "text/css", 'xml:space' => 'preserve' do
138
- rawtext "\n"
139
- self.class.externals(:style).each do |txt|
140
- rawtext "\n"
141
- rawtext txt
163
+ self.class.externals(:style).each do |external|
164
+ style({:type => "text/css", 'xml:space' => 'preserve'}.merge(external.options)) do
165
+ rawtext external.text
142
166
  end
143
167
  end
144
168
  end
145
169
 
146
170
  def inline_scripts
147
- javascript do
148
- self.class.externals(:script).each do |txt|
149
- rawtext "\n"
150
- rawtext txt
171
+ self.class.externals(:script).each do |external|
172
+ javascript external.options do
173
+ rawtext external.text
151
174
  end
152
- self.class.externals(:jquery).each do |txt|
153
- jquery_ready txt
175
+ end
176
+ self.class.externals(:jquery).each do |external|
177
+ javascript external.options do
178
+ jquery_ready external.text
154
179
  end
155
180
  end
156
181
  end
data/rails/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "erector"
2
+ require "erector/rails"
3
+
4
+ Erector.init_rails(binding)
@@ -1,6 +1,47 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
2
  require "erector/rails"
3
3
 
4
+ # backport mktmpdir so this test will work on Ruby 1.8.6
5
+ unless Dir.respond_to?(:mktmpdir)
6
+ def Dir.mktmpdir(prefix_suffix=nil, tmpdir=nil)
7
+ case prefix_suffix
8
+ when nil
9
+ prefix = "d"
10
+ suffix = ""
11
+ when String
12
+ prefix = prefix_suffix
13
+ suffix = ""
14
+ when Array
15
+ prefix = prefix_suffix[0]
16
+ suffix = prefix_suffix[1]
17
+ else
18
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
19
+ end
20
+ tmpdir ||= Dir.tmpdir
21
+ t = Time.now.strftime("%Y%m%d")
22
+ n = nil
23
+ begin
24
+ path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
25
+ path << "-#{n}" if n
26
+ path << suffix
27
+ Dir.mkdir(path, 0700)
28
+ rescue Errno::EEXIST
29
+ n ||= 0
30
+ n += 1
31
+ retry
32
+ end
33
+
34
+ if block_given?
35
+ begin
36
+ yield path
37
+ ensure
38
+ FileUtils.remove_entry_secure path
39
+ end
40
+ else
41
+ path
42
+ end
43
+ end
44
+ end
4
45
 
5
46
  # Note: this is *not* inside the rails_root since we're not testing
6
47
  # Erector inside a rails app. We're testing that we can use the command-line
@@ -9,19 +50,16 @@ require "erector/rails"
9
50
  #
10
51
  module Erector
11
52
 
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
53
+ describe "the Rails version" do
54
+ it "should be #{Erector::Rails::RAILS_VERSION}" do
55
+ ::Rails::VERSION::STRING.should == Erector::Rails::RAILS_VERSION
18
56
  end
19
57
  end
20
58
 
21
59
  describe "Erect in a Rails app" do
22
60
 
23
61
  def run(cmd)
24
- puts "Running #{cmd}"
62
+ # puts "Running #{cmd}"
25
63
  stderr_file = Dir.tmpdir + "/stderr.txt"
26
64
  stdout = IO.popen(cmd + " 2>#{stderr_file}") do |pipe|
27
65
  pipe.read
@@ -44,29 +82,28 @@ module Erector
44
82
  # in a "ruby -e" command line invocation of the rails executable to generate an
45
83
  # app called explode.
46
84
  #
47
- puts "Generating fresh rails #{Erector::Rails::RAILS_VERSION} app in #{app_dir}"
85
+ # puts "Generating fresh rails #{Erector::Rails::RAILS_VERSION} app in #{app_dir}"
48
86
  run "ruby -e \"require 'rubygems'; gem 'rails', '#{Erector::Rails::RAILS_VERSION}'; load 'rails'\" #{app_dir}"
49
87
  end
50
88
 
51
89
  it "works like we say it does in the user guide" do
52
- app_dir = Dir.tmpdir + "/#{Time.now.to_i}" + "/explode"
53
- erector_bin = File.expand_path("#{File.dirname(__FILE__)}/../../bin")
54
-
55
- FileUtils.mkdir_p(app_dir)
56
- run_rails app_dir
57
-
58
- FileUtils.mkdir_p(app_dir + "/vendor/gems")
59
- FileUtils.cp_r("#{File.dirname __FILE__}/../..", "#{app_dir}/vendor/gems/erector")
60
-
61
- FileUtils.cd(app_dir) do
62
- run "script/generate scaffold post title:string body:text published:boolean"
63
- run "#{erector_bin}/erector app/views/posts"
64
- FileUtils.rm_f("app/views/posts/*.erb")
65
- # run "(echo ''; echo \"require 'erector'\") >> config/environment.rb"
66
- run "rake --trace db:migrate"
67
- # run "script/server" # todo: launch in background; use mechanize or something to crawl it; then kill it
68
- # perhaps use open4?
69
- # open http://localhost:3000/posts
90
+ erector_dir = File.expand_path("#{File.dirname(__FILE__)}/../..")
91
+
92
+ Dir.mktmpdir do |app_dir|
93
+ run_rails app_dir
94
+
95
+ FileUtils.mkdir_p(app_dir + "/vendor/gems")
96
+ FileUtils.cp_r(erector_dir, "#{app_dir}/vendor/gems/erector")
97
+
98
+ FileUtils.cd(app_dir) do
99
+ 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"
101
+ FileUtils.rm_f("app/views/posts/*.erb")
102
+ run "rake --trace db:migrate"
103
+ # run "script/server" # todo: launch in background; use mechanize or something to crawl it; then kill it
104
+ # perhaps use open4?
105
+ # open http://localhost:3000/posts
106
+ end
70
107
  end
71
108
  end
72
109
 
@@ -32,9 +32,9 @@ module Erector
32
32
  Erected.new("stuff/foo_bar.html.erb").parent_class.should == "Erector::Widget"
33
33
  end
34
34
 
35
- it "uses RailsWidget as the parent class if it's in a views dir" do
36
- Erected.new("app/views/stuff/foo_bar.html.erb").parent_class.should == "Erector::RailsWidget"
37
- Erected.new("views/stuff/foo_bar.html.erb").parent_class.should == "Erector::RailsWidget"
35
+ it "uses Widget as the parent class if it's in a views dir" do
36
+ Erected.new("app/views/stuff/foo_bar.html.erb").parent_class.should == "Erector::Widget"
37
+ Erected.new("views/stuff/foo_bar.html.erb").parent_class.should == "Erector::Widget"
38
38
  end
39
39
 
40
40
  def convert(dir, input, output)
@@ -70,7 +70,7 @@ module Erector
70
70
  it "converts a views file" do
71
71
  convert("app/views/foos",
72
72
  "<div>hello</div>",
73
- "class Views::Foos::Dummy < Erector::RailsWidget\n" +
73
+ "class Views::Foos::Dummy < Erector::Widget\n" +
74
74
  " def content\n" +
75
75
  " div do\n" +
76
76
  " text 'hello'\n" +
@@ -1,9 +1,50 @@
1
1
  require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
2
2
  require 'benchmark'
3
+ require 'active_support' # for Symbol#to_proc
4
+
5
+ describe Erector::External do
6
+ it "can be constructed with type, klass, text" do
7
+ x = Erector::External.new(:foo, Object, "abc")
8
+ x.type.should == :foo
9
+ x.klass.should == Object
10
+ x.text.should == "abc"
11
+ x.options.should == {}
12
+ end
13
+
14
+ it "can be constructed with type, klass, text, and options" do
15
+ x = Erector::External.new(:foo, Object, "abc", {:bar => 7})
16
+ x.options.should == {:bar => 7}
17
+ end
18
+
19
+ it "is equal to an identical external" do
20
+ x = Erector::External.new(:foo, Object, "abc", {:bar => 7})
21
+ y = Erector::External.new(:foo, Object, "abc", {:bar => 7})
22
+ x.should == y
23
+ [x].should include(y)
24
+ end
25
+
26
+ it "is equal to an identical external with a different class" do
27
+ class_x = Class.new
28
+ class_y = Class.new
29
+ x = Erector::External.new(:foo, class_x, "abc", {:bar => 7})
30
+ y = Erector::External.new(:foo, class_y, "abc", {:bar => 7})
31
+ x.should == y
32
+ [x].should include(y)
33
+ end
34
+
35
+ it "is not equal to an identical external with a different options" do
36
+ class_x = Class.new
37
+ x = Erector::External.new(:foo, class_x, "abc")
38
+ y = Erector::External.new(:foo, class_x, "abc", {:bar => 7})
39
+ x.should_not == y
40
+ [x].should_not include(y)
41
+ end
42
+
43
+ end
3
44
 
4
45
  describe "external declarations" do
5
46
  class HotSauce < Erector::Widget
6
- external :css, "/css/tapatio.css"
47
+ external :css, "/css/tapatio.css", :media => "print"
7
48
  external :css, "/css/salsa_picante.css"
8
49
  external :js, "/lib/jquery.js"
9
50
  external :js, "/lib/picante.js"
@@ -16,7 +57,7 @@ describe "external declarations" do
16
57
  end
17
58
 
18
59
  it "can be fetched via the type" do
19
- Erector::Widget.externals(:css).should == [
60
+ Erector::Widget.externals(:css).map(&:text).should == [
20
61
  "/css/tapatio.css",
21
62
  "/css/salsa_picante.css",
22
63
  "/css/sourcream.css",
@@ -24,25 +65,42 @@ describe "external declarations" do
24
65
  end
25
66
 
26
67
  it "can be filtered via the class" do
27
- Erector::Widget.externals(:css, HotSauce).should == [
68
+ Erector::Widget.externals(:css, HotSauce).map(&:text).should == [
28
69
  "/css/tapatio.css",
29
70
  "/css/salsa_picante.css",
30
71
  ]
31
- Erector::Widget.externals(:css, SourCream).should == [
72
+ Erector::Widget.externals(:css, SourCream).map(&:text).should == [
32
73
  "/css/sourcream.css",
33
74
  ]
34
75
  end
35
76
 
77
+ it "retains the options" do
78
+ Erector::Widget.externals(:css, HotSauce).map(&:options).should == [
79
+ {:media => "print"},
80
+ {}
81
+ ]
82
+ end
83
+
36
84
  it "removes duplicates" do
37
- Erector::Widget.externals(:js).should == [
85
+ Erector::Widget.externals(:js).map(&:text).should == [
38
86
  "/lib/jquery.js",
39
87
  "/lib/picante.js",
40
88
  "/lib/dairy.js",
41
89
  ]
42
90
  end
91
+
92
+
93
+ class Taco < Erector::Widget
94
+ external :filling, "beef"
95
+ external :filling, "beef", :media => "print"
96
+ end
97
+
98
+ it "considers options when removing duplicates" do
99
+ Erector::Widget.externals(:filling).map(&:text).should == ["beef", "beef"]
100
+ end
43
101
 
44
102
  it "works with strings or symbols" do
45
- Erector::Widget.externals("js").should == [
103
+ Erector::Widget.externals("js").map(&:text).should == [
46
104
  "/lib/jquery.js",
47
105
  "/lib/picante.js",
48
106
  "/lib/dairy.js",