eyeliner 0.0.1 → 0.0.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.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ tmp
data/lib/eyeliner.rb CHANGED
@@ -1,44 +1,117 @@
1
1
  require 'nokogiri'
2
2
  require 'css_parser'
3
3
 
4
- module Eyeliner
4
+ class Eyeliner
5
5
 
6
- class Inliner
6
+ def initialize(attributes = {})
7
+ @css = attributes[:css] || ""
8
+ @stylesheet_base = attributes[:stylesheet_base]
9
+ end
10
+
11
+ attr_accessor :stylesheet_base
12
+
13
+ StyleRule = Struct.new(:declarations, :specificity) do
14
+
15
+ def <=>(other)
16
+ specificity <=> other.specificity
17
+ end
18
+
19
+ def to_s
20
+ declarations
21
+ end
22
+
23
+ end
24
+
25
+ def add_css(css)
26
+ @css << css
27
+ end
28
+
29
+ def css
30
+ @css.dup
31
+ end
32
+
33
+ def apply_to(input)
34
+ Application.new(self, input).apply
35
+ end
7
36
 
8
- attr_accessor :css
37
+ # encapsulates the application of CSS to some HTML input
38
+ class Application
9
39
 
10
- def initialize
11
- @css = ""
40
+ def initialize(eyeliner, input)
41
+ @eyeliner = eyeliner
42
+ @input = input
43
+ @css = @eyeliner.css
12
44
  end
13
45
 
14
- StyleRule = Struct.new(:declarations, :specificity) do
46
+ def apply
47
+ parse_input
48
+ extract_stylesheets
49
+ parse_css
50
+ map_styles_to_elements
51
+ apply_styles_to_elements
52
+ @doc.to_html
53
+ end
54
+
55
+ private
15
56
 
16
- def <=>(other)
17
- specificity <=> other.specificity
57
+ def parse_input
58
+ @doc = case @input
59
+ when Nokogiri::XML::Node
60
+ @input
61
+ else
62
+ Nokogiri::HTML.parse(@input)
18
63
  end
64
+ end
19
65
 
20
- def to_s
21
- declarations
66
+ def extract_stylesheets
67
+ @doc.css("style, link[rel=stylesheet][type='text/css']").each do |element|
68
+ case element.name
69
+ when "style"
70
+ @css << element.content
71
+ when "link"
72
+ @css << read_stylesheet(element["href"])
73
+ end
74
+ element.remove
22
75
  end
76
+ end
23
77
 
78
+ def read_stylesheet(name)
79
+ full_path = File.join(@eyeliner.stylesheet_base, name)
80
+ File.read(full_path)
24
81
  end
25
82
 
26
- def inline(input)
27
- fragment = Nokogiri::HTML.fragment(input)
28
- css_parser = CssParser::Parser.new
29
- css_parser.add_block!(css)
30
- styles_by_element = Hash.new do |h,k|
83
+ def parse_css
84
+ @css_parser = CssParser::Parser.new
85
+ @css_parser.add_block!(@css)
86
+ @styles_by_element = Hash.new do |h,k|
31
87
  h[k] = []
32
88
  end
33
- css_parser.each_selector do |selector, declarations, specificity|
34
- fragment.css(selector).each do |element|
35
- styles_by_element[element] << StyleRule.new(declarations, specificity)
89
+ end
90
+
91
+ def map_styles_to_elements
92
+ @css_parser.each_selector do |selector, declarations, specificity|
93
+ @doc.css(selector, PsuedoClassHandler.new).each do |element|
94
+ @styles_by_element[element] << StyleRule.new(declarations, specificity)
36
95
  end
37
96
  end
38
- styles_by_element.each do |element, rules|
39
- element["style"] = rules.sort.join(" ")
97
+ end
98
+
99
+ def apply_styles_to_elements
100
+ @styles_by_element.each do |element, rules|
101
+ parts = rules.sort
102
+ parts.push(element["style"]) if element["style"]
103
+ element["style"] = parts.join(" ")
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ class PsuedoClassHandler
110
+
111
+ %w(visited active hover focus).each do |psuedo_class|
112
+ define_method(psuedo_class) do |node_set|
113
+ []
40
114
  end
41
- fragment.to_html
42
115
  end
43
116
 
44
117
  end
@@ -1,3 +1,3 @@
1
- module Eyeliner
2
- VERSION = "0.0.1"
1
+ class Eyeliner
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,15 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Eyeliner::Inliner do
3
+ describe Eyeliner do
4
4
 
5
- let(:eyeliner) { Eyeliner::Inliner.new }
5
+ let(:eyeliner) { Eyeliner.new }
6
+
7
+ def parse_fragment(html)
8
+ Nokogiri::HTML.fragment(html)
9
+ end
6
10
 
7
11
  def should_not_modify(input)
8
- eyeliner.inline(input).should == input
12
+ eyeliner.apply_to(parse_fragment(input)).should == input
9
13
  end
10
14
 
11
15
  def should_modify(input, options)
12
- eyeliner.inline(input).should == options[:to]
16
+ eyeliner.apply_to(parse_fragment(input)).should == options[:to]
17
+ end
18
+
19
+ it "can be initialized with attributes" do
20
+ eyeliner = Eyeliner.new(:stylesheet_base => "STYLESHEETS", :css => "SOME CSS")
21
+ eyeliner.stylesheet_base.should == "STYLESHEETS"
22
+ eyeliner.css.should == "SOME CSS"
13
23
  end
