stylesheet 0.1.0

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.
Files changed (81) hide show
  1. data/.gitignore +20 -0
  2. data/Gemfile +4 -0
  3. data/Guardfile +9 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +63 -0
  6. data/Rakefile +22 -0
  7. data/features/document_styles.feature +15 -0
  8. data/features/rule_declarations.feature +9 -0
  9. data/features/step_definitions/document_steps.rb +39 -0
  10. data/features/step_definitions/rule_declaration_steps.rb +13 -0
  11. data/features/step_definitions/style_rule_steps.rb +31 -0
  12. data/features/style_rules.feature +15 -0
  13. data/features/support/env.rb +6 -0
  14. data/lib/stylesheet.rb +35 -0
  15. data/lib/stylesheet/css_charset_rule.rb +24 -0
  16. data/lib/stylesheet/css_font_face_rule.rb +26 -0
  17. data/lib/stylesheet/css_import_rule.rb +41 -0
  18. data/lib/stylesheet/css_media_rule.rb +30 -0
  19. data/lib/stylesheet/css_rule.rb +57 -0
  20. data/lib/stylesheet/css_rule_list.rb +30 -0
  21. data/lib/stylesheet/css_style_declaration.rb +41 -0
  22. data/lib/stylesheet/css_style_rule.rb +29 -0
  23. data/lib/stylesheet/css_style_sheet.rb +100 -0
  24. data/lib/stylesheet/document.rb +63 -0
  25. data/lib/stylesheet/errors.rb +5 -0
  26. data/lib/stylesheet/inflector.rb +11 -0
  27. data/lib/stylesheet/location.rb +113 -0
  28. data/lib/stylesheet/media_list.rb +26 -0
  29. data/lib/stylesheet/request.rb +23 -0
  30. data/lib/stylesheet/style_sheet_list.rb +15 -0
  31. data/lib/stylesheet/version.rb +3 -0
  32. data/spec/css_charset_rule_spec.rb +39 -0
  33. data/spec/css_font_face_rule_spec.rb +47 -0
  34. data/spec/css_import_rule_spec.rb +178 -0
  35. data/spec/css_media_rule_spec.rb +57 -0
  36. data/spec/css_rule_list_spec.rb +74 -0
  37. data/spec/css_rule_spec.rb +102 -0
  38. data/spec/css_style_declaration_spec.rb +71 -0
  39. data/spec/css_style_rule_spec.rb +53 -0
  40. data/spec/css_style_sheet_spec.rb +157 -0
  41. data/spec/document_spec.rb +99 -0
  42. data/spec/fixtures/css/absolute_path.html +14 -0
  43. data/spec/fixtures/css/charset.html +13 -0
  44. data/spec/fixtures/css/font_face.html +13 -0
  45. data/spec/fixtures/css/full_url.html +14 -0
  46. data/spec/fixtures/css/html4.html +15 -0
  47. data/spec/fixtures/css/html5.html +14 -0
  48. data/spec/fixtures/css/inline.html +33 -0
  49. data/spec/fixtures/css/inline_import.html +15 -0
  50. data/spec/fixtures/css/invalid.html +14 -0
  51. data/spec/fixtures/css/media.html +13 -0
  52. data/spec/fixtures/css/relative_path.html +14 -0
  53. data/spec/fixtures/css/stylesheets/charset.css +5 -0
  54. data/spec/fixtures/css/stylesheets/colors.css +0 -0
  55. data/spec/fixtures/css/stylesheets/font_face.css +6 -0
  56. data/spec/fixtures/css/stylesheets/fonts.css +0 -0
  57. data/spec/fixtures/css/stylesheets/media.css +3 -0
  58. data/spec/fixtures/css/stylesheets/print.css +3 -0
  59. data/spec/fixtures/css/stylesheets/screen.css +16 -0
  60. data/spec/fixtures/css_import/index.html +14 -0
  61. data/spec/fixtures/css_import/stylesheets/import1.css +3 -0
  62. data/spec/fixtures/css_import/stylesheets/import2.css +3 -0
  63. data/spec/fixtures/css_import/stylesheets/import3.css +3 -0
  64. data/spec/fixtures/css_import/stylesheets/import4.css +3 -0
  65. data/spec/fixtures/css_import/stylesheets/import5.css +3 -0
  66. data/spec/fixtures/css_import/stylesheets/import6.css +3 -0
  67. data/spec/fixtures/css_import/stylesheets/import7.css +3 -0
  68. data/spec/fixtures/css_import/stylesheets/import8.css +3 -0
  69. data/spec/fixtures/css_import/stylesheets/import9.css +3 -0
  70. data/spec/fixtures/css_import/stylesheets/print.css +3 -0
  71. data/spec/fixtures/css_import/stylesheets/screen.css +15 -0
  72. data/spec/fixtures/fonts/VeraSeBd.ttf +0 -0
  73. data/spec/inflector_spec.rb +17 -0
  74. data/spec/location_spec.rb +260 -0
  75. data/spec/media_list_spec.rb +108 -0
  76. data/spec/spec_helper.rb +10 -0
  77. data/spec/stubs/fake_request.rb +19 -0
  78. data/spec/style_sheet_list_spec.rb +53 -0
  79. data/spec/version_spec.rb +9 -0
  80. data/stylesheet.gemspec +34 -0
  81. metadata +294 -0
