roadie 2.0.0 → 2.1.0.pre1

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.
@@ -0,0 +1,74 @@
1
+ require 'pathname'
2
+
3
+ module Roadie
4
+ # A provider that looks for files on the filesystem
5
+ #
6
+ # Usage:
7
+ # config.roadie.provider = FilesystemProvider.new("prefix", "path/to/stylesheets")
8
+ #
9
+ # Path specification follows certain rules thatare detailed in {#initialize}.
10
+ #
11
+ # @see #initialize
12
+ class FilesystemProvider < AssetProvider
13
+ # @return [Pathname] Pathname representing the directory of the assets
14
+ attr_reader :path
15
+
16
+ # Initializes a new instance of FilesystemProvider.
17
+ #
18
+ # The passed path can come in some variants:
19
+ # * +Pathname+ - will be used as-is
20
+ # * +String+ - If pointing to an absolute path, uses that path. If a relative path, relative from the +Rails.root+
21
+ # * +nil+ - Use the default path (equal to "public/stylesheets")
22
+ #
23
+ # @example Pointing to a directory in the project
24
+ # FilesystemProvider.new(Rails.root.join("public", "assets"))
25
+ # FilesystemProvider.new("public/assets")
26
+ #
27
+ # @example Pointing to external resource
28
+ # FilesystemProvider.new("/home/app/stuff")
29
+ #
30
+ # @param [String] prefix The prefix (see {#prefix})
31
+ # @param [String, Pathname, nil] path The path to use
32
+ def initialize(prefix = "/stylesheets", path = nil)
33
+ super(prefix)
34
+ if path
35
+ @path = resolve_path(path)
36
+ else
37
+ @path = default_path
38
+ end
39
+ end
40
+
41
+ # Looks for the file in the tree. If the file cannot be found, and it does not end with ".css", the lookup
42
+ # will be retried with ".css" appended to the filename.
43
+ #
44
+ # @return [String] contents of the file
45
+ def find(name)
46
+ base = remove_prefix(name)
47
+ file = path.join(base)
48
+ if file.exist?
49
+ file.read.strip
50
+ else
51
+ return find("#{base}.css") if base.to_s !~ /\.css$/
52
+ raise CSSFileNotFound.new(name, base.to_s)
53
+ end
54
+ end
55
+
56
+ private
57
+ def default_path
58
+ resolve_path("public/stylesheets")
59
+ end
60
+
61
+ def resolve_path(path)
62
+ if path.kind_of?(Pathname)
63
+ @path = path
64
+ else
65
+ pathname = Pathname.new(path)
66
+ if pathname.absolute?
67
+ @path = pathname
68
+ else
69
+ @path = Roadie.app.root.join(path)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -21,17 +21,24 @@ module Roadie
21
21
  \)
22
22
  }x
23
23
 
24
- # Initialize a new Inliner with the given CSS, HTML and url_options.
24
+ # Initialize a new Inliner with the given Provider, CSS targets, HTML, and `url_options`.
25
25
  #
26
- # @param [String] css
26
+ # @param [AssetProvider] assets
27
+ # @param [Array] targets List of CSS files to load via the provider
27
28
  # @param [String] html
28
- # @param [Hash] url_options Supported keys: +:host+, +:port+, +:protocol+ and +:asset_path_prefix:+
29
- def initialize(css, html, url_options)
30
- @css = css
31
- @inline_css = []
29
+ # @param [Hash] url_options Supported keys: +:host+, +:port+, +:protocol+
30
+ def initialize(assets, targets, html, url_options)
31
+ @assets = assets
32
+ @css = assets.all(targets)
32
33
  @html = html
34
+ @inline_css = []
33
35
  @url_options = url_options
34
- @asset_path_prefix = (url_options && url_options[:asset_path_prefix] || "/assets/")
36
+
37
+ if url_options and url_options[:asset_path_prefix]
38
+ raise ArgumentError, "The asset_path_prefix URL option is not working anymore. You need to add the following configuration to your application.rb:\n" +
39
+ " config.roadie.provider = AssetPipelineProvider.new(#{url_options[:asset_path_prefix].inspect})\n" +
40
+ "Note that the prefix \"/assets\" is the default one, so you do not need to configure anything in that case."
41
+ end
35
42
  end
