indexmap 0.6.0 → 0.7.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/CHANGELOG.md +3 -5
- data/README.md +92 -22
- data/lib/indexmap/configuration.rb +3 -6
- data/lib/indexmap/creator.rb +22 -35
- data/lib/indexmap/index_now_configuration.rb +12 -5
- data/lib/indexmap/output.rb +3 -5
- data/lib/indexmap/parser.rb +26 -14
- data/lib/indexmap/pinger/base.rb +5 -1
- data/lib/indexmap/pinger/google.rb +2 -2
- data/lib/indexmap/pinger/index_now.rb +21 -24
- data/lib/indexmap/storage/active_storage.rb +105 -0
- data/lib/indexmap/storage/file.rb +11 -0
- data/lib/indexmap/storage/filesystem.rb +77 -0
- data/lib/indexmap/storage/memory.rb +61 -0
- data/lib/indexmap/task_runner.rb +8 -8
- data/lib/indexmap/validator.rb +42 -30
- data/lib/indexmap/version.rb +1 -1
- data/lib/indexmap/writer.rb +2 -9
- data/lib/indexmap.rb +4 -1
- data/lib/tasks/indexmap_tasks.rake +5 -5
- data/test/indexmap/configuration_test.rb +98 -129
- data/test/indexmap/parser_test.rb +44 -3
- data/test/indexmap/pinger/google_test.rb +101 -123
- data/test/indexmap/pinger/index_now_test.rb +148 -179
- data/test/indexmap/storage_test.rb +123 -0
- data/test/indexmap/task_runner_test.rb +92 -63
- data/test/indexmap/validator_test.rb +96 -92
- data/test/indexmap/writer_test.rb +63 -74
- metadata +6 -3
- data/lib/indexmap/path.rb +0 -42
- data/test/indexmap/path_test.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90b723b27367659107827ca3ff1f7473c6a4a917b62ae1cdb4ae2cfcf440f3f2
|
|
4
|
+
data.tar.gz: cd5be4391a83b1378efcad593408648822dabff4dc8e5479e6ae3e3c12d93ac3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '099bf5472fae8a8be3d17ad40003ea5802dd410799431af367e604dca3cd0fce04e98555526dfb36b98f270774b41eec160a47c5abdc9fd81ac148080901d96f'
|
|
7
|
+
data.tar.gz: e53e45fe051e02b0000e05c5000f8f23c2e1cc7ab78a952cad6a4a33891b108c8811cd67d9fcadea6cf39bea4abf74eae9736d9e85402e6a1988ef49ace9c0dc
|
data/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.
|
|
8
|
+
## [0.7.0] - 2026-05-08
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Fixed
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
- support named sitemap outputs (#10)
|
|
13
|
+
- avoid rewriting existing IndexNow key files (#11)
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
|
data/README.md
CHANGED
|
@@ -34,6 +34,10 @@ Or install it directly:
|
|
|
34
34
|
gem install indexmap
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Upgrading an existing app? Read [UPGRADE.md](UPGRADE.md) before deploying,
|
|
38
|
+
especially if the app uses custom storage or stores sitemap files under a
|
|
39
|
+
directory prefix such as `sitemaps/`.
|
|
40
|
+
|
|
37
41
|
## Ruby Usage
|
|
38
42
|
|
|
39
43
|
```ruby
|
|
@@ -51,7 +55,6 @@ sections = [
|
|
|
51
55
|
|
|
52
56
|
Indexmap::Writer.new(
|
|
53
57
|
sections: sections,
|
|
54
|
-
public_path: Pathname("public"),
|
|
55
58
|
base_url: "https://example.com"
|
|
56
59
|
).write
|
|
57
60
|
```
|
|
@@ -63,7 +66,12 @@ In an initializer:
|
|
|
63
66
|
```ruby
|
|
64
67
|
Indexmap.configure do |config|
|
|
65
68
|
config.base_url = -> { "https://example.com" }
|
|
66
|
-
config.
|
|
69
|
+
config.storage = -> do
|
|
70
|
+
Indexmap::Storage::Filesystem.new(
|
|
71
|
+
path: Rails.public_path,
|
|
72
|
+
public_url: config.base_url
|
|
73
|
+
)
|
|
74
|
+
end
|
|
67
75
|
config.sections = -> do
|
|
68
76
|
[
|
|
69
77
|
Indexmap::Section.new(
|
|
@@ -85,26 +93,26 @@ bin/rails indexmap:sitemap:format
|
|
|
85
93
|
bin/rails indexmap:sitemap:validate
|
|
86
94
|
```
|
|
87
95
|
|
|
88
|
-
`indexmap:sitemap:create` is the main task. It
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
fails.
|
|
96
|
+
`indexmap:sitemap:create` is the main task. It builds sitemap files in memory,
|
|
97
|
+
formats them, validates the result, then writes the final XML files to the
|
|
98
|
+
configured storage. Existing sitemap files are left untouched if generation or
|
|
99
|
+
validation fails.
|
|
92
100
|
|
|
93
101
|
### Default Index Mode
|
|
94
102
|
|
|
95
103
|
This is the default behavior. `indexmap` writes:
|
|
96
104
|
|
|
97
|
-
- `
|
|
105
|
+
- `sitemap.xml` as a sitemap index
|
|
98
106
|
- one or more child sitemap files from `config.sections`
|
|
99
107
|
|
|
100
108
|
### Single-File Mode
|
|
101
109
|
|
|
102
|
-
For sites that only want one `
|
|
110
|
+
For sites that only want one `sitemap.xml` file:
|
|
103
111
|
|
|
104
112
|
```ruby
|
|
105
113
|
Indexmap.configure do |config|
|
|
106
114
|
config.base_url = -> { "https://example.com" }
|
|
107
|
-
config.
|
|
115
|
+
config.storage = -> { Indexmap::Storage::Filesystem.new(path: Rails.public_path, public_url: config.base_url) }
|
|
108
116
|
config.format = :single_file
|
|
109
117
|
config.entries = -> do
|
|
110
118
|
[
|
|
@@ -122,13 +130,12 @@ In `:single_file` mode, `indexmap` writes a `urlset` directly to `sitemap.xml` a
|
|
|
122
130
|
Most apps only need the default output. Use named outputs when one part of the
|
|
123
131
|
sitemap must be generated separately, for example when static pages can be
|
|
124
132
|
generated during deploy but database-heavy pages should refresh later. Named
|
|
125
|
-
outputs
|
|
126
|
-
serving are application concerns.
|
|
133
|
+
outputs write through the same configured storage as the default output.
|
|
127
134
|
|
|
128
135
|
```ruby
|
|
129
136
|
Indexmap.configure do |config|
|
|
130
137
|
config.base_url = -> { "https://example.com" }
|
|
131
|
-
config.
|
|
138
|
+
config.storage = -> { Indexmap::Storage::Filesystem.new(path: Rails.root.join("storage/sitemaps"), public_url: config.base_url) }
|
|
132
139
|
config.sections = -> { Sitemap.sections }
|
|
133
140
|
|
|
134
141
|
config.output :insights_data do |output|
|
|
@@ -151,12 +158,57 @@ Generate only the named output:
|
|
|
151
158
|
Indexmap.create(:insights_data)
|
|
152
159
|
```
|
|
153
160
|
|
|
154
|
-
Named outputs inherit `base_url
|
|
155
|
-
|
|
161
|
+
Named outputs inherit `base_url` and `format` from the main configuration unless
|
|
162
|
+
you override them. Storage is configured once and shared by every output.
|
|
163
|
+
|
|
164
|
+
`Indexmap.create` uses the same safe publish flow as the rake task: build,
|
|
165
|
+
format, validate, and then write the final XML file or files to storage.
|
|
166
|
+
|
|
167
|
+
### Storage
|
|
168
|
+
|
|
169
|
+
Every `indexmap` operation reads and writes through `config.storage`. The storage
|
|
170
|
+
object is the source of truth for generation, validation, parsing, Google
|
|
171
|
+
submission, IndexNow submission, and IndexNow verification files.
|
|
172
|
+
|
|
173
|
+
The filesystem adapter stores files in a directory and exposes public URLs from
|
|
174
|
+
the same filenames:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
Indexmap.configure do |config|
|
|
178
|
+
config.base_url = "https://example.com"
|
|
179
|
+
config.storage = Indexmap::Storage::Filesystem.new(
|
|
180
|
+
path: Rails.public_path,
|
|
181
|
+
public_url: "https://example.com"
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Rails apps that store sitemap files in Active Storage can use the optional
|
|
187
|
+
adapter. `indexmap` does not depend on `activestorage`; this adapter only uses
|
|
188
|
+
the model and attachment object you pass in.
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
Indexmap.configure do |config|
|
|
192
|
+
config.base_url = "https://example.com"
|
|
193
|
+
config.storage = Indexmap::Storage::ActiveStorage.new(
|
|
194
|
+
model: SitemapArtifact,
|
|
195
|
+
filename_column: :filename,
|
|
196
|
+
attachment: :file,
|
|
197
|
+
public_url: "https://example.com"
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Custom storage backends can implement the same small interface:
|
|
156
203
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
204
|
+
```ruby
|
|
205
|
+
storage.write(filename, body, content_type:)
|
|
206
|
+
storage.read(filename)
|
|
207
|
+
storage.exist?(filename)
|
|
208
|
+
storage.list(prefix:, suffix:)
|
|
209
|
+
storage.delete(filename)
|
|
210
|
+
storage.public_url(filename)
|
|
211
|
+
```
|
|
160
212
|
|
|
161
213
|
### Deferred Dynamic Sections
|
|
162
214
|
|
|
@@ -168,7 +220,7 @@ replaced successfully.
|
|
|
168
220
|
```ruby
|
|
169
221
|
Indexmap.configure do |config|
|
|
170
222
|
config.base_url = -> { "https://example.com" }
|
|
171
|
-
config.
|
|
223
|
+
config.storage = -> { Indexmap::Storage::Filesystem.new(path: Rails.root.join("storage/sitemaps"), public_url: config.base_url) }
|
|
172
224
|
config.sections = -> { Sitemap.sections }
|
|
173
225
|
|
|
174
226
|
config.output :insights_data do |output|
|
|
@@ -201,7 +253,7 @@ while database-dependent output is refreshed by the job backend.
|
|
|
201
253
|
`indexmap` also includes small utilities for working with generated sitemap files:
|
|
202
254
|
|
|
203
255
|
```ruby
|
|
204
|
-
parser = Indexmap::Parser.new(
|
|
256
|
+
parser = Indexmap::Parser.new(source: "sitemap.xml")
|
|
205
257
|
parser.paths
|
|
206
258
|
# => ["/", "/about", "/articles/example"]
|
|
207
259
|
|
|
@@ -263,7 +315,7 @@ If `config.google.property` is not set, `indexmap` defaults to `sc-domain:<host>
|
|
|
263
315
|
IndexNow submission requires a key. `indexmap` supports two ways to provide it:
|
|
264
316
|
|
|
265
317
|
- set `config.index_now.key`
|
|
266
|
-
- or keep a valid verification file
|
|
318
|
+
- or keep a valid verification file in the configured storage as `<key>.txt`
|
|
267
319
|
|
|
268
320
|
Configured-key example:
|
|
269
321
|
|
|
@@ -273,7 +325,25 @@ Indexmap.configure do |config|
|
|
|
273
325
|
end
|
|
274
326
|
```
|
|
275
327
|
|
|
276
|
-
If `config.index_now.key` is set, `indexmap:sitemap:create` also
|
|
328
|
+
If `config.index_now.key` is set, `indexmap:sitemap:create` also ensures the matching `<key>.txt` verification file exists in storage. It leaves an existing valid key file unchanged.
|
|
329
|
+
|
|
330
|
+
If you need a non-standard verification filename, configure it explicitly:
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
Indexmap.configure do |config|
|
|
334
|
+
config.index_now.key = -> { ENV["INDEXNOW_KEY"] }
|
|
335
|
+
config.index_now.key_filename = -> { "#{ENV.fetch("INDEXNOW_KEY")}.txt" }
|
|
336
|
+
end
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
You can also disable automatic key-file writes entirely:
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
Indexmap.configure do |config|
|
|
343
|
+
config.index_now.key = -> { ENV["INDEXNOW_KEY"] }
|
|
344
|
+
config.index_now.write_key_file = false
|
|
345
|
+
end
|
|
346
|
+
```
|
|
277
347
|
|
|
278
348
|
If you prefer the file-based flow, run:
|
|
279
349
|
|
|
@@ -284,7 +354,7 @@ bin/rails indexmap:index_now:write_key
|
|
|
284
354
|
That task:
|
|
285
355
|
|
|
286
356
|
- reuses an existing valid key file when present
|
|
287
|
-
- otherwise generates a new key in
|
|
357
|
+
- otherwise generates a new key in `<key>.txt`
|
|
288
358
|
- makes that key available to `indexmap:index_now:ping` without adding `config.index_now.key`
|
|
289
359
|
|
|
290
360
|
If neither a configured key nor a valid key file is present, `indexmap:index_now:ping` skips IndexNow submission.
|
|
@@ -4,7 +4,7 @@ module Indexmap
|
|
|
4
4
|
class Configuration
|
|
5
5
|
VALID_FORMATS = %i[index single_file].freeze
|
|
6
6
|
|
|
7
|
-
attr_writer :base_url, :entries, :format, :index_filename, :
|
|
7
|
+
attr_writer :base_url, :entries, :format, :index_filename, :sections, :storage
|
|
8
8
|
|
|
9
9
|
def initialize
|
|
10
10
|
@format = :index
|
|
@@ -38,11 +38,8 @@ module Indexmap
|
|
|
38
38
|
@index_now ||= IndexNowConfiguration.new
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
return Pathname("public") if value.nil?
|
|
44
|
-
|
|
45
|
-
Pathname(value)
|
|
41
|
+
def storage
|
|
42
|
+
resolve(@storage) || Storage::Filesystem.new(path: "public", public_url: base_url)
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
def sections
|
data/lib/indexmap/creator.rb
CHANGED
|
@@ -1,74 +1,61 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri"
|
|
4
|
-
require "tmpdir"
|
|
5
4
|
|
|
6
5
|
module Indexmap
|
|
7
6
|
class Creator
|
|
8
|
-
ValidationConfiguration = Struct.new(:base_url, keyword_init: true)
|
|
7
|
+
ValidationConfiguration = Struct.new(:base_url, :index_filename, :storage, keyword_init: true)
|
|
9
8
|
|
|
10
9
|
def initialize(output:)
|
|
11
10
|
@output = output
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def create
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
written_files = write_to(staging_path)
|
|
20
|
-
sitemap_files = sitemap_files_in(staging_path)
|
|
21
|
-
|
|
22
|
-
format(sitemap_files)
|
|
23
|
-
validate(staging_path.join(output.index_filename))
|
|
24
|
-
|
|
25
|
-
publish(sitemap_files)
|
|
26
|
-
written_files.map { |path| output.public_path.join(path.basename) }
|
|
27
|
-
end
|
|
14
|
+
files = format(write)
|
|
15
|
+
validate(files)
|
|
16
|
+
publish(files)
|
|
17
|
+
files.map(&:filename)
|
|
28
18
|
end
|
|
29
19
|
|
|
30
20
|
private
|
|
31
21
|
|
|
32
22
|
attr_reader :output
|
|
33
23
|
|
|
34
|
-
def
|
|
35
|
-
output.writer.
|
|
36
|
-
writer.public_path = staging_path
|
|
37
|
-
end.write
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def sitemap_files_in(path)
|
|
41
|
-
path.glob("sitemap*.xml").sort
|
|
24
|
+
def write
|
|
25
|
+
output.writer.write
|
|
42
26
|
end
|
|
43
27
|
|
|
44
28
|
def format(files)
|
|
45
|
-
files.
|
|
29
|
+
files.map do |file|
|
|
46
30
|
document = Nokogiri::XML(
|
|
47
|
-
|
|
31
|
+
file.body,
|
|
48
32
|
nil,
|
|
49
33
|
nil,
|
|
50
34
|
Nokogiri::XML::ParseOptions::DEFAULT_XML | Nokogiri::XML::ParseOptions::NOBLANKS
|
|
51
35
|
)
|
|
52
36
|
save_options = Nokogiri::XML::Node::SaveOptions::FORMAT | Nokogiri::XML::Node::SaveOptions::AS_XML
|
|
53
37
|
|
|
54
|
-
|
|
38
|
+
Storage::File.new(
|
|
39
|
+
filename: file.filename,
|
|
40
|
+
body: document.to_xml(indent: 2, save_with: save_options),
|
|
41
|
+
content_type: file.content_type
|
|
42
|
+
)
|
|
55
43
|
end
|
|
56
44
|
end
|
|
57
45
|
|
|
58
|
-
def validate(
|
|
46
|
+
def validate(files)
|
|
59
47
|
Validator.new(
|
|
60
|
-
configuration: ValidationConfiguration.new(
|
|
61
|
-
|
|
48
|
+
configuration: ValidationConfiguration.new(
|
|
49
|
+
base_url: output.base_url,
|
|
50
|
+
index_filename: output.index_filename,
|
|
51
|
+
storage: Storage::Memory.new(files)
|
|
52
|
+
)
|
|
62
53
|
).validate!
|
|
63
54
|
end
|
|
64
55
|
|
|
65
56
|
def publish(files)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
files.map do |file_path|
|
|
69
|
-
final_path = output.public_path.join(file_path.basename)
|
|
70
|
-
File.rename(file_path, final_path)
|
|
71
|
-
final_path
|
|
57
|
+
files.each do |file|
|
|
58
|
+
output.storage.write(file.filename, file.body, content_type: file.content_type)
|
|
72
59
|
end
|
|
73
60
|
end
|
|
74
61
|
end
|
|
@@ -5,7 +5,7 @@ module Indexmap
|
|
|
5
5
|
DEFAULT_ENDPOINT = "https://api.indexnow.org"
|
|
6
6
|
DEFAULT_MAX_URLS_PER_REQUEST = 500
|
|
7
7
|
|
|
8
|
-
attr_writer :dry_run, :endpoint, :key, :
|
|
8
|
+
attr_writer :dry_run, :endpoint, :key, :key_filename, :max_urls_per_request, :write_key_file
|
|
9
9
|
|
|
10
10
|
def dry_run?
|
|
11
11
|
value = resolve(@dry_run)
|
|
@@ -21,12 +21,19 @@ module Indexmap
|
|
|
21
21
|
resolve(@key)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
return
|
|
24
|
+
def write_key_file?
|
|
25
|
+
value = resolve(@write_key_file)
|
|
26
|
+
return !key.to_s.strip.empty? if value.nil?
|
|
27
|
+
|
|
28
|
+
value == true || value.to_s == "1"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def key_filename(key: self.key)
|
|
32
|
+
configured_filename = resolve(@key_filename)
|
|
33
|
+
return configured_filename unless configured_filename.to_s.strip.empty?
|
|
27
34
|
return if key.to_s.strip.empty?
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
"#{key}.txt"
|
|
30
37
|
end
|
|
31
38
|
|
|
32
39
|
def max_urls_per_request
|
data/lib/indexmap/output.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Indexmap
|
|
|
4
4
|
class Output
|
|
5
5
|
VALID_FORMATS = %i[index single_file].freeze
|
|
6
6
|
|
|
7
|
-
attr_writer :base_url, :entries, :format, :index_filename, :
|
|
7
|
+
attr_writer :base_url, :entries, :format, :index_filename, :sections
|
|
8
8
|
|
|
9
9
|
def initialize(configuration:)
|
|
10
10
|
@configuration = configuration
|
|
@@ -29,9 +29,8 @@ module Indexmap
|
|
|
29
29
|
resolve(@index_filename) || configuration.index_filename
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
Pathname(value)
|
|
32
|
+
def storage
|
|
33
|
+
configuration.storage
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
def sections
|
|
@@ -57,7 +56,6 @@ module Indexmap
|
|
|
57
56
|
entries: entries,
|
|
58
57
|
format: format,
|
|
59
58
|
sections: sections,
|
|
60
|
-
public_path: public_path,
|
|
61
59
|
base_url: base_url,
|
|
62
60
|
index_filename: index_filename
|
|
63
61
|
)
|
data/lib/indexmap/parser.rb
CHANGED
|
@@ -8,11 +8,11 @@ module Indexmap
|
|
|
8
8
|
class Parser
|
|
9
9
|
Entry = Struct.new(:loc, :lastmod, :source_sitemap, keyword_init: true)
|
|
10
10
|
|
|
11
|
-
def initialize(
|
|
12
|
-
@source =
|
|
11
|
+
def initialize(source: nil, rebase_remote_children: false, index_filename: Indexmap.configuration.index_filename, storage: Indexmap.configuration.storage)
|
|
12
|
+
@source = (source || index_filename).to_s
|
|
13
13
|
@rebase_remote_children = rebase_remote_children
|
|
14
14
|
@index_filename = index_filename
|
|
15
|
-
@
|
|
15
|
+
@storage = storage
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def entries(reset: false)
|
|
@@ -58,11 +58,7 @@ module Indexmap
|
|
|
58
58
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
-
attr_reader :index_filename, :
|
|
62
|
-
|
|
63
|
-
def default_path
|
|
64
|
-
Indexmap::Path.existing_public_path(public_path: public_path, index_filename: index_filename)
|
|
65
|
-
end
|
|
61
|
+
attr_reader :index_filename, :storage
|
|
66
62
|
|
|
67
63
|
def parse_source(source, visited:)
|
|
68
64
|
normalized_source = normalize_source(source)
|
|
@@ -105,12 +101,21 @@ module Indexmap
|
|
|
105
101
|
end
|
|
106
102
|
elsif remote_source?(loc)
|
|
107
103
|
uri = URI.parse(loc)
|
|
108
|
-
|
|
104
|
+
normalize_local_source(uri.path)
|
|
109
105
|
else
|
|
110
|
-
|
|
106
|
+
resolve_local_child_sitemap(parent_source, loc)
|
|
111
107
|
end
|
|
112
108
|
rescue URI::InvalidURIError
|
|
113
|
-
|
|
109
|
+
resolve_local_child_sitemap(parent_source, loc)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def resolve_local_child_sitemap(parent_source, loc)
|
|
113
|
+
if loc.start_with?("/")
|
|
114
|
+
normalize_local_source(loc)
|
|
115
|
+
else
|
|
116
|
+
parent_directory = File.dirname(parent_source)
|
|
117
|
+
normalize_local_source((parent_directory == ".") ? loc : File.join(parent_directory, loc))
|
|
118
|
+
end
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
def remote_child_source(parent_uri, loc)
|
|
@@ -130,12 +135,19 @@ module Indexmap
|
|
|
130
135
|
if remote_source?(source)
|
|
131
136
|
URI.parse(source).to_s
|
|
132
137
|
else
|
|
133
|
-
|
|
138
|
+
normalize_local_source(source)
|
|
134
139
|
end
|
|
135
140
|
rescue URI::InvalidURIError
|
|
136
141
|
nil
|
|
137
142
|
end
|
|
138
143
|
|
|
144
|
+
def normalize_local_source(source)
|
|
145
|
+
normalized = Pathname(source.to_s).cleanpath.to_s.sub(%r{\A/+}, "")
|
|
146
|
+
return if normalized.empty? || normalized == ".." || normalized.start_with?("../")
|
|
147
|
+
|
|
148
|
+
normalized
|
|
149
|
+
end
|
|
150
|
+
|
|
139
151
|
def remote_source?(value)
|
|
140
152
|
uri = URI.parse(value.to_s)
|
|
141
153
|
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
@@ -146,8 +158,8 @@ module Indexmap
|
|
|
146
158
|
def read_source(source)
|
|
147
159
|
if remote_source?(source)
|
|
148
160
|
fetch_remote_source(source)
|
|
149
|
-
elsif
|
|
150
|
-
|
|
161
|
+
elsif storage.exist?(source)
|
|
162
|
+
storage.read(source)
|
|
151
163
|
end
|
|
152
164
|
end
|
|
153
165
|
|
data/lib/indexmap/pinger/base.rb
CHANGED
|
@@ -46,8 +46,12 @@ module Indexmap
|
|
|
46
46
|
hostname.sub(/\Awww\./, "")
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
def storage
|
|
50
|
+
configuration.storage
|
|
51
|
+
end
|
|
52
|
+
|
|
49
53
|
def sitemap_files
|
|
50
|
-
|
|
54
|
+
storage.list(prefix: "sitemap", suffix: ".xml")
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def ping_sitemap(_sitemap_file)
|
|
@@ -36,7 +36,7 @@ module Indexmap
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def ping_sitemap(sitemap_file)
|
|
39
|
-
sitemap_url =
|
|
39
|
+
sitemap_url = storage.public_url(sitemap_file)
|
|
40
40
|
|
|
41
41
|
unless authorized?
|
|
42
42
|
logger.debug("Google Search Console does not have access to the site: #{root_domain}")
|
|
@@ -93,7 +93,7 @@ module Indexmap
|
|
|
93
93
|
|
|
94
94
|
def sitemap_url_count(files)
|
|
95
95
|
files.each_with_object(Set.new) do |sitemap_file, urls|
|
|
96
|
-
Parser.new(
|
|
96
|
+
Parser.new(source: sitemap_file, storage: storage).entries.each do |entry|
|
|
97
97
|
loc = entry.loc.to_s.strip
|
|
98
98
|
urls.add(loc) unless loc.empty?
|
|
99
99
|
end
|
|
@@ -42,14 +42,15 @@ module Indexmap
|
|
|
42
42
|
summarize_results(results)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def write_key_file(key: index_now_configuration.key,
|
|
45
|
+
def write_key_file(key: index_now_configuration.key, filename: nil)
|
|
46
46
|
key = normalized_configured_key(key)
|
|
47
47
|
return if key.empty?
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
filename ||= index_now_configuration.key_filename(key: key)
|
|
50
|
+
return filename if valid_key_file?(filename)
|
|
51
|
+
|
|
52
|
+
storage.write(filename, key, content_type: "text/plain")
|
|
53
|
+
filename
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def ensure_key_file
|
|
@@ -60,7 +61,7 @@ module Indexmap
|
|
|
60
61
|
return existing_path if existing_path
|
|
61
62
|
|
|
62
63
|
key = generated_key
|
|
63
|
-
write_key_file(key: key,
|
|
64
|
+
write_key_file(key: key, filename: "#{key}.txt")
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
private
|
|
@@ -75,7 +76,7 @@ module Indexmap
|
|
|
75
76
|
files = super
|
|
76
77
|
return files if files.one?
|
|
77
78
|
|
|
78
|
-
child_files = files.reject { |file|
|
|
79
|
+
child_files = files.reject { |file| file == configuration.index_filename }
|
|
79
80
|
child_files.empty? ? files : child_files
|
|
80
81
|
end
|
|
81
82
|
|
|
@@ -95,7 +96,7 @@ module Indexmap
|
|
|
95
96
|
|
|
96
97
|
def current_entries
|
|
97
98
|
sitemap_files.each_with_object({}) do |sitemap_file, entries|
|
|
98
|
-
Parser.new(
|
|
99
|
+
Parser.new(source: sitemap_file, storage: storage).entries.each do |entry|
|
|
99
100
|
next if entry.loc.to_s.strip.empty?
|
|
100
101
|
|
|
101
102
|
entries[entry.loc] = entry
|
|
@@ -177,36 +178,32 @@ module Indexmap
|
|
|
177
178
|
configured_key = normalized_configured_key(index_now_configuration.key)
|
|
178
179
|
return configured_key unless configured_key.empty?
|
|
179
180
|
|
|
180
|
-
existing_key_file
|
|
181
|
+
storage.read(existing_key_file) if existing_key_file
|
|
181
182
|
end
|
|
182
183
|
|
|
183
184
|
def existing_key_file
|
|
184
|
-
|
|
185
|
-
return
|
|
185
|
+
configured_filename = index_now_configuration.key_filename
|
|
186
|
+
return configured_filename if valid_key_file?(configured_filename)
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
storage.list(suffix: ".txt").find { |filename| valid_key_file?(filename) }
|
|
188
189
|
end
|
|
189
190
|
|
|
190
191
|
def key_location(api_key:)
|
|
191
|
-
|
|
192
|
-
return unless
|
|
193
|
-
|
|
194
|
-
public_path = configuration.public_path.expand_path
|
|
195
|
-
key_path = path.expand_path
|
|
196
|
-
relative_path = key_path.relative_path_from(public_path)
|
|
192
|
+
filename = index_now_configuration.key_filename(key: api_key) || existing_key_file
|
|
193
|
+
return unless filename
|
|
197
194
|
|
|
198
|
-
|
|
195
|
+
storage.public_url(filename)
|
|
199
196
|
rescue ArgumentError
|
|
200
197
|
nil
|
|
201
198
|
end
|
|
202
199
|
|
|
203
|
-
def valid_key_file?(
|
|
204
|
-
return false unless
|
|
200
|
+
def valid_key_file?(filename)
|
|
201
|
+
return false unless filename && storage.exist?(filename)
|
|
205
202
|
|
|
206
|
-
|
|
207
|
-
return false unless
|
|
203
|
+
key = File.basename(filename, ".txt").to_s
|
|
204
|
+
return false unless key.match?(KEY_FORMAT)
|
|
208
205
|
|
|
209
|
-
|
|
206
|
+
storage.read(filename) == key
|
|
210
207
|
end
|
|
211
208
|
|
|
212
209
|
def generated_key
|