14
24
 
15
25
  context "with no CSS" do
@@ -39,7 +49,7 @@ describe Eyeliner::Inliner do
39
49
  context "with some explicit CSS" do
40
50
 
41
51
  before do
42
- eyeliner.css << %{
52
+ eyeliner.add_css %{
43
53
  .box { border: 1px solid green; }
44
54
  .small { font-size: 8px; }
45
55
  }
@@ -66,14 +76,15 @@ describe Eyeliner::Inliner do
66
76
  :to => %(<p class="small box" style="border: 1px solid green; font-size: 8px;">xyz</p>)
67
77
  end
68
78
 
79
+
69
80
  end
70
81
 
71
82
  end
72
83
 
73
- context "where CSS rules conflict" do
84
+ context "where multiple CSS rules apply" do
74
85
 
75
86
  before do
76
- eyeliner.css << %{
87
+ eyeliner.add_css %{
77
88
  p { color: red; }
78
89
  p.small { text-decoration: underline; }
79
90
  .small { font-size: 8px; }
@@ -87,6 +98,111 @@ describe Eyeliner::Inliner do
87
98
  :to => %(<p class="small" style="color: red; font-size: 8px; text-decoration: underline;">xyz</p>)
88
99
  end
89
100
 
101
+ it "retains contents of existing style attribute" do
102
+ should_modify %(<p style="border: 1px solid blue;">xyz</p>),
103
+ :to => %(<p style="color: red; border: 1px solid blue;">xyz</p>)
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
110
+ %w(visited active hover focus).each do |pseudo_class|
111
+ context "when CSS rule contains pseudo-class :#{pseudo_class}" do
112
+
113
+ before do
114
+ eyeliner.add_css %{
115
+ a:#{pseudo_class} { color: red; }
116
+ }
117
+ end
118
+
119
+ it "is ignored" do
120
+ should_not_modify("<a>xyz</a>")
121
+ end
122
+
123
+ end
124
+ end
125
+
126
+ context "when the document contains a <style> block" do
127
+
128
+ before do
129
+ @input = <<-HTML
130
+ <html>
131
+ <head>
132
+ <style>
133
+ strong { text-decoration: underline; }
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <p>
138
+ Feeling <strong>STRONG</strong>.
139
+ </p>
140
+ </body>
141
+ </html>
142
+ HTML
143
+ end
144
+
145
+ describe "#apply_to" do
146
+
147
+ before do
148
+ @output = eyeliner.apply_to(@input)
149
+ @output_doc = Nokogiri::HTML.fragment(@output)
150
+ end
151
+
152
+ it "inlines the styles" do
153
+ strong_element = @output_doc.css("strong").first
154
+ strong_element["style"].should == "text-decoration: underline;"
155
+ end
156
+
157
+ it "removes the <style> element" do
158
+ @output_doc.css("style").should be_empty
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+
165
+ context "when the document contains a linked stylesheet" do
166
+
167
+ before do
168
+
169
+ $tmp_dir.mkdir
170
+ ($tmp_dir + "styles.css").open("w") do |css_io|
171
+ css_io.puts <<-CSS
172
+ .email h1 { text-decoration: underline; }
173
+ CSS
174
+ end
175
+
176
+ @input = <<-HTML
177
+ <html>
178
+ <head>
179
+ <link rel="stylesheet" href="styles.css" type="text/css" />
180
+ </head>
181
+ <body class="email">
182
+ <h1>Hello</h1>
183
+ </body>
184
+ </html>
185
+ HTML
186
+
187
+ end
188
+
189
+ describe "#apply_to" do
190
+
191
+ before do
192
+ eyeliner.stylesheet_base = $tmp_dir.to_s
193
+ @output = eyeliner.apply_to(@input)
194
+ @output_doc = Nokogiri::HTML.fragment(@output)
195
+ end
196
+
197
+ it "inlines the styles" do
198
+ h1_element = @output_doc.css("h1").first
199
+ h1_element["style"].should == "text-decoration: underline;"
200
+ end
201
+
202
+ it "removes the <link> element" do
203
+ @output_doc.css("link").should be_empty
204
+ end
205
+
90
206
  end
91
207
 
92
208
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,18 @@
1
1
  require 'rspec'
2
2
 
3
- require 'eyeliner'
3
+ require 'eyeliner'
4
+
5
+ require 'pathname'
6
+
7
+ $project_dir = Pathname(__FILE__).expand_path.parent.parent
8
+ $tmp_dir = $project_dir + "tmp"
9
+
10
+ RSpec.configure do |config|
11
+
12
+ config.before(:each) do
13
+ if $tmp_dir.exist?
14
+ $tmp_dir.rmtree
15
+ end
16
+ end
17
+
18
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: eyeliner
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.1
5
+ version: 0.0.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mike Williams
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2011-07-13 00:00:00 Z
14
+ date: 2011-07-18 00:00:00 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: nokogiri
@@ -67,7 +67,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- hash: 3190977879137818689
70
+ hash: -1764815189923101084
71
71
  segments:
72
72
  - 0
73
73
  version: "0"
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  requirements:
77
77
  - - ">="
78
78
  - !ruby/object:Gem::Version
79
- hash: 3190977879137818689
79
+ hash: -1764815189923101084
80
80
  segments:
81
81
  - 0
82
82
  version: "0"