@@ -0,0 +1,26 @@
1
+ module Stylesheet
2
+ class MediaList
3
+ extend Forwardable
4
+ def_delegators :@media, :length, :size, :[], :each, :<<, :push, :delete, :to_s
5
+ include Enumerable
6
+
7
+ MEDIA_TYPES = %w{all braille embossed handheld print projection screen speech tty tv}
8
+
9
+ def initialize(media_text)
10
+ @media = media_text.to_s.split(",").map {|medium| medium.strip }
11
+ end
12
+
13
+ def item(index)
14
+ @media[index]
15
+ end
16
+
17
+ def media_text
18
+ @media.join(", ")
19
+ end
20
+
21
+ alias_method :to_s, :media_text
22
+
23
+ alias_method :append_medium, :<<
24
+ alias_method :delete_medium, :delete
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ module Stylesheet
2
+ class Request
3
+ def get(url)
4
+ curl = Curl::Easy.perform(url) do |curl|
5
+ curl.headers["User-Agent"] = user_agent
6
+ curl.follow_location = true
7
+ end
8
+
9
+ curl.body_str
10
+
11
+ rescue Stylsheet::Error
12
+ raise
13
+
14
+ # re-raise external library errors in our namespace
15
+ rescue => error
16
+ raise Stylsheet::Error.new("#{error.class}: #{error.message}")
17
+ end
18
+
19
+ def user_agent
20
+ "Ruby/Stylesheet Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Stylesheet
2
+ class StyleSheetList
3
+ extend Forwardable
4
+ def_delegators :@styles, :length, :size, :[], :each, :to_s
5
+ include Enumerable
6
+
7
+ def initialize(styles)
8
+ @styles = styles.map {|args| CssStyleSheet.new(args) }
9
+ end
10
+
11
+ def item(index)
12
+ @styles[index]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Stylesheet
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe CssCharsetRule do
4
+
5
+ let(:css_text) { "@charset \"UTF-8\";" }
6
+
7
+ let(:rule) do
8
+ CssCharsetRule.new(:css_text => css_text)
9
+ end
10
+
11
+ describe "#type" do
12
+ it "shows the type of style rule" do
13
+ expect(rule.type).to eq CssRule::CHARSET_RULE
14
+ end
15
+ end
16
+
17
+ describe "#encoding" do
18
+ it "should return specified encoding" do
19
+ expect(rule.encoding).to eq "UTF-8"
20
+ end
21
+ end
22
+
23
+ describe ".matches_rule?" do
24
+ it "should match text starting with @charset" do
25
+ matches = CssCharsetRule.matches_rule?(css_text)
26
+ expect(matches).to be_true
27
+ end
28
+
29
+ it "should not match text without at-rule" do
30
+ matches = CssCharsetRule.matches_rule?("a:link { color: #357ad1; }")
31
+ expect(matches).to be_false
32
+ end
33
+
34
+ it "should not match text without charset" do
35
+ matches = CssCharsetRule.matches_rule?("@import url(\"import1.css\");")
36
+ expect(matches).to be_false
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe CssFontFaceRule do
4
+
5
+ let(:css_text) do
6
+ "@font-face {
7
+ font-family: \"Bitstream Vera Serif Bold\";
8
+ src: url(\"http://example.com/fonts/VeraSeBd.ttf\");
9
+ }"
10
+ end
11
+
12
+ let(:rule) do
13
+ CssFontFaceRule.new(:css_text => css_text)
14
+ end
15
+
16
+ describe "#style" do
17
+ it "returns the css style declaration for the rule" do
18
+ style = rule.style
19
+
20
+ expect(style).to be_kind_of(CssStyleDeclaration)
21
+ expect(style.length).to eq 2
22
+ end
23
+ end
24
+
25
+ describe "#type" do
26
+ it "shows the type of style rule" do
27
+ expect(rule.type).to eq CssRule::FONT_FACE_RULE
28
+ end
29
+ end
30
+
31
+ describe ".matches_rule?" do
32
+ it "should match text starting with @font-face" do
33
+ matches = CssFontFaceRule.matches_rule?(css_text)
34
+ expect(matches).to be_true
35
+ end
36
+
37
+ it "should not match text without at-rule" do
38
+ matches = CssFontFaceRule.matches_rule?("a:link { color: #357ad1; }")
39
+ expect(matches).to be_false
40
+ end
41
+
42
+ it "should not match text without font-face" do
43
+ matches = CssFontFaceRule.matches_rule?("@import url(\"import1.css\");")
44
+ expect(matches).to be_false
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+
3
+ describe CssImportRule do
4
+ before(:each) do
5
+ Stylesheet.request = FakeRequest.new
6
+ end
7
+
8
+ let(:css_text) { "@import url(\"import1.css\");" }
9
+
10
+ let(:rule) do
11
+ CssImportRule.new(:css_text => css_text)
12
+ end
13
+
14
+ describe "#type" do
15
+ it "shows the type of style rule" do
16
+ expect(rule.type).to eq CssRule::IMPORT_RULE
17
+ end
18
+ end
19
+
20
+ describe "#href" do
21
+ let(:parent) do
22
+ CssStyleSheet.new("http://example.com/css_import/stylesheets/screen.css")
23
+ end
24
+
25
+ let(:rule_url) { "http://example.com/css_import/stylesheets/import1.css" }
26
+
27
+ it "parses an url from the style rule" do
28
+ css_text = "@import url(\"http://example.com/css_import/stylesheets/import1.css\");"
29
+
30
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
31
+ expect(rule.href).to eq "http://example.com/css_import/stylesheets/import1.css"
32
+ expect(rule.location.href).to eq rule_url
33
+ end
34
+
35
+ it "parses an absolute path from the style rule" do
36
+ css_text = "@import url(\"/css_import/stylesheets/import1.css\");"
37
+
38
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
39
+ expect(rule.href).to eq '/css_import/stylesheets/import1.css'
40
+ expect(rule.location.href).to eq rule_url
41
+ end
42
+
43
+ it "parses a relative path with double quotes from the style rule" do
44
+ css_text = "@import url(\"import1.css\");"
45
+
46
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
47
+ expect(rule.href).to eq "import1.css"
48
+ expect(rule.location.href).to eq rule_url
49
+ end
50
+
51
+ it "parses a relative path with single quotes from the style rule" do
52
+ css_text = "@import url(\"import1.css\");"
53
+
54
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
55
+ expect(rule.href).to eq "import1.css"
56
+ expect(rule.location.href).to eq rule_url
57
+ end
58
+
59
+ it "parses a relative path with single medium from the style rule" do
60
+ css_text = '@import url("import1.css") print;'
61
+
62
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
63
+ expect(rule.href).to eq "import1.css"
64
+ expect(rule.location.href).to eq rule_url
65
+ end
66
+
67
+ it "parses a relative path with multiple media from the style rule" do
68
+ css_text = '@import url("import1.css") screen,projection;'
69
+
70
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
71
+ expect(rule.href).to eq "import1.css"
72
+ expect(rule.location.href).to eq rule_url
73
+ end
74
+
75
+ it "parses a relative path with media and orientation from the style rule" do
76
+ css_text = '@import url("import1.css") screen and (orientation:landscape);'
77
+
78
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
79
+ expect(rule.href).to eq "import1.css"
80
+ expect(rule.location.href).to eq rule_url
81
+ end
82
+
83
+ it "parses a relative path without url from the style rule" do
84
+ css_text = '@import "import1.css";'
85
+
86
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
87
+ expect(rule.href).to eq "import1.css"
88
+ expect(rule.location.href).to eq rule_url
89
+ end
90
+
91
+ it "parses a relative path without url and single medium from the style rule" do
92
+ css_text = '@import "import1.css" screen;'
93
+
94
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
95
+ expect(rule.href).to eq "import1.css"
96
+ expect(rule.location.href).to eq rule_url
97
+ end
98
+
99
+ it "parses a relative path without url and multiple media from the style rule" do
100
+ css_text = '@import "import1.css" screen, projection;'
101
+
102
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
103
+ expect(rule.href).to eq "import1.css"
104
+ expect(rule.location.href).to eq rule_url
105
+ end
106
+
107
+ it "parses a relative path with an invalid url from the style rule" do
108
+ css_text = '@import url("invalid.css");'
109
+
110
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
111
+ expect(rule.href).to eq "invalid.css"
112
+ expect(rule.location.href).to eq "http://example.com/css_import/stylesheets/invalid.css"
113
+ end
114
+
115
+ end
116
+
117
+ describe "#style_sheet" do
118
+ let(:parent) do
119
+ CssStyleSheet.new("http://example.com/css_import/stylesheets/screen.css")
120
+ end
121
+
122
+ it "should return a style sheet based on the href" do
123
+ css_text = "@import url(\"import1.css\");"
124
+
125
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
126
+ style = rule.style_sheet
127
+
128
+ expect(style).to be_kind_of(CssStyleSheet)
129
+ expect(style.href).to eq "http://example.com/css_import/stylesheets/import1.css"
130
+ end
131
+ end
132
+
133
+ describe "#media" do
134
+ let(:parent) { Object.new }
135
+
136
+ it "should parse a single medium" do
137
+ css_text = '@import url("import1.css") screen;'
138
+
139
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
140
+ expect(rule.media.length).to eq 1
141
+ expect(rule.media[0]).to eq "screen"
142
+ end
143
+
144
+ it "should parse multiple media" do
145
+ css_text = '@import url("import1.css") screen,projection;'
146
+
147
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
148
+ expect(rule.media.length).to eq 2
149
+ expect(rule.media[0]).to eq "screen"
150
+ end
151
+
152
+ it "should parse media with media query" do
153
+ css_text = '@import url("import1.css") screen and (orientation:landscape);'
154
+
155
+ rule = CssImportRule.new(:css_text => css_text, :parent_style_sheet => parent)
156
+ expect(rule.media.length).to eq 1
157
+ expect(rule.media[0]).to eq "screen and (orientation:landscape)"
158
+ end
159
+
160
+ end
161
+
162
+ describe ".matches_rule?" do
163
+ it "should match text starting with @import" do
164
+ matches = CssImportRule.matches_rule?(css_text)
165
+ expect(matches).to be_true
166
+ end
167
+
168
+ it "should not match text without at-rule" do
169
+ matches = CssImportRule.matches_rule?("a:link { color: #357ad1; }")
170
+ expect(matches).to be_false
171
+ end
172
+
173
+ it "should not match text without import" do
174
+ matches = CssImportRule.matches_rule?("@charset \"UTF-8\";")
175
+ expect(matches).to be_false
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe CssMediaRule do
4
+
5
+ let(:css_text) do
6
+ "@media only screen and (max-width: 850px) {
7
+ #main .section {
8
+ font-weight: bold;
9
+ }
10
+ #main a:link {
11
+ text-decoration: none;
12
+ }
13
+ }"
14
+ end
15
+
16
+ let(:rule) do
17
+ CssMediaRule.new(:css_text => css_text)
18
+ end
19
+
20
+ describe "#css_rules" do
21
+ it "should find child rules" do
22
+ expect(rule.css_rules).to be_kind_of CssRuleList
23
+ expect(rule.css_rules.length).to eq 2
24
+ end
25
+
26
+ it "should find child rules when none are present" do
27
+ css_text = "@media only screen and (max-width: 850px) {}"
28
+ rule = CssMediaRule.new(:css_text => css_text)
29
+
30
+ expect(rule.css_rules).to be_kind_of CssRuleList
31
+ expect(rule.css_rules.length).to eq 0
32
+ end
33
+ end
34
+
35
+ describe "#type" do
36
+ it "shows the type of style rule" do
37
+ expect(rule.type).to eq CssRule::MEDIA_RULE
38
+ end
39
+ end
40
+
41
+ describe ".matches_rule?" do
42
+ it "should match text starting with @media" do
43
+ matches = CssMediaRule.matches_rule?(css_text)
44
+ expect(matches).to be_true
45
+ end
46
+
47
+ it "should not match text without at-rule" do
48
+ matches = CssMediaRule.matches_rule?("a:link { color: #357ad1; }")
49
+ expect(matches).to be_false
50
+ end
51
+
52
+ it "should not match text without media" do
53
+ matches = CssMediaRule.matches_rule?("@charset \"UTF-8\";")
54
+ expect(matches).to be_false
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe CssRuleList do
4
+
5
+ let(:styles) do
6
+ "@charset \"UTF-8\";
7
+
8
+ body {
9
+ color: #444;
10
+ background-color: #535353;
11
+ }
12
+
13
+ @import url(\"import1.css\");
14
+
15
+ @media only screen and (max-width: 850px) {
16
+ #main .section {
17
+ font-weight: bold;
18
+ }
19
+ }
20
+
21
+ @font-face {
22
+ font-family: \"Bitstream Vera Serif Bold\";
23
+ src: url(\"http://example.com/fonts/VeraSeBd.ttf\");
24
+ }"
25
+ end
26
+
27
+ describe ".new" do
28
+ it "parses charset rules" do
29
+ rules = CssRuleList.new(styles)
30
+ expect(rules[0]).to be_kind_of(CssCharsetRule)
31
+ end
32
+
33
+ it "parses style rules" do
34
+ rules = CssRuleList.new(styles)
35
+ expect(rules[1]).to be_kind_of(CssStyleRule)
36
+ end
37
+
38
+ it "parses import rules" do
39
+ rules = CssRuleList.new(styles)
40
+ expect(rules[2]).to be_kind_of(CssImportRule)
41
+ end
42
+
43
+ it "parses media rules" do
44
+ rules = CssRuleList.new(styles)
45
+ expect(rules[3]).to be_kind_of(CssMediaRule)
46
+ end
47
+
48
+ it "parses font face rules" do
49
+ rules = CssRuleList.new(styles)
50
+ expect(rules[4]).to be_kind_of(CssFontFaceRule)
51
+ end
52
+ end
53
+
54
+ describe "#[]" do
55
+ it "finds a css style sheet at the given index" do
56
+ rules = CssRuleList.new(styles)
57
+ expect(rules[0]).to be_kind_of(CssRule)
58
+ end
59
+ end
60
+
61
+ describe "#length" do
62
+ it "returns number of items" do
63
+ rules = CssRuleList.new(styles)
64
+ expect(rules.length).to eq 5
65
+ end
66
+ end
67
+
68
+ describe "#item" do
69
+ it "finds a css style sheet at the given index" do
70
+ rules = CssRuleList.new(styles)
71
+ expect(rules.item(0)).to be_kind_of(CssRule)
72
+ end
73
+ end
74
+ end