inline_styles 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jack Danger Canty
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,34 @@
1
+ # InlineStyles
2
+
3
+ Squish a CSS stylesheet into any semantic html
4
+
5
+ ## Why?
6
+
7
+ HTML-formatted emails don't render uniformly unless the css is attached to the 'style' attribute of every element. Unless you want to maintain your templates with explicit inline styles you'll need to apply them automatically.
8
+
9
+ ## How?
10
+
11
+ <pre>
12
+ class Mailer < ActionMailer::Base
13
+ def message(email)
14
+ recipients email
15
+ subject "Looks nice, eh?"
16
+ html = render(:file => "message.html",
17
+ :layout => "email_layout.html")
18
+ body InlineStyles::Page.new(html).apply(stylesheet_content)
19
+ end
20
+
21
+ protected
22
+
23
+ def stylesheet_content
24
+ File.read("#{Rails.root}/public/stylesheets/messages.css")
25
+ end
26
+ end
27
+ </pre>
28
+
29
+ ## Requirements
30
+ InlineStyles uses the [css_parser](http://github.com/DanaDanger/css_parser) and [Hpricot](http://github.com/hpricot/hpricot) gems
31
+
32
+ ### Copyright
33
+
34
+ Copyright (c) 2009 [Jack Danger Canty](http://jåck.com). See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "inline_styles"
8
+ gem.summary = %Q{Squish a CSS stylesheet into any semantic HTML}
9
+ gem.description = %Q{To make your HTML display properly when stylesheet support isn't available (e.g. Gmail) this lets you attach all your CSS to the 'style' attribute of each element.}
10
+ gem.email = "gitcommit@6brand.com"
11
+ gem.homepage = "http://github.com/JackDanger/inline_styles"
12
+ gem.authors = ["Jack Danger Canty"]
13
+ gem.add_dependency "hpricot", ">= 0"
14
+ gem.add_dependency "css_parser", ">= 0"
15
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/test_*.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "inline_styles #{version}"
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.3
@@ -0,0 +1,67 @@
1
+
2
+ module InlineStyles
3
+ class Page
4
+ attr_accessor :html
5
+
6
+ def initialize(html)
7
+ @html = html
8
+ end
9
+
10
+ def apply(css)
11
+ require_dependencies
12
+
13
+ parser = CssParser::Parser.new
14
+ parser.add_block! css
15
+
16
+ tree = Hpricot(html)
17
+
18
+ stable_sorter = 0
19
+ selectors = []
20
+
21
+ # extracting selectors via the API rather than
22
+ # just reaching in and grabbing @selectors
23
+ parser.each_selector do |selector, declarations, specificity|
24
+ selectors << [selector, declarations, specificity]
25
+ end
26
+
27
+ # stable-sort the selectors so that we get them sorted
28
+ # by specificity but also keeping their rough
29
+ # original order. This is how CSS selectors are applied
30
+ # in a browser
31
+ selectors.sort_by do |selector|
32
+ [selector.last, stable_sorter += 1]
33
+ end.each do |selector, declarations, spec|
34
+ # Find each element matching the given slector
35
+ (tree/selector).each do |element|
36
+ # Merge any previously-inlined style with the
37
+ # latest (higher specificity) one
38
+ element['style'] ||= ''
39
+ element['style'] = CssParser.merge(
40
+ CssParser::RuleSet.new('', element['style'], 1),
41
+ CssParser::RuleSet.new('', declarations, 2)
42
+ ).declarations_to_s
43
+ end
44
+ end
45
+
46
+ tree.to_s
47
+ end
48
+
49
+ protected
50
+
51
+ def require_dependencies
52
+ gem 'css_parser'
53
+ require 'css_parser'
54
+ gem 'hpricot'
55
+ require 'hpricot'
56
+ end
57
+ end
58
+
59
+ # taken from http://moserei.de/index.php/17/stable-array-sorting-in-ruby
60
+ class StableSortingArray < Array
61
+ def sort_by
62
+ n = 0
63
+ c = lambda {|x| n+= 1; [x, n] }
64
+ sort {|a, b| yield(c.call(a), c.call(b)) }
65
+ end
66
+ end
67
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'inline_styles'
8
+
9
+ class Test::Unit::TestCase
10
+ def assert_has_style(element, style)
11
+ assert element['style'].include?(style), "Expected #{element['style'].inspect} to include #{style.inspect}"
12
+ end
13
+ def assert_does_not_have_style(element, style)
14
+ assert !element['style'].include?(style), "Expected #{element['style'].inspect} to not include #{style.inspect}"
15
+ end
16
+ end
@@ -0,0 +1,86 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestInlineStyles < Test::Unit::TestCase
4
+
5
+ CSS = <<-EOCSS
6
+ div {display: block;}
7
+ div small {font-size: 14px}
8
+ small {font-size: 0.7em}
9
+ img {border: none}
10
+ div small img {border: 1px solid #000}
11
+ EOCSS
12
+
13
+ HTML = <<-EOHTML
14
+ <body>
15
+ <div> Welcome </div>
16
+ <small> stay awhile! </small>
17
+ <div>
18
+ <small>
19
+ <img src='i.png'>
20
+ </small>
21
+ </small>
22
+ </body>
23
+ EOHTML
24
+
25
+ context "Applying CSS to HTML" do
26
+ setup {
27
+ @inline = InlineStyles::Page.new(HTML).apply(CSS)
28
+ @tree = Hpricot(@inline)
29
+ }
30
+ should "apply <div> style to each <div>" do
31
+ (@tree/:div).each do |element|
32
+ assert_has_style element, "display: block;"
33
+ end
34
+ end
35
+ should "apply specific style to nested <small>" do
36
+ assert_has_style(
37
+ @tree.at('div small'),
38
+ "font-size: 14px;"
39
+ )
40
+ end
41
+ should "apply generic <small> style to top-nested <small>" do
42
+ assert_has_style(
43
+ @tree.at('body > small'),
44
+ "font-size: 0.7em;"
45
+ )
46
+ end
47
+ should "not apply generic <small> style to nested <small>" do
48
+ assert_does_not_have_style(
49
+ @tree.at('div small'),
50
+ "font-size: 0.7em;"
51
+ )
52
+ end
53
+ should "not apply nested <small> style to top-level <small>" do
54
+ assert_does_not_have_style(
55
+ @tree.at('body > small'),
56
+ "font-size: 14px;"
57
+ )
58
+ end
59
+ should "apply specific border to deeply nested <img>" do
60
+ assert_has_style(
61
+ @tree.at('img'),
62
+ "border: 1px solid #000;"
63
+ )
64
+ end
65
+ should "not apply generic border style to <img>" do
66
+ assert_does_not_have_style(
67
+ @tree.at('img'),
68
+ "border: none;"
69
+ )
70
+ end
71
+ should "render inline html exactly as expected" do
72
+ assert_equal <<-NEWHTML, @inline
73
+ <body>
74
+ <div style="display: block;"> Welcome </div>
75
+ <small style="font-size: 0.7em;"> stay awhile! </small>
76
+ <div style="display: block;">
77
+ <small style="font-size: 14px;">
78
+ <img src="i.png" style="border: 1px solid #000;" />
79
+ </small>
80
+ </small>
81
+ </div></body>
82
+ NEWHTML
83
+ end
84
+ end
85
+
86
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inline_styles
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Jack Danger Canty
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-22 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: css_parser
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: thoughtbot-shoulda
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: To make your HTML display properly when stylesheet support isn't available (e.g. Gmail) this lets you attach all your CSS to the 'style' attribute of each element.
46
+ email: gitcommit@6brand.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.markdown
54
+ files:
55
+ - LICENSE
56
+ - README.markdown
57
+ - Rakefile
58
+ - VERSION
59
+ - lib/inline_styles.rb
60
+ - test/helper.rb
61
+ - test/test_inline_styles.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/JackDanger/inline_styles
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.5
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Squish a CSS stylesheet into any semantic HTML
90
+ test_files:
91
+ - test/helper.rb
92
+ - test/test_inline_styles.rb