roadie 3.5.1 → 5.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +46 -0
- data/.rubocop.yml +5 -0
- data/.solargraph.yml +16 -0
- data/Changelog.md +35 -4
- data/Gemfile +7 -2
- data/README.md +14 -14
- data/Rakefile +4 -3
- data/lib/roadie/asset_provider.rb +5 -1
- data/lib/roadie/asset_scanner.rb +8 -6
- data/lib/roadie/cached_provider.rb +3 -0
- data/lib/roadie/deduplicator.rb +3 -0
- data/lib/roadie/document.rb +24 -17
- data/lib/roadie/errors.rb +22 -16
- data/lib/roadie/filesystem_provider.rb +15 -3
- data/lib/roadie/inliner.rb +51 -19
- data/lib/roadie/markup_improver.rb +24 -31
- data/lib/roadie/net_http_provider.rb +27 -12
- data/lib/roadie/null_provider.rb +20 -5
- data/lib/roadie/null_url_rewriter.rb +11 -3
- data/lib/roadie/path_rewriter_provider.rb +6 -1
- data/lib/roadie/provider_list.rb +17 -11
- data/lib/roadie/rspec/asset_provider.rb +6 -1
- data/lib/roadie/rspec/cache_store.rb +2 -0
- data/lib/roadie/rspec.rb +4 -2
- data/lib/roadie/selector.rb +18 -5
- data/lib/roadie/style_attribute_builder.rb +4 -1
- data/lib/roadie/style_block.rb +5 -3
- data/lib/roadie/style_property.rb +5 -2
- data/lib/roadie/stylesheet.rb +4 -13
- data/lib/roadie/url_generator.rb +26 -8
- data/lib/roadie/url_rewriter.rb +12 -9
- data/lib/roadie/utils.rb +3 -1
- data/lib/roadie/version.rb +1 -1
- data/lib/roadie.rb +25 -23
- data/roadie.gemspec +23 -23
- data/spec/hash_as_cache_store_spec.rb +3 -1
- data/spec/integration_spec.rb +75 -44
- data/spec/lib/roadie/asset_scanner_spec.rb +11 -5
- data/spec/lib/roadie/cached_provider_spec.rb +6 -4
- data/spec/lib/roadie/css_not_found_spec.rb +10 -5
- data/spec/lib/roadie/deduplicator_spec.rb +5 -3
- data/spec/lib/roadie/document_spec.rb +57 -28
- data/spec/lib/roadie/filesystem_provider_spec.rb +10 -11
- data/spec/lib/roadie/inliner_spec.rb +42 -45
- data/spec/lib/roadie/markup_improver_spec.rb +19 -26
- data/spec/lib/roadie/net_http_provider_spec.rb +16 -14
- data/spec/lib/roadie/null_provider_spec.rb +4 -3
- data/spec/lib/roadie/null_url_rewriter_spec.rb +4 -3
- data/spec/lib/roadie/path_rewriter_provider_spec.rb +6 -4
- data/spec/lib/roadie/provider_list_spec.rb +27 -22
- data/spec/lib/roadie/selector_spec.rb +7 -5
- data/spec/lib/roadie/style_attribute_builder_spec.rb +7 -5
- data/spec/lib/roadie/style_block_spec.rb +3 -2
- data/spec/lib/roadie/style_property_spec.rb +10 -8
- data/spec/lib/roadie/stylesheet_spec.rb +4 -21
- data/spec/lib/roadie/test_provider_spec.rb +6 -4
- data/spec/lib/roadie/url_generator_spec.rb +3 -2
- data/spec/lib/roadie/url_rewriter_spec.rb +10 -7
- data/spec/lib/roadie/utils_spec.rb +2 -0
- data/spec/shared_examples/asset_provider.rb +2 -0
- data/spec/shared_examples/url_rewriter.rb +5 -3
- data/spec/spec_helper.rb +10 -8
- data/spec/support/have_attribute_matcher.rb +3 -2
- data/spec/support/have_node_matcher.rb +5 -3
- data/spec/support/have_selector_matcher.rb +4 -3
- data/spec/support/have_styling_matcher.rb +12 -11
- data/spec/support/have_xpath_matcher.rb +4 -3
- data/spec/support/test_provider.rb +2 -0
- metadata +24 -8
- data/.travis.yml +0 -22
data/lib/roadie/inliner.rb
CHANGED
@@ -1,19 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "nokogiri"
|
5
|
+
require "uri"
|
6
|
+
require "css_parser"
|
5
7
|
|
6
8
|
module Roadie
|
7
9
|
# @api private
|
8
10
|
# The Inliner inlines stylesheets to the elements of the DOM.
|
9
11
|
#
|
10
12
|
# Inlining means that {StyleBlock}s and a DOM tree are combined:
|
11
|
-
#
|
12
|
-
#
|
13
|
+
#
|
14
|
+
# ```css
|
15
|
+
# a { color: red; } # StyleBlock
|
16
|
+
# ```
|
17
|
+
# ```html
|
18
|
+
# <a href="/"></a> # DOM
|
19
|
+
# ```
|
13
20
|
#
|
14
21
|
# becomes
|
15
22
|
#
|
16
|
-
#
|
23
|
+
# ```html
|
24
|
+
# <a href="/" style="color:red"></a>
|
25
|
+
# ```
|
17
26
|
class Inliner
|
18
27
|
# @param [Array<Stylesheet>] stylesheets the stylesheets to use in the inlining
|
19
28
|
# @param [Nokogiri::HTML::Document] dom
|
@@ -45,9 +54,11 @@ module Roadie
|
|
45
54
|
end
|
46
55
|
|
47
56
|
protected
|
57
|
+
|
48
58
|
attr_reader :stylesheets, :dom
|
49
59
|
|
50
60
|
private
|
61
|
+
|
51
62
|
def consume_stylesheets
|
52
63
|
style_map = StyleMap.new
|
53
64
|
extra_blocks = []
|
@@ -89,12 +100,18 @@ module Roadie
|
|
89
100
|
# with having to rescue errors.
|
90
101
|
# Pseudo selectors that are known to be bad are skipped automatically but
|
91
102
|
# this will catch the rest.
|
92
|
-
rescue Nokogiri::XML::XPath::SyntaxError, Nokogiri::CSS::SyntaxError
|
93
|
-
Utils.warn
|
103
|
+
rescue Nokogiri::XML::XPath::SyntaxError, Nokogiri::CSS::SyntaxError
|
104
|
+
Utils.warn(
|
105
|
+
"Cannot inline #{selector.inspect} from \"#{stylesheet.name}\" " \
|
106
|
+
"stylesheet. If this is valid CSS, please report a bug."
|
107
|
+
)
|
94
108
|
nil
|
95
109
|
rescue => error
|
96
|
-
Utils.warn
|
97
|
-
|
110
|
+
Utils.warn(
|
111
|
+
"Got error when looking for #{selector.inspect} " \
|
112
|
+
"(from \"#{stylesheet.name}\" stylesheet): #{error}"
|
113
|
+
)
|
114
|
+
raise unless error.message.include?("XPath")
|
98
115
|
nil
|
99
116
|
end
|
100
117
|
|
@@ -120,12 +137,12 @@ module Roadie
|
|
120
137
|
end
|
121
138
|
|
122
139
|
def find_head
|
123
|
-
dom.at_xpath(
|
140
|
+
dom.at_xpath("html/head")
|
124
141
|
end
|
125
142
|
|
126
143
|
def create_style_element(style_blocks, parent, merge_media_queries)
|
127
144
|
return unless parent
|
128
|
-
element = Nokogiri::XML::Node.new(
|
145
|
+
element = Nokogiri::XML::Node.new("style", parent.document)
|
129
146
|
|
130
147
|
element.content =
|
131
148
|
if merge_media_queries
|
@@ -139,17 +156,23 @@ module Roadie
|
|
139
156
|
# For performance reasons, we should group styles with the same media types within
|
140
157
|
# one media query instead of creating thousands of media queries.
|
141
158
|
# https://github.com/artifex404/media-queries-benchmark
|
142
|
-
#
|
159
|
+
#
|
160
|
+
# Example result:
|
161
|
+
#
|
162
|
+
# ```ruby
|
163
|
+
# ["@media(max-width: 600px) { .col-12 { display: block; } }"]
|
164
|
+
# ```
|
165
|
+
#
|
143
166
|
# @param {Array<StyleBlock>} style_blocks Style blocks that could not be inlined
|
144
167
|
# @return {Array<String>}
|
145
168
|
def styles_in_shared_media_queries(style_blocks)
|
146
169
|
style_blocks.group_by(&:media).map do |media_types, blocks|
|
147
170
|
css_rules = blocks.map(&:to_s).join("\n")
|
148
171
|
|
149
|
-
if media_types == [
|
172
|
+
if media_types == ["all"]
|
150
173
|
css_rules
|
151
174
|
else
|
152
|
-
"@media #{media_types.join(
|
175
|
+
"@media #{media_types.join(", ")} {\n#{css_rules}\n}"
|
153
176
|
end
|
154
177
|
end
|
155
178
|
end
|
@@ -157,27 +180,36 @@ module Roadie
|
|
157
180
|
# Some users might prefer to not group rules within media queries because
|
158
181
|
# it will result in rules getting reordered.
|
159
182
|
# e.g.
|
183
|
+
#
|
184
|
+
# ```css
|
160
185
|
# @media(max-width: 600px) { .col-6 { display: block; } }
|
161
186
|
# @media(max-width: 400px) { .col-12 { display: inline-block; } }
|
162
187
|
# @media(max-width: 600px) { .col-12 { display: block; } }
|
188
|
+
# ````
|
189
|
+
#
|
163
190
|
# will become
|
191
|
+
#
|
192
|
+
# ```css
|
164
193
|
# @media(max-width: 600px) { .col-6 { display: block; } .col-12 { display: block; } }
|
165
194
|
# @media(max-width: 400px) { .col-12 { display: inline-block; } }
|
195
|
+
# ```
|
196
|
+
#
|
197
|
+
#
|
166
198
|
# which would change the styling on the page
|
167
199
|
# (before it would've yielded display: block; for .col-12 at max-width: 600px
|
168
200
|
# and now it yields inline-block;)
|
169
201
|
#
|
170
202
|
# If merge_media_queries is set to false,
|
171
|
-
# we will generate
|
203
|
+
# we will generate `style_blocks.size` media queries, potentially
|
172
204
|
# causing performance issues.
|
173
205
|
# @param {Array<StyleBlock>} style_blocks All style blocks
|
174
206
|
# @return {Array<String>}
|
175
207
|
def styles_in_individual_media_queries(style_blocks)
|
176
208
|
style_blocks.map do |css_rule|
|
177
|
-
if css_rule.media == [
|
209
|
+
if css_rule.media == ["all"]
|
178
210
|
css_rule
|
179
211
|
else
|
180
|
-
"@media #{css_rule.media.join(
|
212
|
+
"@media #{css_rule.media.join(", ")} {\n#{css_rule}\n}"
|
181
213
|
end
|
182
214
|
end
|
183
215
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# @api private
|
3
5
|
# Class that improves the markup of a HTML DOM tree
|
@@ -5,13 +7,10 @@ module Roadie
|
|
5
7
|
# This class will improve the following aspects of the DOM:
|
6
8
|
# * A HTML5 doctype will be added if missing, other doctypes will be left as-is.
|
7
9
|
# * Basic HTML elements will be added if missing.
|
8
|
-
# *
|
9
|
-
# *
|
10
|
-
# *
|
11
|
-
# *
|
12
|
-
#
|
13
|
-
# @note Due to a Nokogiri bug, the HTML5 doctype cannot be added under JRuby. No doctype is outputted under JRuby.
|
14
|
-
# See https://github.com/sparklemotion/nokogiri/issues/984
|
10
|
+
# * `<html>`
|
11
|
+
# * `<head>`
|
12
|
+
# * `<body>`
|
13
|
+
# * `<meta>` declaring charset and content-type (text/html)
|
15
14
|
class MarkupImprover
|
16
15
|
# The original HTML must also be passed in in order to handle the doctypes
|
17
16
|
# since a +Nokogiri::HTML::Document+ will always have a doctype, no matter if
|
@@ -31,45 +30,39 @@ module Roadie
|
|
31
30
|
end
|
32
31
|
|
33
32
|
protected
|
33
|
+
|
34
34
|
attr_reader :dom
|
35
35
|
|
36
36
|
private
|
37
|
+
|
37
38
|
def ensure_doctype_present
|
38
|
-
return if
|
39
|
-
return if @html.include?('<!DOCTYPE ')
|
39
|
+
return if @html.include?("<!DOCTYPE ")
|
40
40
|
# Nokogiri adds a "default" doctype to the DOM, which we will remove
|
41
|
-
dom.internal_subset
|
42
|
-
dom.create_internal_subset
|
43
|
-
end
|
44
|
-
|
45
|
-
# JRuby up to at least 1.6.0 has a bug where the doctype of a document cannot be changed.
|
46
|
-
# See https://github.com/sparklemotion/nokogiri/issues/984
|
47
|
-
def uses_buggy_jruby?
|
48
|
-
# No reason to check for version yet since no existing version has a fix.
|
49
|
-
defined?(JRuby)
|
41
|
+
dom.internal_subset&.remove
|
42
|
+
dom.create_internal_subset "html", nil, nil
|
50
43
|
end
|
51
44
|
|
52
45
|
def ensure_html_element_present
|
53
|
-
return if dom.at_xpath(
|
54
|
-
html = Nokogiri::XML::Node.new
|
46
|
+
return if dom.at_xpath("html")
|
47
|
+
html = Nokogiri::XML::Node.new "html", dom
|
55
48
|
dom << html
|
56
49
|
end
|
57
50
|
|
58
51
|
def ensure_head_element_present
|
59
|
-
if (head = dom.at_xpath(
|
52
|
+
if (head = dom.at_xpath("html/head"))
|
60
53
|
head
|
61
54
|
else
|
62
|
-
create_head_element dom.at_xpath(
|
55
|
+
create_head_element dom.at_xpath("html")
|
63
56
|
end
|
64
57
|
end
|
65
58
|
|
66
59
|
def create_head_element(parent)
|
67
|
-
head = Nokogiri::XML::Node.new
|
68
|
-
|
60
|
+
head = Nokogiri::XML::Node.new "head", dom
|
61
|
+
if parent.children.empty?
|
62
|
+
parent << head
|
63
|
+
else
|
69
64
|
# Crashes when no children are present
|
70
65
|
parent.children.before head
|
71
|
-
else
|
72
|
-
parent << head
|
73
66
|
end
|
74
67
|
head
|
75
68
|
end
|
@@ -81,15 +74,15 @@ module Roadie
|
|
81
74
|
end
|
82
75
|
|
83
76
|
def content_type_meta_element_missing?
|
84
|
-
dom.xpath(
|
85
|
-
meta[
|
77
|
+
dom.xpath("html/head/meta").none? do |meta|
|
78
|
+
meta["http-equiv"].to_s.downcase == "content-type"
|
86
79
|
end
|
87
80
|
end
|
88
81
|
|
89
82
|
def make_content_type_element
|
90
|
-
meta = Nokogiri::XML::Node.new(
|
91
|
-
meta[
|
92
|
-
meta[
|
83
|
+
meta = Nokogiri::XML::Node.new("meta", dom)
|
84
|
+
meta["http-equiv"] = "Content-Type"
|
85
|
+
meta["content"] = "text/html; charset=UTF-8"
|
93
86
|
meta
|
94
87
|
end
|
95
88
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "uri"
|
5
|
+
require "net/http"
|
5
6
|
|
6
7
|
module Roadie
|
7
8
|
# @api public
|
@@ -33,19 +34,29 @@ module Roadie
|
|
33
34
|
|
34
35
|
def find_stylesheet!(url)
|
35
36
|
response = download(url)
|
36
|
-
if response.
|
37
|
+
if response.is_a? Net::HTTPSuccess
|
37
38
|
Stylesheet.new(url, response_body(response))
|
38
39
|
else
|
39
|
-
raise CssNotFound.new(
|
40
|
+
raise CssNotFound.new(
|
41
|
+
css_name: url,
|
42
|
+
message: "Server returned #{response.code}: #{truncate response.body}",
|
43
|
+
provider: self
|
44
|
+
)
|
40
45
|
end
|
41
46
|
rescue Timeout::Error
|
42
|
-
raise CssNotFound.new(url, "Timeout", self)
|
47
|
+
raise CssNotFound.new(css_name: url, message: "Timeout", provider: self)
|
43
48
|
end
|
44
49
|
|
45
|
-
def to_s
|
46
|
-
|
50
|
+
def to_s
|
51
|
+
inspect
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
"#<#{self.class} whitelist: #{whitelist.inspect}>"
|
56
|
+
end
|
47
57
|
|
48
58
|
private
|
59
|
+
|
49
60
|
def host_set(hosts)
|
50
61
|
hosts.each { |host| validate_host(host) }.to_set
|
51
62
|
end
|
@@ -62,7 +73,11 @@ module Roadie
|
|
62
73
|
if access_granted_to?(uri.host)
|
63
74
|
get_response(uri)
|
64
75
|
else
|
65
|
-
raise CssNotFound.new(
|
76
|
+
raise CssNotFound.new(
|
77
|
+
css_name: url,
|
78
|
+
message: "#{uri.host} is not part of whitelist!",
|
79
|
+
provider: self
|
80
|
+
)
|
66
81
|
end
|
67
82
|
end
|
68
83
|
|
@@ -70,7 +85,7 @@ module Roadie
|
|
70
85
|
if RUBY_VERSION >= "2.0.0"
|
71
86
|
Net::HTTP.get_response(uri)
|
72
87
|
else
|
73
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme ==
|
88
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme == "https")) do |http|
|
74
89
|
http.request(Net::HTTP::Get.new(uri.request_uri))
|
75
90
|
end
|
76
91
|
end
|
@@ -91,7 +106,7 @@ module Roadie
|
|
91
106
|
def response_body(response)
|
92
107
|
# Make sure we respect encoding because Net:HTTP will encode body as ASCII by default
|
93
108
|
# which will break if the response is not compatible.
|
94
|
-
supplied_charset = response.type_params[
|
109
|
+
supplied_charset = response.type_params["charset"]
|
95
110
|
body = response.body
|
96
111
|
|
97
112
|
if supplied_charset
|
data/lib/roadie/null_provider.rb
CHANGED
@@ -1,16 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# An asset provider that returns empty stylesheets for any name.
|
3
5
|
#
|
4
6
|
# Use it to ignore missing assets or in your tests when you need a provider
|
5
7
|
# but you do not care what it contains or that it is even referenced at all.
|
6
8
|
class NullProvider
|
7
|
-
def find_stylesheet(name)
|
8
|
-
|
9
|
+
def find_stylesheet(name)
|
10
|
+
empty_stylesheet
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_stylesheet!(name)
|
14
|
+
empty_stylesheet
|
15
|
+
end
|
9
16
|
|
10
|
-
def to_s
|
11
|
-
|
17
|
+
def to_s
|
18
|
+
inspect
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"#<#{self.class}>"
|
23
|
+
end
|
12
24
|
|
13
25
|
private
|
14
|
-
|
26
|
+
|
27
|
+
def empty_stylesheet
|
28
|
+
Stylesheet.new "(null)", ""
|
29
|
+
end
|
15
30
|
end
|
16
31
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# @api private
|
3
5
|
# Null Object for the URL rewriter role.
|
@@ -5,8 +7,14 @@ module Roadie
|
|
5
7
|
# Used whenever client does not pass any URL options and no URL rewriting
|
6
8
|
# should take place.
|
7
9
|
class NullUrlRewriter
|
8
|
-
def initialize(generator = nil)
|
9
|
-
|
10
|
-
|
10
|
+
def initialize(generator = nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
def transform_dom(dom)
|
14
|
+
end
|
15
|
+
|
16
|
+
def transform_css(css)
|
17
|
+
css
|
18
|
+
end
|
11
19
|
end
|
12
20
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# @api public
|
3
5
|
# This provider acts a bit like a pipeline in normal UNIX parlour by enabling
|
@@ -57,7 +59,10 @@ module Roadie
|
|
57
59
|
if new_path
|
58
60
|
provider.find_stylesheet!(new_path)
|
59
61
|
else
|
60
|
-
raise CssNotFound
|
62
|
+
raise CssNotFound.new(
|
63
|
+
css_name: path,
|
64
|
+
message: "Filter returned #{new_path.inspect}"
|
65
|
+
)
|
61
66
|
end
|
62
67
|
end
|
63
68
|
end
|
data/lib/roadie/provider_list.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
2
4
|
|
3
5
|
module Roadie
|
4
6
|
# An asset provider that just composes a list of other asset providers.
|
@@ -23,7 +25,7 @@ module Roadie
|
|
23
25
|
# @overload wrap(provider1, provider2, ...)
|
24
26
|
# @return a new {ProviderList} with all the passed providers in it.
|
25
27
|
def self.wrap(*providers)
|
26
|
-
if providers.size == 1 && providers.first.
|
28
|
+
if providers.size == 1 && providers.first.instance_of?(self)
|
27
29
|
providers.first
|
28
30
|
else
|
29
31
|
new(providers.flatten)
|
@@ -31,7 +33,9 @@ module Roadie
|
|
31
33
|
end
|
32
34
|
|
33
35
|
# Returns a new empty list.
|
34
|
-
def self.empty
|
36
|
+
def self.empty
|
37
|
+
new([])
|
38
|
+
end
|
35
39
|
|
36
40
|
def initialize(providers)
|
37
41
|
@providers = providers
|
@@ -53,13 +57,13 @@ module Roadie
|
|
53
57
|
def find_stylesheet!(name)
|
54
58
|
errors = []
|
55
59
|
@providers.each do |provider|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
errors << error
|
60
|
-
end
|
60
|
+
return provider.find_stylesheet!(name)
|
61
|
+
rescue CssNotFound => error
|
62
|
+
errors << error
|
61
63
|
end
|
62
|
-
raise ProvidersFailed.new(
|
64
|
+
raise ProvidersFailed.new(
|
65
|
+
css_name: name, providers: self, errors: errors
|
66
|
+
)
|
63
67
|
end
|
64
68
|
|
65
69
|
def to_s
|
@@ -67,12 +71,14 @@ module Roadie
|
|
67
71
|
# Indent every line one level
|
68
72
|
provider.to_s.split("\n").join("\n\t")
|
69
73
|
}
|
70
|
-
"ProviderList: [\n\t#{list.join(",\n\t")}\n]"
|
74
|
+
"ProviderList: [\n\t#{list.join(",\n\t")}\n]\n"
|
71
75
|
end
|
72
76
|
|
73
77
|
# ProviderList can be coerced to an array. This makes Array#flatten work
|
74
78
|
# with it, among other things.
|
75
|
-
def to_ary
|
79
|
+
def to_ary
|
80
|
+
to_a
|
81
|
+
end
|
76
82
|
|
77
83
|
# @!method each
|
78
84
|
# @see Array#each
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
shared_examples_for "roadie asset provider" do |options|
|
2
4
|
valid_name = options[:valid_name] or raise "You must provide a :valid_name option to the shared examples"
|
3
5
|
invalid_name = options[:invalid_name] or raise "You must provide an :invalid_name option to the shared examples"
|
@@ -43,7 +45,10 @@ shared_examples_for "roadie asset provider" do |options|
|
|
43
45
|
it "raises Roadie::CssNotFound on invalid stylesheets" do
|
44
46
|
expect {
|
45
47
|
subject.find_stylesheet!(invalid_name)
|
46
|
-
}.to raise_error
|
48
|
+
}.to raise_error(
|
49
|
+
Roadie::CssNotFound,
|
50
|
+
Regexp.new(Regexp.quote(invalid_name))
|
51
|
+
)
|
47
52
|
end
|
48
53
|
end
|
49
54
|
end
|
data/lib/roadie/rspec.rb
CHANGED
data/lib/roadie/selector.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# @api private
|
3
5
|
#
|
@@ -35,9 +37,17 @@ module Roadie
|
|
35
37
|
!(pseudo_element? || at_rule? || pseudo_function?)
|
36
38
|
end
|
37
39
|
|
38
|
-
def to_s
|
39
|
-
|
40
|
-
|
40
|
+
def to_s
|
41
|
+
selector
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_str
|
45
|
+
to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
selector.inspect
|
50
|
+
end
|
41
51
|
|
42
52
|
# {Selector}s are equal to other {Selector}s if, and only if, their string
|
43
53
|
# representations are equal.
|
@@ -50,22 +60,25 @@ module Roadie
|
|
50
60
|
end
|
51
61
|
|
52
62
|
protected
|
63
|
+
|
53
64
|
attr_reader :selector
|
54
65
|
|
55
66
|
private
|
67
|
+
|
56
68
|
BAD_PSEUDO_FUNCTIONS = %w[
|
57
69
|
:active :focus :hover :link :target :visited
|
58
70
|
:-ms-input-placeholder :-moz-placeholder
|
59
71
|
:before :after
|
60
72
|
:enabled :disabled :checked
|
73
|
+
:host
|
61
74
|
].freeze
|
62
75
|
|
63
76
|
def pseudo_element?
|
64
|
-
selector.include?
|
77
|
+
selector.include? "::"
|
65
78
|
end
|
66
79
|
|
67
80
|
def at_rule?
|
68
|
-
selector[0, 1] ==
|
81
|
+
selector[0, 1] == "@"
|
69
82
|
end
|
70
83
|
|
71
84
|
def pseudo_function?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
class StyleAttributeBuilder
|
3
5
|
def initialize
|
@@ -9,10 +11,11 @@ module Roadie
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def attribute_string
|
12
|
-
Deduplicator.apply(stable_sort(@styles).map(&:to_s)).join(
|
14
|
+
Deduplicator.apply(stable_sort(@styles).map(&:to_s)).join(";")
|
13
15
|
end
|
14
16
|
|
15
17
|
private
|
18
|
+
|
16
19
|
def stable_sort(list)
|
17
20
|
# Ruby's sort is unstable for performance reasons. We need it to be
|
18
21
|
# stable, e.g. to preserve order of elements that are compared equal in
|
data/lib/roadie/style_block.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
2
4
|
|
3
5
|
module Roadie
|
4
6
|
# @api private
|
@@ -37,7 +39,7 @@ module Roadie
|
|
37
39
|
# @return {String}
|
38
40
|
def to_s
|
39
41
|
# NB - leave off redundant final semicolon - see https://www.w3.org/TR/CSS2/syndata.html#declaration
|
40
|
-
"#{selector}{#{properties.map(&:to_s).join(
|
42
|
+
"#{selector}{#{properties.map(&:to_s).join(";")}}"
|
41
43
|
end
|
42
44
|
|
43
45
|
private
|
@@ -47,7 +49,7 @@ module Roadie
|
|
47
49
|
# @media only screen and (max-width: 600px) {...} cannot be inlined
|
48
50
|
# @return {Boolean}
|
49
51
|
def inlinable_media?
|
50
|
-
@media.none? { |media_query| media_query.include?
|
52
|
+
@media.none? { |media_query| media_query.include? "(" }
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# @api private
|
3
5
|
# Domain object for a CSS property such as "color: red !important".
|
@@ -36,14 +38,15 @@ module Roadie
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def to_s
|
39
|
-
[property, value_with_important].join(
|
41
|
+
[property, value_with_important].join(":")
|
40
42
|
end
|
41
43
|
|
42
44
|
def inspect
|
43
|
-
"#{
|
45
|
+
"#{self} (#{specificity})"
|
44
46
|
end
|
45
47
|
|
46
48
|
private
|
49
|
+
|
47
50
|
def value_with_important
|
48
51
|
if important
|
49
52
|
"#{value} !important"
|
data/lib/roadie/stylesheet.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# Domain object that represents a stylesheet (from disc, perhaps).
|
3
5
|
#
|
@@ -6,7 +8,7 @@ module Roadie
|
|
6
8
|
# @attr_reader [String] name the name of the stylesheet ("stylesheets/main.css", "Admin user styles", etc.). The name of the stylesheet will be visible if any errors occur.
|
7
9
|
# @attr_reader [Array<StyleBlock>] blocks
|
8
10
|
class Stylesheet
|
9
|
-
BOM = "\xEF\xBB\xBF".force_encoding(
|
11
|
+
BOM = (+"\xEF\xBB\xBF").force_encoding("UTF-8").freeze
|
10
12
|
|
11
13
|
attr_reader :name, :blocks
|
12
14
|
|
@@ -19,23 +21,12 @@ module Roadie
|
|
19
21
|
@blocks = parse_blocks(css.sub(BOM, ""))
|
20
22
|
end
|
21
23
|
|
22
|
-
# @yield [selector, properties]
|
23
|
-
# @yieldparam [Selector] selector
|
24
|
-
# @yieldparam [Array<StyleProperty>] properties
|
25
|
-
# @deprecated Iterate over the #{blocks} instead. Will be removed on version 4.0.
|
26
|
-
def each_inlinable_block(&block)
|
27
|
-
# #map and then #each in order to support chained enumerations, etc. if
|
28
|
-
# no block is provided
|
29
|
-
inlinable_blocks.map { |style_block|
|
30
|
-
[style_block.selector, style_block.properties]
|
31
|
-
}.each(&block)
|
32
|
-
end
|
33
|
-
|
34
24
|
def to_s
|
35
25
|
blocks.join("\n")
|
36
26
|
end
|
37
27
|
|
38
28
|
private
|
29
|
+
|
39
30
|
def inlinable_blocks
|
40
31
|
blocks.select(&:inlinable?)
|
41
32
|
end
|