36
43
 
37
44
  # Start the inlining and return the final HTML output
@@ -50,7 +57,7 @@ module Roadie
50
57
  end
51
58
 
52
59
  private
53
- attr_reader :css, :html, :url_options, :document
60
+ attr_reader :css, :html, :assets, :url_options, :document
54
61
 
55
62
  def inline_css
56
63
  @inline_css.join("\n")
@@ -63,13 +70,6 @@ module Roadie
63
70
  end
64
71
  end
65
72
 
66
- def find_asset_from_url(url)
67
- asset_filename = url.path.sub(/^#{Regexp.quote(@asset_path_prefix)}/, '').gsub(%r{^/|//+}, '')
68
- Roadie.assets[asset_filename].tap do |asset|
69
- raise CSSFileNotFound.new(asset_filename, url) unless asset
70
- end
71
- end
72
-
73
73
  def adjust_html
74
74
  Nokogiri::HTML.parse(html).tap do |document|
75
75
  yield document
@@ -99,7 +99,7 @@ module Roadie
99
99
 
100
100
  def extract_link_elements
101
101
  all_link_elements_to_be_inlined_with_url.each do |link, url|
102
- asset = find_asset_from_url(url)
102
+ asset = assets.find(url.path)
103
103
  @inline_css << asset.to_s
104
104
  link.remove
105
105
  end
@@ -0,0 +1,22 @@
1
+ require 'action_mailer'
2
+ require 'roadie/action_mailer_extensions'
3
+
4
+ module Roadie
5
+ # {Roadie::Railtie} registers {Roadie} with the current Rails application
6
+ # It adds another configuration option:
7
+ #
8
+ # config.roadie.provider = nil
9
+ #
10
+ # You can use this to set a provider yourself.
11
+ #
12
+ # @see Roadie
13
+ # @see AssetProvider
14
+ class Railtie < Rails::Railtie
15
+ config.roadie = ActiveSupport::OrderedOptions.new
16
+ config.roadie.provider = nil
17
+
18
+ initializer "roadie.extend_action_mailer" do
19
+ ActionMailer::Base.send :include, Roadie::ActionMailerExtensions
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Roadie
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0.pre1'
3
3
  end
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency 'actionmailer', '~> 3.1.0'
20
20
  s.add_dependency 'sprockets'
21
21
 
22
+ s.add_development_dependency 'rails'
22
23
  s.add_development_dependency 'rspec-rails', '>= 2.0.0'
23
24
 
24
25
  s.extra_rdoc_files = %w[README.md Changelog.md]
@@ -0,0 +1,10 @@
1
+ body { background: url(../images/dots.png) repeat-x; }
2
+ #message { background-color: #fff; margin: 0 auto; width: 75%; }
3
+
4
+ h1 { color: #eee; }
5
+ strong {
6
+ -moz-box-shadow: #62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0;
7
+ -webkit-box-shadow: #62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0;
8
+ -o-box-shadow: #62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0;
9
+ box-shadow: #62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0;
10
+ }
@@ -1,68 +1,83 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "roadie integration" do
4
- class IntegrationMailer < ActionMailer::Base
5
- default :css => :integration, :from => 'john@example.com'
6
- append_view_path FIXTURES_PATH.join('views')
7
-
8
- def notification(to, reason)
9
- @reason = reason
10
- mail(:subject => 'Notification for you', :to => to) { |format| format.html; format.text }
3
+ module Roadie
4
+ shared_examples "roadie integration" do
5
+ mailer = Class.new(ActionMailer::Base) do
6
+ def self.name() "IntegrationMailer" end
7
+
8
+ default :css => :integration, :from => 'john@example.com'
9
+ append_view_path FIXTURES_PATH.join('views')
10
+
11
+ def notification(to, reason)
12
+ @reason = reason
13
+ mail(:subject => 'Notification for you', :to => to) { |format| format.html; format.text }
14
+ end
15
+
16
+ def marketing(to)
17
+ headers('X-Spam' => 'No way! Trust us!')
18
+ mail(:subject => 'Buy cheap v1agra', :to => to)
19
+ end
11
20
  end
12
21
 
13
- def marketing(to)
14
- headers('X-Spam' => 'No way! Trust us!')
15
- mail(:subject => 'Buy cheap v1agra', :to => to)
22
+ before(:each) do
23
+ Rails.application.config.action_mailer.default_url_options = {:host => "example.app.org"}
24
+
25
+ Rails.stub(:root => FIXTURES_PATH)
26
+ mailer.delivery_method = :test
16
27
  end
17
- end
18
28
 
19
- before(:each) do
20
- config = double(:action_mailer => double(:default_url_options => {:host => "example.app.org"}))
21
- Rails.application.stub(:config => config)
29
+ it "inlines styles for an email" do
30
+ email = mailer.notification('doe@example.com', 'your quota limit has been reached')
22
31
 
23
- Rails.stub(:root => FIXTURES_PATH)
24
- IntegrationMailer.delivery_method = :test
25
- end
32
+ email.to.should == ['doe@example.com']
33
+ email.from.should == ['john@example.com']
34
+ email.should have(2).parts
26
35
 
27
- it "inlines styles for an email" do
28
- email = IntegrationMailer.notification('doe@example.com', 'your quota limit has been reached')
36
+ email.parts.find { |part| part.mime_type == 'text/html' }.tap do |html_part|
37
+ document = Nokogiri::HTML.parse(html_part.body.decoded)
38
+ document.should have_selector('html > head + body')
39
+ document.should have_selector('body #message h1')
40
+ document.should have_styling('background' => 'url(http://example.app.org/images/dots.png) repeat-x').at_selector('body')
41
+ document.should have_selector('strong[contains("quota")]')
42
+ end
29
43
 
30
- email.to.should == ['doe@example.com']
31
- email.from.should == ['john@example.com']
32
- email.should have(2).parts
44
+ email.parts.find { |part| part.mime_type == 'text/plain' }.tap do |plain_part|
45
+ plain_part.body.decoded.should_not match(/<.*>/)
46
+ end
33
47
 
34
- email.parts.find { |part| part.mime_type == 'text/html' }.tap do |html_part|
35
- document = Nokogiri::HTML.parse(html_part.body.decoded)
36
- document.should have_selector('html > head + body')
37
- document.should have_selector('body #message h1')
38
- document.should have_styling('background' => 'url(http://example.app.org/images/dots.png) repeat-x').at_selector('body')
39
- document.should have_selector('strong[contains("quota")]')
48
+ # If we deliver mails we can catch weird problems with headers being invalid
49
+ email.deliver
40
50
  end
41
51
 
42
- email.parts.find { |part| part.mime_type == 'text/plain' }.tap do |plain_part|
43
- plain_part.body.decoded.should_not match(/<.*>/)
52
+ it "does not add headers for the roadie options" do
53
+ email = mailer.notification('doe@example.com', 'no berries left in chest')
54
+ email.header.fields.map(&:name).should_not include('css')
44
55
  end
45
56
 
46
- # If we deliver mails we can catch weird problems with headers being invalid
47
- email.deliver
48
- end
57
+ it "keeps custom headers in place" do
58
+ email = mailer.marketing('everyone@inter.net')
59
+ email.header['X-Spam'].should be_present
60
+ end
49
61
 
50
- it "does not add headers for the roadie options" do
51
- email = IntegrationMailer.notification('doe@example.com', 'no berries left in chest')
52
- email.header.fields.map(&:name).should_not include('css')
62
+ it "applies CSS3 styles" do
63
+ email = mailer.notification('doe@example.com', 'your quota limit has been reached')
64
+ document = Nokogiri::HTML.parse(email.html_part.body.decoded)
65
+ strong_node = document.css('strong').first
66
+ stylings = SpecHelpers.styling_of_node(strong_node)
67
+ stylings.should include(['box-shadow', '#62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0'])
68
+ stylings.should include(['-o-box-shadow', '#62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0'])
69
+ end
53
70
  end
54
71
 
55
- it "keeps custom headers in place" do
56
- email = IntegrationMailer.marketing('everyone@inter.net')
57
- email.header['X-Spam'].should be_present
72
+ describe "filesystem integration" do
73
+ it_behaves_like "roadie integration" do
74
+ before(:each) { Rails.application.config.assets.enabled = false }
75
+ end
58
76
  end
59
77
 
60
- it "applies CSS3 styles" do
61
- email = IntegrationMailer.notification('doe@example.com', 'your quota limit has been reached')
62
- document = Nokogiri::HTML.parse(email.html_part.body.decoded)
63
- strong_node = document.css('strong').first
64
- stylings = SpecHelpers.styling_of_node(strong_node)
65
- stylings.should include(['box-shadow', '#62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0'])
66
- stylings.should include(['-o-box-shadow', '#62b0d7 1px 1px 1px 1px inset, #aaaaaa 1px 1px 3px 0'])
78
+ describe "asset pipeline integration" do
79
+ it_behaves_like "roadie integration" do
80
+ before(:each) { Rails.application.config.assets.enabled = true }
81
+ end
67
82
  end
68
83
  end
@@ -1,106 +1,122 @@
1
1
  # coding: utf-8
2
2
  require 'spec_helper'
3
3
 
4
- describe Roadie::ActionMailerExtensions, "inlining styles" do
5
- class InliningMailer < ActionMailer::Base
6
- default :css => :simple
7
-
8
- def multipart
9
- mail(:subject => "Multipart email") do |format|
10
- format.html { render :text => 'Hello HTML' }
11
- format.text { render :text => 'Hello Text' }
4
+ module Roadie
5
+ describe ActionMailerExtensions, "CSS selection" do
6
+ mailer = Class.new(ActionMailer::Base) do
7
+ default :css => :default
8
+
9
+ # Just to make ActionMailer::Base not puke
10
+ def self.name
11
+ "SomeMailer"
12
12
  end
13
- end
14
13
 
15
- def singlepart_html
16
- mail(:subject => "HTML email") do |format|
17
- format.html { render :text => 'Hello HTML' }
14
+ def default_css
15
+ mail(:subject => "Default CSS") do |format|
16
+ format.html { render :text => '' }
17
+ end
18
18
  end
19
- end
20
19
 
21
- def singlepart_plain
22
- mail(:subject => "Text email") do |format|
23
- format.text { render :text => 'Hello Text' }
20
+ def override_css(css)
21
+ mail(:subject => "Default CSS", :css => css) do |format|
22
+ format.html { render :text => '' }
23
+ end
24
24
  end
25
25
  end
26
- end
27
-
28
- before(:each) do
29
- Roadie.stub!(:load_css => 'loaded css')
30
- Roadie.stub!(:inline_css => 'unexpected value passed to inline_css')
31
- end
32
26
 
33
- describe "for singlepart text/plain" do
34
- it "does not touch the email body" do
35
- Roadie.should_not_receive(:inline_css)
36
- InliningMailer.singlepart_plain
27
+ def expect_global_css(files)
28
+ Roadie.should_receive(:inline_css).with(provider, files, anything, anything).and_return('')
37
29
  end
38
- end
39
30
 
40
- describe "for singlepart text/html" do
41
- it "inlines css to the email body" do
42
- Roadie.should_receive(:inline_css).with(anything, 'Hello HTML', anything).and_return('html')
43
- InliningMailer.singlepart_html.body.decoded.should == 'html'
31
+ let(:provider) { double("asset provider", :all => '') }
32
+
33
+ before(:each) do
34
+ Roadie.stub(:inline_css => 'unexpected value passed to inline_css')
35
+ Roadie.stub(:current_provider => provider)
44
36
  end
45
- end
46
37
 
47
- describe "for multipart" do
48
- it "keeps both parts" do
49
- InliningMailer.multipart.should have(2).parts
38
+ it "uses no global CSS when :css is set to nil" do
39
+ expect_global_css []
40
+ mailer.override_css(nil)
50
41
  end
51
42
 
52
- it "inlines css to the email's html part" do
53
- Roadie.should_receive(:inline_css).with(anything, 'Hello HTML', anything).and_return('html')
54
- email = InliningMailer.multipart
55
- email.html_part.body.decoded.should == 'html'
56
- email.text_part.body.decoded.should == 'Hello Text'
43
+ it "uses no global CSS when :css is set to false" do
44
+ expect_global_css []
45
+ mailer.override_css(false)
57
46
  end
58
- end
59
- end
60
47
 
61
- describe Roadie::ActionMailerExtensions, "loading css files" do
62
- class CssLoadingMailer < ActionMailer::Base
63
- default :css => :default_value
64
- def use_default
65
- mail &with_empty_html_response
48
+ it "uses the default CSS when :css is not specified" do
49
+ expect_global_css ['default']
50
+ mailer.default_css
66
51
  end
67
52
 
68
- def override(target)
69
- mail :css => target, &with_empty_html_response
53
+ it "uses the specified CSS instead of the default" do
54
+ expect_global_css ['some', 'other/files']
55
+ mailer.override_css([:some, 'other/files'])
70
56
  end
57
+ end
71
58
 
72
- protected
73
- def with_empty_html_response
74
- Proc.new { |format| format.html { render :text => '' } }
59
+ describe ActionMailerExtensions, "using HTML" do
60
+ mailer = Class.new(ActionMailer::Base) do
61
+ default :css => :simple
62
+
63
+ # Just to make ActionMailer::Base not puke
64
+ def self.name
65
+ "SomeMailer"
75
66
  end
76
- end
77
67
 
78
- before(:each) do
79
- Roadie.stub!(:inline_css => 'html')
80
- end
68
+ def multipart
69
+ mail(:subject => "Multipart email") do |format|
70
+ format.html { render :text => 'Hello HTML' }
71
+ format.text { render :text => 'Hello Text' }
72
+ end
73
+ end
81
74
 
82
- it "loads css from Roadie" do
83
- Roadie.should_receive(:load_css).with(anything).and_return('')
84
- CssLoadingMailer.use_default
85
- end
75
+ def singlepart_html
76
+ mail(:subject => "HTML email") do |format|
77
+ format.html { render :text => 'Hello HTML' }
78
+ end
79
+ end
86
80
 
87
- it "loads the css specified in the default mailer settings" do
88
- Roadie.should_receive(:load_css).with(['default_value']).and_return('')
89
- CssLoadingMailer.use_default
90
- end
81
+ def singlepart_plain
82
+ mail(:subject => "Text email") do |format|
83
+ format.text { render :text => 'Hello Text' }
84
+ end
85
+ end
86
+ end
91
87
 
92
- it "loads the css specified in the specific mailer action instead of the default choice" do
93
- Roadie.should_receive(:load_css).with(['specific']).and_return('')
94
- CssLoadingMailer.override(:specific)
95
- end
88
+ let(:provider) { double("asset provider", :all => '') }
96
89
 
97
- it "loads no css when specifying false in the mailer action" do
98
- Roadie.should_not_receive(:load_css)
99
- CssLoadingMailer.override(false)
100
- end
90
+ before(:each) do
91
+ Roadie.stub(:inline_css => 'unexpected value passed to inline_css')
92
+ Roadie.stub(:current_provider => provider)
93
+ end
94
+
95
+ describe "for singlepart text/plain" do
96
+ it "does not touch the email body" do
97
+ Roadie.should_not_receive(:inline_css)
98
+ mailer.singlepart_plain
99
+ end
100
+ end
101
+
102
+ describe "for singlepart text/html" do
103
+ it "inlines css to the email body" do
104
+ Roadie.should_receive(:inline_css).with(provider, ['simple'], 'Hello HTML', anything).and_return('html')
105
+ mailer.singlepart_html.body.decoded.should == 'html'
106
+ end
107
+ end
101
108
 
102
- it "loads multiple css files when given an array" do
103
- Roadie.should_receive(:load_css).with(['specific', 'other']).and_return('')
104
- CssLoadingMailer.override([:specific, :other])
109
+ describe "for multipart" do
110
+ it "keeps both parts" do
111
+ mailer.multipart.should have(2).parts
112
+ end
113
+
114
+ it "inlines css to the email's html part" do
115
+ Roadie.should_receive(:inline_css).with(provider, ['simple'], 'Hello HTML', anything).and_return('html')
116
+ email = mailer.multipart
117
+ email.html_part.body.decoded.should == 'html'
118
+ email.text_part.body.decoded.should == 'Hello Text'
119
+ end
120
+ end
105
121
  end
106
122
  end