eyeliner 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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"