roadie 3.5.1 → 5.0.1
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 +43 -0
- data/.rubocop.yml +5 -0
- data/.solargraph.yml +16 -0
- data/Changelog.md +30 -4
- data/Gemfile +7 -2
- data/README.md +12 -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 +10 -11
- 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 +43 -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 +47 -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
@@ -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
|
data/lib/roadie/url_generator.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
2
4
|
|
3
5
|
module Roadie
|
4
6
|
# @api private
|
@@ -21,8 +23,15 @@ module Roadie
|
|
21
23
|
# @option url_options [String] :scheme URL scheme ("http" is default)
|
22
24
|
# @option url_options [String] :protocol alias for :scheme
|
23
25
|
def initialize(url_options)
|
24
|
-
|
25
|
-
|
26
|
+
unless url_options
|
27
|
+
raise ArgumentError, "No URL options were specified"
|
28
|
+
end
|
29
|
+
|
30
|
+
unless url_options[:host]
|
31
|
+
raise ArgumentError,
|
32
|
+
"No :host was specified; options were: #{url_options.inspect}"
|
33
|
+
end
|
34
|
+
|
26
35
|
validate_options url_options
|
27
36
|
|
28
37
|
@url_options = url_options
|
@@ -56,7 +65,7 @@ module Roadie
|
|
56
65
|
# @param [String] base The base which the relative path comes from
|
57
66
|
# @return [String] an absolute URL
|
58
67
|
def generate_url(path, base = "/")
|
59
|
-
return root_uri.to_s if path.nil?
|
68
|
+
return root_uri.to_s if path.nil? || path.empty?
|
60
69
|
return path if path_is_anchor?(path)
|
61
70
|
return add_scheme(path) if path_is_schemeless?(path)
|
62
71
|
return path if Utils.path_is_absolute?(path)
|
@@ -65,13 +74,20 @@ module Roadie
|
|
65
74
|
end
|
66
75
|
|
67
76
|
protected
|
77
|
+
|
68
78
|
attr_reader :root_uri, :scheme
|
69
79
|
|
70
80
|
private
|
81
|
+
|
71
82
|
def build_root_uri
|
72
83
|
path = make_absolute url_options[:path]
|
73
84
|
port = parse_port url_options[:port]
|
74
|
-
URI::Generic.build(
|
85
|
+
URI::Generic.build(
|
86
|
+
scheme: scheme,
|
87
|
+
host: url_options[:host],
|
88
|
+
port: port,
|
89
|
+
path: path
|
90
|
+
)
|
75
91
|
end
|
76
92
|
|
77
93
|
def add_scheme(path)
|
@@ -96,7 +112,7 @@ module Roadie
|
|
96
112
|
|
97
113
|
# Strip :// from any scheme, if present
|
98
114
|
def normalize_scheme(scheme)
|
99
|
-
return
|
115
|
+
return "http" unless scheme
|
100
116
|
scheme.to_s[/^\w+/]
|
101
117
|
end
|
102
118
|
|
@@ -117,7 +133,7 @@ module Roadie
|
|
117
133
|
end
|
118
134
|
|
119
135
|
def path_is_anchor?(path)
|
120
|
-
path.start_with?
|
136
|
+
path.start_with? "#"
|
121
137
|
end
|
122
138
|
|
123
139
|
VALID_OPTIONS = Set[:host, :port, :path, :protocol, :scheme].freeze
|
@@ -125,7 +141,9 @@ module Roadie
|
|
125
141
|
def validate_options(options)
|
126
142
|
keys = Set.new(options.keys)
|
127
143
|
unless keys.subset? VALID_OPTIONS
|
128
|
-
raise ArgumentError,
|
144
|
+
raise ArgumentError,
|
145
|
+
"Passed invalid options: #{(keys - VALID_OPTIONS).to_a}, " \
|
146
|
+
"valid options are: #{VALID_OPTIONS.to_a}"
|
129
147
|
end
|
130
148
|
end
|
131
149
|
end
|
data/lib/roadie/url_rewriter.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
# @api private
|
3
5
|
#
|
@@ -23,9 +25,9 @@ module Roadie
|
|
23
25
|
dom.css(
|
24
26
|
"a[href]:not([data-roadie-ignore]), " \
|
25
27
|
"img[src]:not([data-roadie-ignore]), " \
|
26
|
-
"*[style]:not([data-roadie-ignore])"
|
28
|
+
"*[style]:not([data-roadie-ignore])"
|
27
29
|
).each do |element|
|
28
|
-
transform_element_style element if element.has_attribute?(
|
30
|
+
transform_element_style element if element.has_attribute?("style")
|
29
31
|
transform_element element
|
30
32
|
end
|
31
33
|
nil
|
@@ -35,20 +37,22 @@ module Roadie
|
|
35
37
|
#
|
36
38
|
# This will make all URLs inside url() absolute.
|
37
39
|
#
|
38
|
-
#
|
39
|
-
# the passed string.
|
40
|
+
# Copy of CSS that is mutated is returned, passed string is not mutated.
|
40
41
|
#
|
41
42
|
# @param [String] css the css to mutate
|
42
|
-
# @return [
|
43
|
+
# @return [String] copy of css that is mutated
|
43
44
|
def transform_css(css)
|
44
|
-
css.gsub
|
45
|
+
css.gsub(CSS_URL_REGEXP) do
|
45
46
|
matches = Regexp.last_match
|
46
47
|
"url(#{matches[:quote]}#{generate_url(matches[:url])}#{matches[:quote]})"
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
51
|
private
|
51
|
-
|
52
|
+
|
53
|
+
def generate_url(*args)
|
54
|
+
@generator.generate_url(*args)
|
55
|
+
end
|
52
56
|
|
53
57
|
# Regexp matching all the url() declarations in CSS
|
54
58
|
#
|
@@ -81,8 +85,7 @@ module Roadie
|
|
81
85
|
# We need to use a setter for Nokogiri to detect the string mutation.
|
82
86
|
# If nokogiri used "dumber" data structures, this would all be redundant.
|
83
87
|
css = element["style"]
|
84
|
-
transform_css
|
85
|
-
element["style"] = css
|
88
|
+
element["style"] = transform_css(css)
|
86
89
|
end
|
87
90
|
end
|
88
91
|
end
|
data/lib/roadie/utils.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Roadie
|
2
4
|
module Utils
|
3
5
|
# @api private
|
@@ -8,7 +10,7 @@ module Roadie
|
|
8
10
|
#
|
9
11
|
# URLs that start with double slashes (//css/app.css) are also absolute
|
10
12
|
# in modern browsers, but most email clients do not understand them.
|
11
|
-
return true if
|
13
|
+
return true if %r{^(\w+:|//)}.match?(path)
|
12
14
|
|
13
15
|
begin
|
14
16
|
!URI.parse(path).relative?
|