sitemap_generator 7.0.3 → 7.1.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/CHANGES.md +84 -64
- data/README.md +142 -143
- data/VERSION +1 -1
- data/lib/capistrano/sitemap_generator.rb +2 -0
- data/lib/sitemap_generator/adapters/active_storage_adapter.rb +15 -10
- data/lib/sitemap_generator/adapters/aws_sdk_adapter.rb +13 -12
- data/lib/sitemap_generator/adapters/file_adapter.rb +10 -13
- data/lib/sitemap_generator/adapters/fog_adapter.rb +2 -2
- data/lib/sitemap_generator/adapters/google_storage_adapter.rb +5 -4
- data/lib/sitemap_generator/adapters/s3_adapter.rb +15 -14
- data/lib/sitemap_generator/adapters/wave_adapter.rb +5 -5
- data/lib/sitemap_generator/application.rb +5 -3
- data/lib/sitemap_generator/builder/sitemap_file.rb +21 -20
- data/lib/sitemap_generator/builder/sitemap_index_file.rb +20 -18
- data/lib/sitemap_generator/builder/sitemap_index_url.rb +5 -6
- data/lib/sitemap_generator/builder/sitemap_url.rb +82 -36
- data/lib/sitemap_generator/builder.rb +4 -2
- data/lib/sitemap_generator/core_ext/big_decimal.rb +38 -33
- data/lib/sitemap_generator/core_ext/numeric.rb +50 -46
- data/lib/sitemap_generator/helpers/number_helper.rb +52 -46
- data/lib/sitemap_generator/interpreter.rb +17 -15
- data/lib/sitemap_generator/link_set.rb +64 -66
- data/lib/sitemap_generator/railtie.rb +1 -0
- data/lib/sitemap_generator/simple_namer.rb +3 -4
- data/lib/sitemap_generator/sitemap_location.rb +44 -36
- data/lib/sitemap_generator/tasks.rb +3 -1
- data/lib/sitemap_generator/templates.rb +6 -7
- data/lib/sitemap_generator/utilities.rb +36 -17
- data/lib/sitemap_generator.rb +22 -14
- data/lib/tasks/sitemap_generator_tasks.rake +2 -0
- data/rails/install.rb +2 -0
- data/rails/uninstall.rb +2 -0
- data/templates/sitemap.rb +2 -0
- metadata +2 -2
|
@@ -10,23 +10,26 @@ module SitemapGenerator
|
|
|
10
10
|
# compressed prior to being written out. Otherwise the data will be written out
|
|
11
11
|
# unchanged.
|
|
12
12
|
# @param raw_data - data to write to the file.
|
|
13
|
-
def write(location, raw_data)
|
|
13
|
+
def write(location, raw_data) # rubocop:disable Metrics/MethodLength
|
|
14
14
|
# Ensure that the directory exists
|
|
15
15
|
dir = location.directory
|
|
16
16
|
if !File.exist?(dir)
|
|
17
17
|
FileUtils.mkdir_p(dir)
|
|
18
18
|
elsif !File.directory?(dir)
|
|
19
|
-
raise SitemapError
|
|
19
|
+
raise SitemapError, "#{dir} should be a directory!"
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
File.open(location.path, 'wb') do |stream|
|
|
23
|
+
if location.gzip?
|
|
24
|
+
gzip(stream, raw_data)
|
|
25
|
+
else
|
|
26
|
+
stream.write raw_data
|
|
27
|
+
end
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
private
|
|
32
|
+
|
|
30
33
|
# Write `data` to a stream, passing the data through a GzipWriter
|
|
31
34
|
# to compress it.
|
|
32
35
|
def gzip(stream, data)
|
|
@@ -34,11 +37,5 @@ module SitemapGenerator
|
|
|
34
37
|
gz.write data
|
|
35
38
|
gz.close
|
|
36
39
|
end
|
|
37
|
-
|
|
38
|
-
# Write `data` to a stream as is.
|
|
39
|
-
def plain(stream, data)
|
|
40
|
-
stream.write data
|
|
41
|
-
stream.close
|
|
42
|
-
end
|
|
43
40
|
end
|
|
44
41
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
unless defined?(Fog::Storage)
|
|
4
4
|
raise LoadError, "Error: `Fog::Storage` is not defined.\n\n" \
|
|
5
|
-
|
|
5
|
+
"Please `require 'fog'` - or another library that defines this class."
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
module SitemapGenerator
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
unless defined?(Google::Cloud::Storage)
|
|
4
4
|
raise LoadError, "Error: `Google::Cloud::Storage` is not defined.\n\n" \
|
|
5
|
-
|
|
5
|
+
"Please `require 'google/cloud/storage'` - or another library that defines this class."
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
module SitemapGenerator
|
|
@@ -12,7 +12,8 @@ module SitemapGenerator
|
|
|
12
12
|
#
|
|
13
13
|
# @param [Hash] opts Google::Cloud::Storage configuration options.
|
|
14
14
|
# @option :bucket [String] Required. Name of Google Storage Bucket where the file is to be uploaded.
|
|
15
|
-
# @option :acl [String] Optional. Access control which is applied to the uploaded file(s).
|
|
15
|
+
# @option :acl [String] Optional. Access control which is applied to the uploaded file(s).
|
|
16
|
+
# Default value is 'public'.
|
|
16
17
|
#
|
|
17
18
|
# All options other than the `:bucket` and `:acl` options are passed to the `Google::Cloud::Storage.new`
|
|
18
19
|
# initializer. See https://googleapis.dev/ruby/google-cloud-storage/latest/file.AUTHENTICATION.html
|
|
@@ -25,7 +26,7 @@ module SitemapGenerator
|
|
|
25
26
|
def initialize(opts = {})
|
|
26
27
|
opts = opts.clone
|
|
27
28
|
@bucket = opts.delete(:bucket)
|
|
28
|
-
@acl = opts.
|
|
29
|
+
@acl = opts.key?(:acl) ? opts.delete(:acl) : 'public'
|
|
29
30
|
@storage_options = opts
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
unless defined?(Fog::Storage)
|
|
4
4
|
raise LoadError, "Error: `Fog::Storage` is not defined.\n\n" \
|
|
5
|
-
|
|
5
|
+
"Please `require 'fog-aws'` - or another library that defines this class."
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
module SitemapGenerator
|
|
@@ -22,21 +22,21 @@ module SitemapGenerator
|
|
|
22
22
|
#
|
|
23
23
|
# Alternatively you can use an environment variable to configure each option (except `fog_storage_options`).
|
|
24
24
|
# The environment variables have the same name but capitalized, e.g. `FOG_PATH_STYLE`.
|
|
25
|
-
def initialize(opts = {})
|
|
26
|
-
@aws_access_key_id = opts[:aws_access_key_id] || ENV
|
|
27
|
-
@aws_secret_access_key = opts[:aws_secret_access_key] || ENV
|
|
28
|
-
@aws_session_token = opts[:aws_session_token] || ENV
|
|
29
|
-
@fog_provider = opts[:fog_provider] || ENV
|
|
30
|
-
@fog_directory = opts[:fog_directory] || ENV
|
|
31
|
-
@fog_region = opts[:fog_region] || ENV
|
|
32
|
-
@fog_path_style = opts[:fog_path_style] || ENV
|
|
25
|
+
def initialize(opts = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
26
|
+
@aws_access_key_id = opts[:aws_access_key_id] || ENV.fetch('AWS_ACCESS_KEY_ID', nil)
|
|
27
|
+
@aws_secret_access_key = opts[:aws_secret_access_key] || ENV.fetch('AWS_SECRET_ACCESS_KEY', nil)
|
|
28
|
+
@aws_session_token = opts[:aws_session_token] || ENV.fetch('AWS_SESSION_TOKEN', nil)
|
|
29
|
+
@fog_provider = opts[:fog_provider] || ENV.fetch('FOG_PROVIDER', nil)
|
|
30
|
+
@fog_directory = opts[:fog_directory] || ENV.fetch('FOG_DIRECTORY', nil)
|
|
31
|
+
@fog_region = opts[:fog_region] || ENV.fetch('FOG_REGION', nil)
|
|
32
|
+
@fog_path_style = opts[:fog_path_style] || ENV.fetch('FOG_PATH_STYLE', nil)
|
|
33
33
|
@fog_storage_options = opts[:fog_storage_options] || {}
|
|
34
|
-
fog_public = opts[:fog_public].nil? ? ENV
|
|
35
|
-
@fog_public = SitemapGenerator::Utilities.falsy?(fog_public)
|
|
34
|
+
fog_public = opts[:fog_public].nil? ? ENV.fetch('FOG_PUBLIC', nil) : opts[:fog_public]
|
|
35
|
+
@fog_public = !SitemapGenerator::Utilities.falsy?(fog_public)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Call with a SitemapLocation and string data
|
|
39
|
-
def write(location, raw_data)
|
|
39
|
+
def write(location, raw_data) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
40
40
|
SitemapGenerator::FileAdapter.new.write(location, raw_data)
|
|
41
41
|
|
|
42
42
|
credentials = { provider: @fog_provider }
|
|
@@ -57,7 +57,8 @@ module SitemapGenerator
|
|
|
57
57
|
directory.files.create(
|
|
58
58
|
key: location.path_in_public,
|
|
59
59
|
body: File.open(location.path),
|
|
60
|
-
public: @fog_public
|
|
60
|
+
public: @fog_public,
|
|
61
|
+
content_type: location.content_type
|
|
61
62
|
)
|
|
62
63
|
end
|
|
63
64
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
unless defined?(CarrierWave::Uploader::Base)
|
|
4
4
|
raise LoadError, "Error: `CarrierWave::Uploader::Base` is not defined.\n\n" \
|
|
5
|
-
|
|
5
|
+
"Please `require 'carrierwave'` - or another library that defines this class."
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
module SitemapGenerator
|
|
@@ -14,10 +14,10 @@ module SitemapGenerator
|
|
|
14
14
|
def write(location, raw_data)
|
|
15
15
|
SitemapGenerator::FileAdapter.new.write(location, raw_data)
|
|
16
16
|
directory = File.dirname(location.path_in_public)
|
|
17
|
-
if directory != '.'
|
|
18
|
-
|
|
17
|
+
self.store_dir = directory if directory != '.'
|
|
18
|
+
File.open(location.path, 'rb') do |io|
|
|
19
|
+
store!(io)
|
|
19
20
|
end
|
|
20
|
-
store!(open(location.path, 'rb'))
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
require 'pathname'
|
|
4
4
|
|
|
5
5
|
module SitemapGenerator
|
|
6
|
+
# Thin abstraction over the host application environment.
|
|
7
|
+
# Provides the app +root+ path and Rails version detection.
|
|
6
8
|
class Application
|
|
7
|
-
def is_rails?
|
|
9
|
+
def is_rails? # rubocop:disable Naming/PredicatePrefix
|
|
8
10
|
!!defined?(Rails::VERSION)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
# Returns a boolean indicating whether this environment is Rails 3
|
|
12
14
|
#
|
|
13
15
|
# @return [Boolean]
|
|
14
|
-
def is_at_least_rails3?
|
|
16
|
+
def is_at_least_rails3? # rubocop:disable Naming/PredicatePrefix
|
|
15
17
|
is_rails? && Rails.version.to_f >= 3
|
|
16
|
-
rescue
|
|
18
|
+
rescue StandardError
|
|
17
19
|
false # Rails.version defined in 2.1.0
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -11,10 +11,12 @@ module SitemapGenerator
|
|
|
11
11
|
#
|
|
12
12
|
# sitemap = SitemapFile.new(:location => SitemapLocation.new(...))
|
|
13
13
|
# sitemap.add('/', { ... }) <- add a link to the sitemap
|
|
14
|
-
# sitemap.finalize! <- write the sitemap file and freeze the object
|
|
14
|
+
# sitemap.finalize! <- write the sitemap file and freeze the object
|
|
15
|
+
# to protect it from further modification
|
|
15
16
|
#
|
|
16
17
|
class SitemapFile
|
|
17
18
|
include SitemapGenerator::Helpers::NumberHelper
|
|
19
|
+
|
|
18
20
|
attr_reader :link_count, :filesize, :location, :news_count
|
|
19
21
|
|
|
20
22
|
# === Options
|
|
@@ -22,7 +24,7 @@ module SitemapGenerator
|
|
|
22
24
|
# * <tt>location</tt> - a SitemapGenerator::SitemapLocation instance or a Hash of options
|
|
23
25
|
# from which a SitemapLocation will be created for you. See `SitemapGenerator::SitemapLocation` for
|
|
24
26
|
# the supported list of options.
|
|
25
|
-
def initialize(opts = {})
|
|
27
|
+
def initialize(opts = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
26
28
|
@location = opts.is_a?(Hash) ? SitemapGenerator::SitemapLocation.new(opts) : opts
|
|
27
29
|
@link_count = 0
|
|
28
30
|
@news_count = 0
|
|
@@ -46,33 +48,33 @@ module SitemapGenerator
|
|
|
46
48
|
HTML
|
|
47
49
|
@xml_wrapper_start.gsub!(/\s+/, ' ').gsub!(/ *> */, '>').strip!
|
|
48
50
|
@xml_wrapper_end = '</urlset>'
|
|
49
|
-
@filesize = SitemapGenerator::Utilities.bytesize(@xml_wrapper_start) +
|
|
51
|
+
@filesize = SitemapGenerator::Utilities.bytesize(@xml_wrapper_start) +
|
|
52
|
+
SitemapGenerator::Utilities.bytesize(@xml_wrapper_end)
|
|
50
53
|
@xml_content = @xml_wrapper_start
|
|
51
54
|
@written = false
|
|
52
55
|
@reserved_name = nil # holds the name reserved from the namer
|
|
53
56
|
@frozen = false # rather than actually freeze, use this boolean
|
|
54
57
|
end
|
|
55
58
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
# mess up the name-assignment sequence.
|
|
59
|
+
# Return the time the sitemap was written, or nil if it has not been written yet.
|
|
60
|
+
# Returning nil before write prevents prematurely reserving a name in the
|
|
61
|
+
# namer sequence.
|
|
60
62
|
def lastmod
|
|
61
|
-
|
|
62
|
-
rescue
|
|
63
|
-
nil
|
|
63
|
+
@lastmod if location.reserved_name?
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def empty?
|
|
67
|
-
@link_count
|
|
67
|
+
@link_count.zero?
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# Return a boolean indicating whether the sitemap file can fit another link
|
|
71
71
|
# of <tt>bytes</tt> bytes in size. You can also pass a string and the
|
|
72
72
|
# bytesize will be calculated for you.
|
|
73
73
|
def file_can_fit?(bytes)
|
|
74
|
-
bytes =
|
|
75
|
-
(@filesize + bytes) < SitemapGenerator::MAX_SITEMAP_FILESIZE &&
|
|
74
|
+
bytes = SitemapGenerator::Utilities.bytesize(bytes) if bytes.is_a?(String)
|
|
75
|
+
(@filesize + bytes) < SitemapGenerator::MAX_SITEMAP_FILESIZE &&
|
|
76
|
+
@link_count < max_sitemap_links &&
|
|
77
|
+
@news_count < SitemapGenerator::MAX_SITEMAP_NEWS
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
# Add a link to the sitemap file.
|
|
@@ -94,7 +96,7 @@ module SitemapGenerator
|
|
|
94
96
|
#
|
|
95
97
|
# The link added to the sitemap will use the host from its location object
|
|
96
98
|
# if no host has been specified.
|
|
97
|
-
def add(link, options = {})
|
|
99
|
+
def add(link, options = {}) # rubocop:disable Metrics/MethodLength
|
|
98
100
|
raise SitemapGenerator::SitemapFinalizedError if finalized?
|
|
99
101
|
|
|
100
102
|
sitemap_url =
|
|
@@ -106,11 +108,9 @@ module SitemapGenerator
|
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
xml = sitemap_url.to_xml
|
|
109
|
-
raise SitemapGenerator::SitemapFullError
|
|
111
|
+
raise SitemapGenerator::SitemapFullError unless file_can_fit?(xml)
|
|
110
112
|
|
|
111
|
-
if sitemap_url.news?
|
|
112
|
-
@news_count += 1
|
|
113
|
-
end
|
|
113
|
+
@news_count += 1 if sitemap_url.news?
|
|
114
114
|
|
|
115
115
|
# Add the XML to the sitemap
|
|
116
116
|
@xml_content << xml
|
|
@@ -140,12 +140,13 @@ module SitemapGenerator
|
|
|
140
140
|
# A SitemapGenerator::SitemapError exception is raised if the file has
|
|
141
141
|
# already been written.
|
|
142
142
|
def write
|
|
143
|
-
raise SitemapGenerator::SitemapError
|
|
143
|
+
raise SitemapGenerator::SitemapError, 'Sitemap already written!' if written?
|
|
144
144
|
|
|
145
145
|
finalize! unless finalized?
|
|
146
146
|
reserve_name
|
|
147
147
|
@xml_content << @xml_wrapper_end
|
|
148
148
|
@location.write(@xml_content, link_count)
|
|
149
|
+
@lastmod = SitemapGenerator::Utilities.current_time
|
|
149
150
|
@xml_content = @xml_wrapper_end = ''
|
|
150
151
|
@written = true
|
|
151
152
|
end
|
|
@@ -158,7 +159,7 @@ module SitemapGenerator
|
|
|
158
159
|
# Reserve a name from the namer unless one has already been reserved.
|
|
159
160
|
# Safe to call more than once.
|
|
160
161
|
def reserve_name
|
|
161
|
-
@reserved_name ||= @location.reserve_name
|
|
162
|
+
@reserved_name ||= @location.reserve_name # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
162
163
|
end
|
|
163
164
|
|
|
164
165
|
# Return a boolean indicating whether a name has been reserved
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module SitemapGenerator
|
|
4
4
|
module Builder
|
|
5
|
+
# Builds the sitemap index XML file, listing all sitemap files.
|
|
6
|
+
# Manages deferred naming (waits for a second sitemap before reserving the index name)
|
|
7
|
+
# and controls whether an index is written at all via +create_index+.
|
|
5
8
|
class SitemapIndexFile < SitemapFile
|
|
6
|
-
|
|
7
9
|
# === Options
|
|
8
10
|
#
|
|
9
11
|
# * <tt>location</tt> - a SitemapGenerator::SitemapIndexLocation instance or a Hash of options
|
|
10
12
|
# from which a SitemapLocation will be created for you.
|
|
11
|
-
def initialize(opts = {})
|
|
13
|
+
def initialize(opts = {}) # rubocop:disable Lint/MissingSuper, Metrics/MethodLength
|
|
12
14
|
@location = opts.is_a?(Hash) ? SitemapGenerator::SitemapIndexLocation.new(opts) : opts
|
|
13
15
|
@link_count = 0
|
|
14
16
|
@sitemaps_link_count = 0
|
|
@@ -49,9 +51,9 @@ module SitemapGenerator
|
|
|
49
51
|
# If a link is being added to the index manually as a string, then we
|
|
50
52
|
# can assume that the index is required (unless create_index is false of course).
|
|
51
53
|
# This seems like the logical thing to do.
|
|
52
|
-
|
|
53
|
-
def add(link, options = {})
|
|
54
|
-
if file = link.is_a?(SitemapFile) && link
|
|
54
|
+
alias super_add add
|
|
55
|
+
def add(link, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
56
|
+
if (file = link.is_a?(SitemapFile) && link)
|
|
55
57
|
@sitemaps_link_count += file.link_count
|
|
56
58
|
file.finalize! unless file.finalized?
|
|
57
59
|
|
|
@@ -62,7 +64,7 @@ module SitemapGenerator
|
|
|
62
64
|
# first name (the index, or the sitemap). If the item is not a SitemapFile,
|
|
63
65
|
# then it has been manually added and we can be sure that the user intends
|
|
64
66
|
# for there to be an index.
|
|
65
|
-
if @link_count
|
|
67
|
+
if @link_count.zero?
|
|
66
68
|
@first_sitemap = SitemapGenerator::Builder::LinkHolder.new(file, options)
|
|
67
69
|
@link_count += 1 # pretend it's added, but don't add it yet
|
|
68
70
|
else
|
|
@@ -90,7 +92,7 @@ module SitemapGenerator
|
|
|
90
92
|
# of <tt>bytes</tt> bytes in size. You can also pass a string and the
|
|
91
93
|
# bytesize will be calculated for you.
|
|
92
94
|
def file_can_fit?(bytes)
|
|
93
|
-
bytes =
|
|
95
|
+
bytes = SitemapGenerator::Utilities.bytesize(bytes) if bytes.is_a?(String)
|
|
94
96
|
(@filesize + bytes) < SitemapGenerator::MAX_SITEMAP_FILESIZE && @link_count < SitemapGenerator::MAX_SITEMAP_FILES
|
|
95
97
|
end
|
|
96
98
|
|
|
@@ -101,7 +103,7 @@ module SitemapGenerator
|
|
|
101
103
|
|
|
102
104
|
def stats_summary(opts = {})
|
|
103
105
|
str = "Sitemap stats: #{number_with_delimiter(@sitemaps_link_count)} links / #{@link_count} sitemaps"
|
|
104
|
-
str
|
|
106
|
+
str + format(' / %dm%02ds', *opts[:time_taken].divmod(60)) if opts[:time_taken] # rubocop:disable Style/FormatStringToken
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
def finalize!
|
|
@@ -122,7 +124,7 @@ module SitemapGenerator
|
|
|
122
124
|
# If a link is added manually and create_index is not false, we force index
|
|
123
125
|
# creation because they obviously intend for there to be an index. False otherwise.
|
|
124
126
|
def create_index?
|
|
125
|
-
@create_index || @location.create_index == true || @location.create_index == :auto && @link_count > 1
|
|
127
|
+
@create_index || @location.create_index == true || (@location.create_index == :auto && @link_count > 1)
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
# Return the index file URL. If create_index is true, this is the URL
|
|
@@ -141,15 +143,15 @@ module SitemapGenerator
|
|
|
141
143
|
|
|
142
144
|
# Make sure the first sitemap has been written out and added to the index
|
|
143
145
|
def write_first_sitemap
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
return unless @first_sitemap
|
|
147
|
+
|
|
148
|
+
@first_sitemap.link.write unless @first_sitemap.link.written?
|
|
149
|
+
super_add(SitemapGenerator::Builder::SitemapIndexUrl.new(@first_sitemap.link, @first_sitemap.options))
|
|
150
|
+
@link_count -= 1 # we already counted it, don't count it twice
|
|
151
|
+
# Store the URL because if create_index is false, this is the
|
|
152
|
+
# "index" URL
|
|
153
|
+
@first_sitemap_url = @first_sitemap.link.location.url
|
|
154
|
+
@first_sitemap = nil
|
|
153
155
|
end
|
|
154
156
|
end
|
|
155
157
|
end
|
|
@@ -4,16 +4,15 @@ require 'builder'
|
|
|
4
4
|
|
|
5
5
|
module SitemapGenerator
|
|
6
6
|
module Builder
|
|
7
|
+
# Wraps a sitemap file reference as a +<sitemap>+ XML entry for use in a sitemap index.
|
|
7
8
|
class SitemapIndexUrl < SitemapUrl
|
|
8
|
-
|
|
9
9
|
def initialize(path, options = {})
|
|
10
|
-
if index = path.is_a?(SitemapGenerator::Builder::SitemapIndexFile) && path
|
|
11
|
-
options = SitemapGenerator::Utilities.reverse_merge(options, host: index.location.host, lastmod:
|
|
10
|
+
if (index = path.is_a?(SitemapGenerator::Builder::SitemapIndexFile) && path)
|
|
11
|
+
options = SitemapGenerator::Utilities.reverse_merge(options, host: index.location.host, lastmod: SitemapGenerator::Utilities.current_time,
|
|
12
|
+
priority: 1.0)
|
|
12
13
|
path = index.location.path_in_public
|
|
13
|
-
super(path, options)
|
|
14
|
-
else
|
|
15
|
-
super
|
|
16
14
|
end
|
|
15
|
+
super
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
# Return the URL as XML
|
|
@@ -9,8 +9,7 @@ module SitemapGenerator
|
|
|
9
9
|
module Builder
|
|
10
10
|
# A Hash-like class for holding information about a sitemap URL and
|
|
11
11
|
# generating an XML <url> element suitable for sitemaps.
|
|
12
|
-
class SitemapUrl < Hash
|
|
13
|
-
|
|
12
|
+
class SitemapUrl < Hash # rubocop:disable Metrics/ClassLength
|
|
14
13
|
# Return a new instance with options configured on it.
|
|
15
14
|
#
|
|
16
15
|
# == Arguments
|
|
@@ -31,9 +30,10 @@ module SitemapGenerator
|
|
|
31
30
|
# * +mobile+
|
|
32
31
|
# * +alternate+/+alternates+
|
|
33
32
|
# * +pagemap+
|
|
34
|
-
def initialize(path, options = {})
|
|
33
|
+
def initialize(path, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
34
|
+
super()
|
|
35
35
|
options = SitemapGenerator::Utilities.symbolize_keys(options)
|
|
36
|
-
if sitemap = path.is_a?(SitemapGenerator::Builder::SitemapFile) && path
|
|
36
|
+
if (sitemap = path.is_a?(SitemapGenerator::Builder::SitemapFile) && path)
|
|
37
37
|
SitemapGenerator::Utilities.reverse_merge!(
|
|
38
38
|
options,
|
|
39
39
|
host: sitemap.location.host,
|
|
@@ -44,13 +44,15 @@ module SitemapGenerator
|
|
|
44
44
|
|
|
45
45
|
SitemapGenerator::Utilities.assert_valid_keys(
|
|
46
46
|
options,
|
|
47
|
-
:priority, :changefreq, :lastmod, :expires, :host,
|
|
47
|
+
:priority, :changefreq, :lastmod, :expires, :host,
|
|
48
|
+
:images, :video, :news, :videos, :mobile,
|
|
49
|
+
:alternate, :alternates, :pagemap
|
|
48
50
|
)
|
|
49
51
|
SitemapGenerator::Utilities.reverse_merge!(
|
|
50
52
|
options,
|
|
51
53
|
priority: 0.5,
|
|
52
54
|
changefreq: 'weekly',
|
|
53
|
-
lastmod:
|
|
55
|
+
lastmod: SitemapGenerator::Utilities.current_time,
|
|
54
56
|
images: [],
|
|
55
57
|
news: {},
|
|
56
58
|
videos: [],
|
|
@@ -59,16 +61,17 @@ module SitemapGenerator
|
|
|
59
61
|
)
|
|
60
62
|
raise 'Cannot generate a url without a host' unless SitemapGenerator::Utilities.present?(options[:host])
|
|
61
63
|
|
|
62
|
-
if video = options.delete(:video)
|
|
64
|
+
if (video = options.delete(:video))
|
|
63
65
|
options[:videos] = video.is_a?(Array) ? options[:videos].concat(video) : options[:videos] << video
|
|
64
66
|
end
|
|
65
|
-
if alternate = options.delete(:alternate)
|
|
66
|
-
options[:alternates] =
|
|
67
|
+
if (alternate = options.delete(:alternate))
|
|
68
|
+
options[:alternates] =
|
|
69
|
+
alternate.is_a?(Array) ? options[:alternates].concat(alternate) : options[:alternates] << alternate
|
|
67
70
|
end
|
|
68
71
|
|
|
69
|
-
path = path.to_s.sub(
|
|
70
|
-
loc = path.empty? ? options[:host] :
|
|
71
|
-
|
|
72
|
+
path = encode_non_ascii(path.to_s.sub(%r{^/}, ''))
|
|
73
|
+
loc = path.empty? ? options[:host] : "#{options[:host].to_s.sub(%r{/$}, '')}/#{path}"
|
|
74
|
+
merge!(
|
|
72
75
|
priority: options[:priority],
|
|
73
76
|
changefreq: options[:changefreq],
|
|
74
77
|
lastmod: options[:lastmod],
|
|
@@ -79,15 +82,15 @@ module SitemapGenerator
|
|
|
79
82
|
news: prepare_news(options[:news]),
|
|
80
83
|
videos: options[:videos],
|
|
81
84
|
mobile: options[:mobile],
|
|
82
|
-
alternates: options[:alternates],
|
|
85
|
+
alternates: prepare_alternates(options[:alternates], options[:host]),
|
|
83
86
|
pagemap: options[:pagemap]
|
|
84
87
|
)
|
|
85
88
|
end
|
|
86
89
|
|
|
87
90
|
# Return the URL as XML
|
|
88
|
-
def to_xml(builder = nil)
|
|
91
|
+
def to_xml(builder = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
89
92
|
builder = ::Builder::XmlMarkup.new if builder.nil?
|
|
90
|
-
builder.url do
|
|
93
|
+
builder.url do # rubocop:disable Metrics/BlockLength
|
|
91
94
|
builder.loc self[:loc]
|
|
92
95
|
builder.lastmod w3c_date(self[:lastmod]) if self[:lastmod]
|
|
93
96
|
builder.expires w3c_date(self[:expires]) if self[:expires]
|
|
@@ -121,15 +124,17 @@ module SitemapGenerator
|
|
|
121
124
|
end
|
|
122
125
|
end
|
|
123
126
|
|
|
124
|
-
self[:videos].each do |video|
|
|
125
|
-
builder.video :video do
|
|
127
|
+
self[:videos].each do |video| # rubocop:disable Metrics/BlockLength
|
|
128
|
+
builder.video :video do # rubocop:disable Metrics/BlockLength
|
|
126
129
|
builder.video :thumbnail_loc, video[:thumbnail_loc].to_s
|
|
127
130
|
builder.video :title, video[:title].to_s
|
|
128
131
|
builder.video :description, video[:description].to_s
|
|
129
132
|
builder.video :content_loc, video[:content_loc].to_s if video[:content_loc]
|
|
130
133
|
if video[:player_loc]
|
|
131
134
|
loc_attributes = { allow_embed: yes_or_no_with_default(video[:allow_embed], true) }
|
|
132
|
-
|
|
135
|
+
if SitemapGenerator::Utilities.present?(video[:autoplay])
|
|
136
|
+
loc_attributes[:autoplay] = video[:autoplay].to_s
|
|
137
|
+
end
|
|
133
138
|
builder.video :player_loc, video[:player_loc].to_s, loc_attributes
|
|
134
139
|
end
|
|
135
140
|
builder.video :duration, video[:duration].to_s if video[:duration]
|
|
@@ -137,17 +142,30 @@ module SitemapGenerator
|
|
|
137
142
|
builder.video :rating, format_float(video[:rating]) if video[:rating]
|
|
138
143
|
builder.video :view_count, video[:view_count].to_s if video[:view_count]
|
|
139
144
|
builder.video :publication_date, w3c_date(video[:publication_date]) if video[:publication_date]
|
|
140
|
-
video[:tags]
|
|
145
|
+
video[:tags]&.each { |tag| builder.video :tag, tag.to_s }
|
|
141
146
|
builder.video :tag, video[:tag].to_s if video[:tag]
|
|
142
147
|
builder.video :category, video[:category].to_s if video[:category]
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
if video.key?(:family_friendly)
|
|
149
|
+
builder.video :family_friendly,
|
|
150
|
+
yes_or_no_with_default(video[:family_friendly], true)
|
|
151
|
+
end
|
|
152
|
+
if video[:gallery_loc]
|
|
153
|
+
builder.video :gallery_loc, video[:gallery_loc].to_s,
|
|
154
|
+
title: video[:gallery_title].to_s
|
|
155
|
+
end
|
|
156
|
+
if SitemapGenerator::Utilities.present?(video[:price])
|
|
157
|
+
builder.video :price, video[:price].to_s, prepare_video_price_attribs(video)
|
|
158
|
+
end
|
|
146
159
|
if video[:uploader]
|
|
147
|
-
builder.video :uploader, video[:uploader].to_s,
|
|
160
|
+
builder.video :uploader, video[:uploader].to_s,
|
|
161
|
+
video[:uploader_info] ? { info: video[:uploader_info].to_s } : {}
|
|
162
|
+
end
|
|
163
|
+
builder.video :live, yes_or_no_with_default(video[:live], true) if video.key?(:live)
|
|
164
|
+
if video.key?(:requires_subscription)
|
|
165
|
+
builder.video :requires_subscription,
|
|
166
|
+
yes_or_no_with_default(video[:requires_subscription],
|
|
167
|
+
true)
|
|
148
168
|
end
|
|
149
|
-
builder.video :live, yes_or_no_with_default(video[:live], true) if video.has_key?(:live)
|
|
150
|
-
builder.video :requires_subscription, yes_or_no_with_default(video[:requires_subscription], true) if video.has_key?(:requires_subscription)
|
|
151
169
|
end
|
|
152
170
|
end
|
|
153
171
|
|
|
@@ -159,9 +177,7 @@ module SitemapGenerator
|
|
|
159
177
|
builder.xhtml :link, attributes
|
|
160
178
|
end
|
|
161
179
|
|
|
162
|
-
unless SitemapGenerator::Utilities.blank?(self[:mobile])
|
|
163
|
-
builder.mobile :mobile
|
|
164
|
-
end
|
|
180
|
+
builder.mobile :mobile unless SitemapGenerator::Utilities.blank?(self[:mobile])
|
|
165
181
|
|
|
166
182
|
unless SitemapGenerator::Utilities.blank?(self[:pagemap])
|
|
167
183
|
builder.pagemap :PageMap do
|
|
@@ -188,18 +204,40 @@ module SitemapGenerator
|
|
|
188
204
|
attribs = {}
|
|
189
205
|
attribs[:currency] = video[:price_currency].to_s # required
|
|
190
206
|
attribs[:type] = video[:price_type] if SitemapGenerator::Utilities.present?(video[:price_type])
|
|
191
|
-
|
|
207
|
+
if SitemapGenerator::Utilities.present?(video[:price_resolution])
|
|
208
|
+
attribs[:resolution] = video[:price_resolution]
|
|
209
|
+
end
|
|
192
210
|
attribs
|
|
193
211
|
end
|
|
194
212
|
|
|
195
213
|
def prepare_news(news)
|
|
196
|
-
|
|
214
|
+
unless news.empty?
|
|
215
|
+
SitemapGenerator::Utilities.assert_valid_keys(
|
|
216
|
+
news,
|
|
217
|
+
:publication_name, :publication_language, :publication_date,
|
|
218
|
+
:genres, :access, :title, :keywords, :stock_tickers
|
|
219
|
+
)
|
|
220
|
+
end
|
|
197
221
|
news
|
|
198
222
|
end
|
|
199
223
|
|
|
224
|
+
# Return an Array of alternate option Hashes with relative hrefs expanded to absolute URLs.
|
|
225
|
+
def prepare_alternates(alternates, host)
|
|
226
|
+
alternates.map do |alt|
|
|
227
|
+
next alt unless alt.is_a?(Hash)
|
|
228
|
+
|
|
229
|
+
href = alt[:href].to_s
|
|
230
|
+
if href.start_with?('/')
|
|
231
|
+
alt.merge(href: URI.join(host, href).to_s)
|
|
232
|
+
else
|
|
233
|
+
alt
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
200
238
|
# Return an Array of image option Hashes suitable to be parsed by SitemapGenerator::Builder::SitemapFile
|
|
201
239
|
def prepare_images(images, host)
|
|
202
|
-
images.delete_if { |key,
|
|
240
|
+
images.delete_if { |key, _value| key[:loc].nil? }
|
|
203
241
|
images.each do |r|
|
|
204
242
|
SitemapGenerator::Utilities.assert_valid_keys(r, :loc, :caption, :geo_location, :title, :license)
|
|
205
243
|
r[:loc] = URI.join(host, r[:loc]).to_s
|
|
@@ -207,7 +245,7 @@ module SitemapGenerator
|
|
|
207
245
|
images[0..(SitemapGenerator::MAX_SITEMAP_IMAGES - 1)]
|
|
208
246
|
end
|
|
209
247
|
|
|
210
|
-
def w3c_date(date)
|
|
248
|
+
def w3c_date(date) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
211
249
|
if date.is_a?(String)
|
|
212
250
|
date
|
|
213
251
|
elsif date.respond_to?(:iso8601)
|
|
@@ -222,8 +260,6 @@ module SitemapGenerator
|
|
|
222
260
|
date.utc
|
|
223
261
|
elsif date.is_a?(Integer)
|
|
224
262
|
Time.at(date).utc
|
|
225
|
-
else
|
|
226
|
-
nil
|
|
227
263
|
end
|
|
228
264
|
|
|
229
265
|
if zulutime
|
|
@@ -239,7 +275,9 @@ module SitemapGenerator
|
|
|
239
275
|
# value must be 'yes' or 'no'. Pass the default value as a boolean using `default`.
|
|
240
276
|
def yes_or_no(value)
|
|
241
277
|
if value.is_a?(String)
|
|
242
|
-
|
|
278
|
+
unless /^(yes|no)$/i.match?(value)
|
|
279
|
+
raise ArgumentError, "Unrecognized value for yes/no field: #{value.inspect}"
|
|
280
|
+
end
|
|
243
281
|
|
|
244
282
|
value.downcase
|
|
245
283
|
else
|
|
@@ -256,7 +294,15 @@ module SitemapGenerator
|
|
|
256
294
|
# Format a float to to one decimal precision.
|
|
257
295
|
# TODO: Use rounding with precision once merged with framework_agnostic.
|
|
258
296
|
def format_float(value)
|
|
259
|
-
value.is_a?(String) ? value : ('%0.1f'
|
|
297
|
+
value.is_a?(String) ? value : format('%0.1f', value)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
# Percent-encode non-ASCII bytes in a path segment, leaving already-encoded %XX sequences untouched.
|
|
303
|
+
# Applied to paths only — not the host — IDN hostnames require punycode, not percent-encoding.
|
|
304
|
+
def encode_non_ascii(str)
|
|
305
|
+
str.gsub(/[^\x00-\x7F]/) { |c| c.bytes.map { |b| format('%%%02X', b) }.join }
|
|
260
306
|
end
|
|
261
307
|
end
|
|
262
308
|
end
|