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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef452a5928b87f84f65ecb9ba2afcab5340d9d09c5d8c8dfd8c4ffb3217a68fd
4
- data.tar.gz: 03b11e1d9360bbd797d6b61886c99d1619247877dfd2c4676efeca9a1119764c
3
+ metadata.gz: 90b723b27367659107827ca3ff1f7473c6a4a917b62ae1cdb4ae2cfcf440f3f2
4
+ data.tar.gz: cd5be4391a83b1378efcad593408648822dabff4dc8e5479e6ae3e3c12d93ac3
5
5
  SHA512:
6
- metadata.gz: a04a772029e2438636df90b65004a27c54e1cf3360205fd556feff615768fd527668fe2a3e3063f2831b9af9afb4edd983e48e73b922278c4c10a63d936b938e
7
- data.tar.gz: 6ce68d9ee6343aaf7c4bbff6860a712d743899390b4a8a3a09c712694c95cff32cc0acd85f30a43e2ba37f0c8ff671d2af11028e63976649ae90e5100d5e0a1b
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.6.0] - 2026-05-01
8
+ ## [0.7.0] - 2026-05-08
9
9
 
10
10
 
11
- ### Added
11
+ ### Fixed
12
12
 
13
- - add the url count to google ping output (#9)
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.public_path = -> { Rails.public_path }
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 writes sitemap files to a local
89
- temporary directory, formats them, validates the result, then replaces the final
90
- XML files. Existing sitemap files are left untouched if generation or validation
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
- - `public/sitemap.xml` as a sitemap index
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 `public/sitemap.xml` file:
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.public_path = -> { Rails.public_path }
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 still write normal sitemap XML files to a filesystem path; storage and
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.public_path = -> { Rails.root.join("storage/sitemaps") }
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`, `public_path`, and `format` from the main
155
- configuration unless you override them.
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
- `Indexmap.create` uses the same safe local publish flow as the rake task:
158
- generate in a temporary directory, format, validate, and then replace the final
159
- XML file or files.
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.public_path = -> { Rails.root.join("storage/sitemaps") }
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(path: Rails.public_path.join("sitemap.xml"))
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 at `public/<key>.txt`
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 writes the matching `public/<key>.txt` verification file automatically.
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 `public/<key>.txt`
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, :public_path, :sections
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 public_path
42
- value = resolve(@public_path)
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
@@ -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
- FileUtils.mkdir_p(output.public_path.dirname)
16
-
17
- Dir.mktmpdir("indexmap", output.public_path.dirname) do |dir|
18
- staging_path = Pathname(dir)
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 write_to(staging_path)
35
- output.writer.tap do |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.each do |file_path|
29
+ files.map do |file|
46
30
  document = Nokogiri::XML(
47
- file_path.read,
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
- file_path.write(document.to_xml(indent: 2, save_with: save_options))
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(index_path)
46
+ def validate(files)
59
47
  Validator.new(
60
- configuration: ValidationConfiguration.new(base_url: output.base_url),
61
- path: index_path
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
- FileUtils.mkdir_p(output.public_path)
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, :key_path, :max_urls_per_request
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 key_path(public_path:, key: self.key)
25
- configured_path = resolve(@key_path)
26
- return Pathname(configured_path) unless configured_path.to_s.strip.empty?
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
- Pathname(public_path).join("#{key}.txt")
36
+ "#{key}.txt"
30
37
  end
31
38
 
32
39
  def max_urls_per_request
@@ -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, :public_path, :sections
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 public_path
33
- value = resolve(@public_path) || configuration.public_path
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
  )
@@ -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(path: default_path, rebase_remote_children: false, index_filename: Indexmap.configuration.index_filename, public_path: Indexmap.configuration.public_path)
12
- @source = path.to_s
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
- @public_path = public_path
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, :public_path
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
- File.join(File.dirname(parent_source), File.basename(uri.path))
104
+ normalize_local_source(uri.path)
109
105
  else
110
- File.expand_path(loc, File.dirname(parent_source))
106
+ resolve_local_child_sitemap(parent_source, loc)
111
107
  end
112
108
  rescue URI::InvalidURIError
113
- File.expand_path(loc, File.dirname(parent_source))
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
- Pathname(source).expand_path.to_s
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 File.exist?(source)
150
- File.read(source, encoding: "UTF-8")
161
+ elsif storage.exist?(source)
162
+ storage.read(source)
151
163
  end
152
164
  end
153
165
 
@@ -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
- Dir.glob(configuration.public_path.join("sitemap*.xml")).sort
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 = URI.join(host, File.basename(sitemap_file)).to_s
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(path: sitemap_file).entries.each do |entry|
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, path: nil)
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
- path ||= index_now_configuration.key_path(public_path: configuration.public_path, key: key)
50
- FileUtils.mkdir_p(path.dirname)
51
- File.write(path, key)
52
- path
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, path: configuration.public_path.join("#{key}.txt"))
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| File.basename(file) == configuration.index_filename }
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(path: sitemap_file).entries.each do |entry|
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&.read
181
+ storage.read(existing_key_file) if existing_key_file
181
182
  end
182
183
 
183
184
  def existing_key_file
184
- configured_path = index_now_configuration.key_path(public_path: configuration.public_path)
185
- return configured_path if valid_key_file?(configured_path)
185
+ configured_filename = index_now_configuration.key_filename
186
+ return configured_filename if valid_key_file?(configured_filename)
186
187
 
187
- configuration.public_path.glob("*.txt").sort.find { |file| valid_key_file?(file) }
188
+ storage.list(suffix: ".txt").find { |filename| valid_key_file?(filename) }
188
189
  end
189
190
 
190
191
  def key_location(api_key:)
191
- path = index_now_configuration.key_path(public_path: configuration.public_path, key: api_key) || existing_key_file
192
- return unless path
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
- URI.join("#{host}/", relative_path.to_s).to_s
195
+ storage.public_url(filename)
199
196
  rescue ArgumentError
200
197
  nil
201
198
  end
202
199
 
203
- def valid_key_file?(path)
204
- return false unless path&.file?
200
+ def valid_key_file?(filename)
201
+ return false unless filename && storage.exist?(filename)
205
202
 
206
- filename = path.basename(".txt").to_s
207
- return false unless filename.match?(KEY_FORMAT)
203
+ key = File.basename(filename, ".txt").to_s
204
+ return false unless key.match?(KEY_FORMAT)
208
205
 
209
- path.read == filename
206
+ storage.read(filename) == key
210
207
  end
211
208
 
212
209
  def generated_key