site_maps 0.1.1 → 0.2.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/.tool-versions +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile.lock +4 -4
- data/README.md +23 -1
- data/docs/adapters.md +1 -0
- data/docs/brand/BRAND.md +65 -0
- data/docs/brand/apple-touch-icon.png +0 -0
- data/docs/brand/favicon-16.png +0 -0
- data/docs/brand/favicon-32.png +0 -0
- data/docs/brand/favicon.ico +0 -0
- data/docs/brand/favicon.svg +1 -0
- data/docs/brand/icon-512.png +0 -0
- data/docs/brand/lockup-sitemaps-mono.svg +1 -0
- data/docs/brand/lockup-sitemaps-on-dark.svg +1 -0
- data/docs/brand/lockup-sitemaps-on-light.svg +1 -0
- data/docs/brand/lockup-sitemaps.svg +1 -0
- data/docs/brand/mark-mono.svg +1 -0
- data/docs/brand/mark-on-dark.svg +1 -0
- data/docs/brand/mark-on-light.svg +1 -0
- data/docs/brand/mark-small-mono.svg +1 -0
- data/docs/brand/mark-small-on-dark.svg +1 -0
- data/docs/brand/mark-small-on-light.svg +1 -0
- data/docs/brand/mark-small.svg +1 -0
- data/docs/brand/mark.svg +1 -0
- data/docs/events.md +1 -0
- data/docs/processes.md +28 -0
- data/lib/site_maps/adapters/adapter.rb +46 -1
- data/lib/site_maps/adapters/aws_sdk/storage.rb +6 -6
- data/lib/site_maps/adapters/aws_sdk.rb +10 -0
- data/lib/site_maps/adapters/file_system.rb +9 -0
- data/lib/site_maps/adapters/noop.rb +4 -0
- data/lib/site_maps/configuration.rb +1 -0
- data/lib/site_maps/incremental_location.rb +16 -0
- data/lib/site_maps/middleware.rb +22 -1
- data/lib/site_maps/notification.rb +1 -0
- data/lib/site_maps/runner.rb +89 -10
- data/lib/site_maps/sitemap_builder.rb +6 -0
- data/lib/site_maps/version.rb +1 -1
- metadata +20 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 312638d84df202fdc438b1d93c61325a66eb2513be24846b5dce67dfe47b5737
|
|
4
|
+
data.tar.gz: 9e346f74fd71bb0f5f9187407adb934ae577acac5c71f4a2100ac650c5969f40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91ee260ef7da511dbaa184a34d05225e9293c6bf220d3b7bd2447fa69e84fe47bec7aa77f0ae96a9b4d4eeb50646df9d50c7137d44077490622b3152a1ed1c2d
|
|
7
|
+
data.tar.gz: a6d1c0ee3b32571b801ddb2f152b92f1684db588b15bc58ca6af08f0afdc66e919286b1973b352bb7012e33538f13e95e81724a4509adf4181b6ff7e7ebdd973
|
data/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby 3.
|
|
1
|
+
ruby 3.0.7
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.2.0 - 2026-11-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `index_from_storage` configuration option. The sitemap index is rebuilt from the sitemap files found in storage (filtered by the declared process locations) instead of the in-memory state of the run, so it always lists every shard that exists.
|
|
11
|
+
- The index is still written when a process fails, and the other processes keep running. The first error is re-raised afterwards.
|
|
12
|
+
- Numbered parts a successful process no longer writes (e.g. `sitemap3.xml` after a shard shrank to two files) are deleted. Files of failed processes are never touched.
|
|
13
|
+
- `Runner#rebuild_index!` rewrites the index from storage without running any process.
|
|
14
|
+
- `Adapter#list_sitemaps` for the `:file_system`, `:aws_sdk` and `:noop` adapters.
|
|
15
|
+
- `sitemaps.prune_sitemap` event.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Partial runs preloaded the existing index by fetching `config.url` over HTTP, which failed whenever the stored URL is not publicly routable (e.g. behind the middleware `storage_prefix`) and silently dropped every shard not regenerated by the run. The index is now read through the adapter storage.
|
|
19
|
+
|
|
20
|
+
## 0.1.2 - 2026-07-17
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Middleware `aliases:` option: maps an incoming public request path to another public request path (applied before the prefix options), so the same content can be served at multiple locations without a redirect — e.g. `aliases: { "/sitemap.xml" => "/sitemap_index.xml" }`. Accepts a hash or a callable returning a hash.
|
|
24
|
+
|
|
7
25
|
## 0.1.1 - 2026-05-12
|
|
8
26
|
|
|
9
27
|
### Fixed
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
site_maps (0.
|
|
4
|
+
site_maps (0.2.0)
|
|
5
5
|
builder (~> 3.0)
|
|
6
6
|
concurrent-ruby (>= 1.1)
|
|
7
7
|
rack (>= 2.0)
|
|
@@ -37,7 +37,7 @@ GEM
|
|
|
37
37
|
bigdecimal (4.1.1)
|
|
38
38
|
builder (3.3.0)
|
|
39
39
|
coderay (1.1.3)
|
|
40
|
-
concurrent-ruby (1.3.
|
|
40
|
+
concurrent-ruby (1.3.8)
|
|
41
41
|
crack (1.0.1)
|
|
42
42
|
bigdecimal
|
|
43
43
|
rexml
|
|
@@ -64,7 +64,7 @@ GEM
|
|
|
64
64
|
reline (>= 0.6.0)
|
|
65
65
|
public_suffix (7.0.5)
|
|
66
66
|
racc (1.8.1)
|
|
67
|
-
rack (3.2.
|
|
67
|
+
rack (3.2.8)
|
|
68
68
|
rainbow (3.1.1)
|
|
69
69
|
regexp_parser (2.12.0)
|
|
70
70
|
reline (0.6.3)
|
|
@@ -126,7 +126,7 @@ GEM
|
|
|
126
126
|
addressable (>= 2.8.0)
|
|
127
127
|
crack (>= 0.3.2)
|
|
128
128
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
129
|
-
zeitwerk (2.
|
|
129
|
+
zeitwerk (2.8.3)
|
|
130
130
|
|
|
131
131
|
PLATFORMS
|
|
132
132
|
x86_64-linux
|
data/README.md
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/lockup-sitemaps-on-dark.svg">
|
|
4
|
+
<img alt="SiteMaps" src="docs/brand/lockup-sitemaps-on-light.svg" width="310">
|
|
5
|
+
</picture>
|
|
6
|
+
</h1>
|
|
2
7
|
|
|
3
8
|
A concurrent, incremental sitemap generator for Ruby. Framework-agnostic with built-in Rails support.
|
|
4
9
|
|
|
@@ -109,6 +114,7 @@ SiteMaps.use(:file_system, url: "https://example.com/sitemap.xml.gz", directory:
|
|
|
109
114
|
| `xsl_index_stylesheet_url` | `nil` | URL of the XSL stylesheet for the sitemap index. |
|
|
110
115
|
| `ping_search_engines` | `false` | Ping search engines after sitemap generation. |
|
|
111
116
|
| `ping_engines` | `nil` | Custom engines hash. Defaults to Bing when `nil`. |
|
|
117
|
+
| `index_from_storage` | `false` | Build the sitemap index from the files found in storage. The index survives failed processes and lists every stored shard. See [Index generation](docs/processes.md#index-generation). |
|
|
112
118
|
|
|
113
119
|
### Gzip Compression
|
|
114
120
|
|
|
@@ -269,6 +275,7 @@ Use these when the public URL path and the storage path differ:
|
|
|
269
275
|
|---|---|---|
|
|
270
276
|
| `public_prefix:` | Public URL has an extra prefix → strip it to find the file | Stored at `/sitemap.xml`, served at `/sitemaps/tenant/sitemap.xml` |
|
|
271
277
|
| `storage_prefix:` | Storage has an extra prefix → prepend it to the public path | Stored at `/sitemaps/tenant/sitemap.xml`, served at `/sitemap.xml` |
|
|
278
|
+
| `aliases:` | Serve one public path's content at another public path (no redirect) | `/sitemap.xml` serves the content of `/sitemap_index.xml` |
|
|
272
279
|
|
|
273
280
|
```ruby
|
|
274
281
|
# Sitemaps stored at /sitemaps/{slug}/sitemap.xml, served at /sitemap.xml
|
|
@@ -283,6 +290,20 @@ Rails.application.middleware.insert_after MultitenancyMiddleware, SiteMaps::Midd
|
|
|
283
290
|
adapter: -> { ... }
|
|
284
291
|
```
|
|
285
292
|
|
|
293
|
+
`aliases:` maps an incoming public request path to another public request path,
|
|
294
|
+
applied before the prefix options. Use it to serve the same content at multiple
|
|
295
|
+
locations — for example, to answer the conventional `/sitemap.xml` with the
|
|
296
|
+
sitemap index that is stored/served as `/sitemap_index.xml`:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
Rails.application.middleware.insert_after MultitenancyMiddleware, SiteMaps::Middleware,
|
|
300
|
+
aliases: { "/sitemap.xml" => "/sitemap_index.xml" },
|
|
301
|
+
storage_prefix: -> { site = Current.site; "/sitemaps/#{site.slug}" if site },
|
|
302
|
+
adapter: -> { ... }
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
It accepts a hash or a callable (0-arg or 1-arg receiving `env`) returning a hash.
|
|
306
|
+
|
|
286
307
|
XSL stylesheet requests (`/_sitemap-stylesheet.xsl`, `/_sitemap-index-stylesheet.xsl`) are served directly without resolving the adapter or prefix.
|
|
287
308
|
|
|
288
309
|
### Thread safety
|
|
@@ -536,6 +557,7 @@ use SiteMaps::Middleware,
|
|
|
536
557
|
adapter: SiteMaps.current_adapter, # defaults to SiteMaps.current_adapter
|
|
537
558
|
public_prefix: nil, # strip this prefix from the public URL before lookup
|
|
538
559
|
storage_prefix: nil, # prepend this prefix to the public URL for storage lookup
|
|
560
|
+
aliases: nil, # map a public path to another public path, e.g. { "/sitemap.xml" => "/sitemap_index.xml" }
|
|
539
561
|
x_robots_tag: "noindex, follow", # default
|
|
540
562
|
cache_control: "public, max-age=3600" # default
|
|
541
563
|
```
|
data/docs/adapters.md
CHANGED
|
@@ -138,6 +138,7 @@ end
|
|
|
138
138
|
| `#write(url, raw_data, **kwargs)` | Persist `raw_data` at the location implied by `url`. |
|
|
139
139
|
| `#read(url)` | Return `[raw_data, { content_type: '…' }]` for the given URL. |
|
|
140
140
|
| `#delete(url)` | Remove the file at the URL. |
|
|
141
|
+
| `#list_sitemaps` | (optional) Return a `SiteMaps::Builder::SitemapIndex::Item` for every stored sitemap file. Required by `index_from_storage`. |
|
|
141
142
|
| `.config_class` | (optional) Return a `Configuration` subclass to expose adapter-specific settings. |
|
|
142
143
|
|
|
143
144
|
The adapter base class handles everything else: URL filters, the process registry, and thread-safe URL tracking.
|
data/docs/brand/BRAND.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# SiteMaps brand kit
|
|
2
|
+
|
|
3
|
+
## The mark
|
|
4
|
+
|
|
5
|
+
A map pin drawn as one route that is also a pair of angle brackets. The `<` and `>` sides meet at the pin's point, and the gap at the top is where the tag opens. The green dot is the URL it holds. From a distance it reads as a pin; up close, as XML.
|
|
6
|
+
|
|
7
|
+
## Colours
|
|
8
|
+
|
|
9
|
+
| Role | Light background | Dark background |
|
|
10
|
+
|-------|------------------|-----------------|
|
|
11
|
+
| Ink | `#1a2233` | `#e3e9f3` |
|
|
12
|
+
| Green | `#1f9d6b` | `#1f9d6b` |
|
|
13
|
+
| Tile (app icons) | `#1a2233` | `#1a2233` |
|
|
14
|
+
|
|
15
|
+
Green is reserved for the dot. The ink and line weight match the Lepus kit, so the libraries read as one family.
|
|
16
|
+
|
|
17
|
+
## Clear space and minimum size
|
|
18
|
+
|
|
19
|
+
- Clear space: keep at least a quarter of the mark's height empty on every side.
|
|
20
|
+
- `mark*.svg`: 48 px and up.
|
|
21
|
+
- `mark-small*.svg` / `favicon.svg`: 16 to 48 px. Its line is heavier so it stays sharp at favicon sizes. Do not use it large.
|
|
22
|
+
- `lockup-sitemaps*`: 120 px wide minimum.
|
|
23
|
+
|
|
24
|
+
## Which file where
|
|
25
|
+
|
|
26
|
+
| Use | File |
|
|
27
|
+
|-----|------|
|
|
28
|
+
| README header | `lockup-sitemaps-on-light.svg` + `lockup-sitemaps-on-dark.svg` in a `<picture>` (below) |
|
|
29
|
+
| Web page header that follows the OS theme | `lockup-sitemaps.svg`, `mark.svg` (ink switches with `prefers-color-scheme`) |
|
|
30
|
+
| Inline in an app that sets its own text colour | `*-mono.svg` (everything is `currentColor`) |
|
|
31
|
+
| Fixed background you control | `*-on-light.svg` / `*-on-dark.svg` |
|
|
32
|
+
| Browser tab | `favicon.svg` (adaptive), with `favicon.ico` (16/32/48) as fallback |
|
|
33
|
+
| iOS home screen | `apple-touch-icon.png` (180, solid tile) |
|
|
34
|
+
| Web manifest / PWA | `icon-512.png` (solid tile, mark inside the maskable safe zone) |
|
|
35
|
+
| Legacy PNG favicons | `favicon-16.png`, `favicon-32.png` (transparent, light-background ink) |
|
|
36
|
+
|
|
37
|
+
All wordmark text is outlined Overpass (SIL Open Font License), so no font is needed to render it.
|
|
38
|
+
|
|
39
|
+
### README
|
|
40
|
+
|
|
41
|
+
GitHub follows the viewer's GitHub theme only through `<picture>`, so use the light/dark pair rather than the adaptive file:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<h1 align="center">
|
|
45
|
+
<picture>
|
|
46
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/lockup-sitemaps-on-dark.svg">
|
|
47
|
+
<img alt="SiteMaps" src="docs/brand/lockup-sitemaps-on-light.svg" width="310">
|
|
48
|
+
</picture>
|
|
49
|
+
</h1>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### HTML head
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<link rel="icon" href="/favicon.ico" sizes="48x48">
|
|
56
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
57
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Don'ts
|
|
61
|
+
|
|
62
|
+
- Don't redraw the wordmark in live text or another typeface.
|
|
63
|
+
- Don't stretch, rotate, outline or add effects to the mark.
|
|
64
|
+
- Don't put the ink mark on mid-tone or busy backgrounds; use the tile or the mono file instead.
|
|
65
|
+
- Don't recolour the line in the accent or put the dot in ink (except in the mono files).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><path class="i" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><g transform="translate(-11.79 -61.50) scale(1.2414)"><path stroke="currentColor" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="currentColor"/></g><path fill="currentColor" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><g transform="translate(-11.79 -61.50) scale(1.2414)"><path stroke="#e3e9f3" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></g><path fill="#e3e9f3" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><g transform="translate(-11.79 -61.50) scale(1.2414)"><path stroke="#1a2233" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></g><path fill="#1a2233" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><g transform="translate(-11.79 -61.50) scale(1.2414)"><path class="i" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></g><path class="f" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="currentColor" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="currentColor"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#e3e9f3" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#1a2233" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="currentColor" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="currentColor"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#e3e9f3" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#1a2233" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><path class="i" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
|
data/docs/brand/mark.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><path class="i" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></svg>
|
data/docs/events.md
CHANGED
|
@@ -57,6 +57,7 @@ This is subscribed automatically by the CLI.
|
|
|
57
57
|
| `sitemaps.process_execution` | `process`, `kwargs`, `runtime` |
|
|
58
58
|
| `sitemaps.finalize_urlset` | `url`, `links_count`, `news_count`, `last_modified`, `runtime`, `process` |
|
|
59
59
|
| `sitemaps.ping` | `results` |
|
|
60
|
+
| `sitemaps.prune_sitemap` | `url`, `runtime` |
|
|
60
61
|
|
|
61
62
|
`process` is a `SiteMaps::Process` struct (`name`, `location_template`, `kwargs_template`, `block`).
|
|
62
63
|
|
data/docs/processes.md
CHANGED
|
@@ -63,6 +63,8 @@ When you call `runner.run`:
|
|
|
63
63
|
3. Each process builds a `URLSet`. When the set fills up (50,000 links, 1,000 news items, or 50 MB uncompressed), it's finalized and written, and a new URLSet starts — automatically.
|
|
64
64
|
4. After every process finishes, the sitemap index is aggregated and written to `config.url`.
|
|
65
65
|
|
|
66
|
+
By default the first failing process stops the run and the index is left untouched. With `index_from_storage` the remaining processes still run, the index is written, and the first error is re-raised afterwards.
|
|
67
|
+
|
|
66
68
|
## Splitting rules
|
|
67
69
|
|
|
68
70
|
A URL set is finalized and rolled over when **any** of these apply:
|
|
@@ -83,6 +85,32 @@ A sitemap index is produced when:
|
|
|
83
85
|
|
|
84
86
|
Otherwise a single `urlset` is written directly at `config.url` (the "inline" optimization).
|
|
85
87
|
|
|
88
|
+
On a partial run the existing index is read back through the adapter, and the entries of shards that were not regenerated are kept.
|
|
89
|
+
|
|
90
|
+
### Building the index from storage
|
|
91
|
+
|
|
92
|
+
Set `index_from_storage` when the index must always reflect what is stored, no matter which processes ran or failed:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
SiteMaps.use(:aws_sdk) do
|
|
96
|
+
configure do |config|
|
|
97
|
+
config.url = "https://example.com/sitemap.xml"
|
|
98
|
+
config.index_from_storage = true
|
|
99
|
+
end
|
|
100
|
+
# ...
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The index then lists every stored file that matches a declared process location, plus the external sitemaps. A successful process deletes the numbered parts it no longer writes. Files of failed processes are left as they were.
|
|
105
|
+
|
|
106
|
+
To rebuild a lost or stale index without querying any data source:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
SiteMaps.generate(config_file: "config/sitemap.rb").rebuild_index!
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Adapters need `#list_sitemaps` for this option (built in for `:file_system`, `:aws_sdk` and `:noop`).
|
|
113
|
+
|
|
86
114
|
## Adding external sitemaps
|
|
87
115
|
|
|
88
116
|
Reference third-party or pre-existing sitemaps in the index:
|
|
@@ -12,7 +12,11 @@ module SiteMaps::Adapters
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
SITEMAP_ENTRY_RE = %r{<sitemap>(.*?)</sitemap>}m
|
|
16
|
+
LOC_RE = %r{<loc>\s*(.*?)\s*</loc>}m
|
|
17
|
+
LASTMOD_RE = %r{<lastmod>\s*(.*?)\s*</lastmod>}m
|
|
18
|
+
XML_ENTITIES = {"&" => "&", "<" => "<", ">" => ">", """ => '"', "'" => "'"}.freeze
|
|
19
|
+
|
|
16
20
|
attr_reader :sitemap_index, :processes, :process_mixins
|
|
17
21
|
|
|
18
22
|
def initialize(**options, &block)
|
|
@@ -48,6 +52,28 @@ module SiteMaps::Adapters
|
|
|
48
52
|
raise NotImplementedError
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
# @abstract
|
|
56
|
+
# @return [Array<SiteMaps::Builder::SitemapIndex::Item>] every sitemap file stored under the sitemap directory
|
|
57
|
+
# @raise [NotImplementedError] if the adapter can not list its storage
|
|
58
|
+
def list_sitemaps
|
|
59
|
+
raise NotImplementedError, "#{self.class} does not support listing stored sitemaps"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Reads the current sitemap index straight from the adapter storage.
|
|
63
|
+
# Adapters without a #read implementation fall back to fetching the index URL.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<SiteMaps::Builder::SitemapIndex::Item>]
|
|
66
|
+
def fetch_sitemap_index_links
|
|
67
|
+
raw_data, metadata = read(config.url)
|
|
68
|
+
return [] if raw_data.nil?
|
|
69
|
+
|
|
70
|
+
parse_sitemap_index(decompress(raw_data, metadata))
|
|
71
|
+
rescue SiteMaps::FileNotFoundError
|
|
72
|
+
[]
|
|
73
|
+
rescue NotImplementedError
|
|
74
|
+
config.fetch_sitemap_index_links
|
|
75
|
+
end
|
|
76
|
+
|
|
51
77
|
def config
|
|
52
78
|
yield(@config) if block_given?
|
|
53
79
|
@config
|
|
@@ -106,5 +132,24 @@ module SiteMaps::Adapters
|
|
|
106
132
|
@sitemap_index = SiteMaps::Builder::SitemapIndex.new(xsl_url: xsl_url)
|
|
107
133
|
@repo = nil
|
|
108
134
|
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def parse_sitemap_index(xml)
|
|
139
|
+
xml.scan(SITEMAP_ENTRY_RE).filter_map do |(entry)|
|
|
140
|
+
loc = entry[LOC_RE, 1]
|
|
141
|
+
next if loc.nil? || loc.empty?
|
|
142
|
+
|
|
143
|
+
SiteMaps::Builder::SitemapIndex::Item.new(loc.gsub(/&(?:amp|lt|gt|quot|apos);/, XML_ENTITIES), entry[LASTMOD_RE, 1])
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def decompress(raw_data, metadata)
|
|
148
|
+
return raw_data unless metadata.is_a?(Hash) && metadata[:content_type] == "application/gzip"
|
|
149
|
+
|
|
150
|
+
Zlib::GzipReader.new(StringIO.new(raw_data)).read
|
|
151
|
+
rescue Zlib::GzipFile::Error
|
|
152
|
+
raw_data
|
|
153
|
+
end
|
|
109
154
|
end
|
|
110
155
|
end
|
|
@@ -37,14 +37,14 @@ class SiteMaps::Adapters::AwsSdk::Storage
|
|
|
37
37
|
raise SiteMaps::FileNotFoundError, "File not found: #{location.remote_path}"
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def
|
|
43
|
-
config.s3_bucket.objects(
|
|
44
|
-
prefix: prefix
|
|
45
|
-
)
|
|
40
|
+
# @param [String] prefix
|
|
41
|
+
# @return [Array<Array(String, Time)>] the key and last modified time of every object under the prefix
|
|
42
|
+
def list(prefix)
|
|
43
|
+
config.s3_bucket.objects(prefix: prefix).map { |object| [object.key, object.last_modified] }
|
|
46
44
|
end
|
|
47
45
|
|
|
46
|
+
private
|
|
47
|
+
|
|
48
48
|
def object(remote_path)
|
|
49
49
|
config.s3_bucket.object(remote_path)
|
|
50
50
|
end
|
|
@@ -18,6 +18,16 @@ module SiteMaps::Adapters
|
|
|
18
18
|
s3_storage.delete(location)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
def list_sitemaps
|
|
22
|
+
prefix = config.remote_sitemap_directory
|
|
23
|
+
prefix = "#{prefix}/" unless prefix.empty?
|
|
24
|
+
s3_storage.list(prefix).filter_map do |key, last_modified|
|
|
25
|
+
next unless key.end_with?(".xml", ".xml.gz")
|
|
26
|
+
|
|
27
|
+
SiteMaps::Builder::SitemapIndex::Item.new("#{config.base_uri}/#{key}", last_modified)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
21
31
|
private
|
|
22
32
|
|
|
23
33
|
def local_storage
|
|
@@ -17,6 +17,15 @@ module SiteMaps::Adapters
|
|
|
17
17
|
storage.delete(location)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def list_sitemaps
|
|
21
|
+
root = Pathname.new(config.directory)
|
|
22
|
+
pattern = root.join(config.remote_sitemap_directory, "**", "*.{xml,xml.gz}").to_s
|
|
23
|
+
Dir.glob(pattern).map do |path|
|
|
24
|
+
relative = Pathname.new(path).relative_path_from(root).to_s
|
|
25
|
+
SiteMaps::Builder::SitemapIndex::Item.new("#{config.base_uri}/#{relative}", File.mtime(path))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
20
29
|
private
|
|
21
30
|
|
|
22
31
|
def storage
|
|
@@ -44,6 +44,7 @@ module SiteMaps
|
|
|
44
44
|
attribute :xsl_index_stylesheet_url
|
|
45
45
|
attribute :ping_search_engines, default: false
|
|
46
46
|
attribute :ping_engines
|
|
47
|
+
attribute :index_from_storage, default: false
|
|
47
48
|
|
|
48
49
|
def initialize(**options)
|
|
49
50
|
default_attributes.merge(options).each do |key, value|
|
|
@@ -4,6 +4,14 @@ module SiteMaps
|
|
|
4
4
|
class IncrementalLocation
|
|
5
5
|
FILENAME = "sitemap.xml"
|
|
6
6
|
PLACEHOLDER = "%{index}"
|
|
7
|
+
ANY_SEGMENT = "__site_maps_any_segment__"
|
|
8
|
+
TEMPLATE_KEY_RE = /%\{\w+\}/
|
|
9
|
+
|
|
10
|
+
# Matches every file a process location template may produce, whatever its
|
|
11
|
+
# keyword arguments and part number.
|
|
12
|
+
def self.template_pattern(main_url, location_template)
|
|
13
|
+
new(main_url, location_template&.gsub(TEMPLATE_KEY_RE, ANY_SEGMENT)).pattern
|
|
14
|
+
end
|
|
7
15
|
|
|
8
16
|
def initialize(main_url, process_location)
|
|
9
17
|
@main_uri = URI(main_url)
|
|
@@ -24,6 +32,14 @@ module SiteMaps
|
|
|
24
32
|
main_uri.to_s
|
|
25
33
|
end
|
|
26
34
|
|
|
35
|
+
# @return [Regexp] matches every numbered part of this location
|
|
36
|
+
def pattern
|
|
37
|
+
source = Regexp.escape(placeholder_url)
|
|
38
|
+
.sub(Regexp.escape(PLACEHOLDER)) { "\\d+" }
|
|
39
|
+
.gsub(ANY_SEGMENT) { "[^/]+" }
|
|
40
|
+
Regexp.new("\\A#{source}\\z")
|
|
41
|
+
end
|
|
42
|
+
|
|
27
43
|
def relative_directory
|
|
28
44
|
File.dirname(@uri.path).sub(%r{^/}, "")
|
|
29
45
|
end
|
data/lib/site_maps/middleware.rb
CHANGED
|
@@ -28,11 +28,21 @@ module SiteMaps
|
|
|
28
28
|
# Both options accept a callable (0-arg or 1-arg receiving env), which is useful
|
|
29
29
|
# in multi-tenant setups where the prefix depends on the current request/site.
|
|
30
30
|
#
|
|
31
|
+
# @param aliases [Hash{String=>String}, #call, nil] Maps an incoming **public
|
|
32
|
+
# request path** to another public request path, applied before prefix
|
|
33
|
+
# handling. Serves the target's content at the alias path (no redirect).
|
|
34
|
+
#
|
|
35
|
+
# Example: serve the sitemap index at both locations →
|
|
36
|
+
# `aliases: { "/sitemap.xml" => "/sitemap_index.xml" }`
|
|
37
|
+
#
|
|
38
|
+
# Accepts a callable (0-arg or 1-arg receiving env) returning such a hash.
|
|
39
|
+
#
|
|
31
40
|
def initialize(
|
|
32
41
|
app,
|
|
33
42
|
adapter: nil,
|
|
34
43
|
public_prefix: nil,
|
|
35
44
|
storage_prefix: nil,
|
|
45
|
+
aliases: nil,
|
|
36
46
|
x_robots_tag: DEFAULT_X_ROBOTS_TAG,
|
|
37
47
|
cache_control: DEFAULT_CACHE_CONTROL
|
|
38
48
|
)
|
|
@@ -40,12 +50,13 @@ module SiteMaps
|
|
|
40
50
|
@adapter = adapter
|
|
41
51
|
@public_prefix = public_prefix
|
|
42
52
|
@storage_prefix = storage_prefix
|
|
53
|
+
@aliases = aliases
|
|
43
54
|
@x_robots_tag = x_robots_tag
|
|
44
55
|
@cache_control = cache_control
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
def call(env)
|
|
48
|
-
path = env["PATH_INFO"]
|
|
59
|
+
path = resolve_alias(env["PATH_INFO"], env)
|
|
49
60
|
|
|
50
61
|
if xsl_request?(path)
|
|
51
62
|
serve_xsl(path)
|
|
@@ -74,6 +85,16 @@ module SiteMaps
|
|
|
74
85
|
|
|
75
86
|
private
|
|
76
87
|
|
|
88
|
+
# Rewrites the incoming public path through the alias map (if configured)
|
|
89
|
+
# before any prefix handling, so the aliased path takes the exact same code
|
|
90
|
+
# path as a real request for the target.
|
|
91
|
+
def resolve_alias(path, env)
|
|
92
|
+
aliases = @aliases.respond_to?(:call) ? call_with_env(@aliases, env) : @aliases
|
|
93
|
+
return path unless aliases
|
|
94
|
+
|
|
95
|
+
aliases[path] || path
|
|
96
|
+
end
|
|
97
|
+
|
|
77
98
|
def resolve_adapter(env)
|
|
78
99
|
if @adapter.respond_to?(:call)
|
|
79
100
|
call_with_env(@adapter, env)
|
data/lib/site_maps/runner.rb
CHANGED
|
@@ -11,6 +11,7 @@ module SiteMaps
|
|
|
11
11
|
@execution = Concurrent::Hash.new
|
|
12
12
|
@failed = Concurrent::AtomicBoolean.new(false)
|
|
13
13
|
@errors = Concurrent::Array.new
|
|
14
|
+
@written = Concurrent::Array.new
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def enqueue(process_name = :default, **kwargs)
|
|
@@ -68,6 +69,7 @@ module SiteMaps
|
|
|
68
69
|
adapter.process_mixins.each { |mixin| builder.extend(mixin) }
|
|
69
70
|
process.call(builder, **kwargs)
|
|
70
71
|
builder.finalize!
|
|
72
|
+
written << [process.location(**kwargs), builder.written_urls]
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
end
|
|
@@ -75,30 +77,104 @@ module SiteMaps
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
futures.each(&:wait)
|
|
78
|
-
failed.false?
|
|
80
|
+
if failed.false?
|
|
81
|
+
finalize!
|
|
82
|
+
else
|
|
83
|
+
write_index! if index_from_storage?
|
|
84
|
+
fail_with_errors!
|
|
85
|
+
end
|
|
79
86
|
ensure
|
|
80
87
|
pool.shutdown
|
|
81
88
|
pool.wait_for_termination
|
|
82
89
|
@execution.clear
|
|
90
|
+
@written.clear
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Rewrites the sitemap index from the sitemap files found in storage,
|
|
94
|
+
# without running any process.
|
|
95
|
+
def rebuild_index!
|
|
96
|
+
add_stored_index_links(adapter.list_sitemaps)
|
|
97
|
+
add_external_index_links
|
|
98
|
+
write_sitemap_index
|
|
99
|
+
self
|
|
83
100
|
end
|
|
84
101
|
|
|
85
102
|
private
|
|
86
103
|
|
|
87
|
-
attr_reader :pool, :failed, :errors
|
|
104
|
+
attr_reader :pool, :failed, :errors, :written
|
|
88
105
|
|
|
89
106
|
def finalize!
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
write_index!
|
|
108
|
+
should_ping = @ping.nil? ? adapter.config.respond_to?(:ping_search_engines?) && adapter.config.ping_search_engines? : @ping
|
|
109
|
+
ping_search_engines if should_ping
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def write_index!
|
|
113
|
+
if index_from_storage?
|
|
114
|
+
add_stored_index_links(prune_stale_sitemaps(adapter.list_sitemaps))
|
|
115
|
+
else
|
|
116
|
+
adapter.repo.remaining_index_links.each do |item|
|
|
117
|
+
adapter.sitemap_index.add(item)
|
|
118
|
+
end
|
|
92
119
|
end
|
|
120
|
+
add_external_index_links
|
|
121
|
+
write_sitemap_index
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def write_sitemap_index
|
|
125
|
+
return if adapter.sitemap_index.empty?
|
|
126
|
+
|
|
127
|
+
raw_data = adapter.sitemap_index.to_xml
|
|
128
|
+
adapter.write(adapter.config.url, raw_data, last_modified: adapter.sitemap_index.last_modified)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def add_external_index_links
|
|
93
132
|
adapter.external_sitemaps.each do |item|
|
|
94
133
|
adapter.sitemap_index.add(item)
|
|
95
134
|
end
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Adds the stored files that belong to a declared process. Files written by
|
|
138
|
+
# this run are already in the index, and the index keeps the first entry
|
|
139
|
+
# added for each location.
|
|
140
|
+
def add_stored_index_links(stored)
|
|
141
|
+
patterns = adapter.processes.values.map do |process|
|
|
142
|
+
SiteMaps::IncrementalLocation.template_pattern(adapter.config.url, process.location_template)
|
|
99
143
|
end
|
|
100
|
-
|
|
101
|
-
|
|
144
|
+
|
|
145
|
+
stored.each do |item|
|
|
146
|
+
next if item.loc == adapter.config.url
|
|
147
|
+
next unless patterns.any? { |pattern| pattern.match?(item.loc) }
|
|
148
|
+
|
|
149
|
+
adapter.sitemap_index.add(item)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Deletes the numbered parts a successful process no longer writes, such as
|
|
154
|
+
# sitemap3.xml after a month shrank to two files. Returns the stored files
|
|
155
|
+
# that are left.
|
|
156
|
+
def prune_stale_sitemaps(stored)
|
|
157
|
+
stale = written.flat_map do |location, urls|
|
|
158
|
+
next [] if urls.empty?
|
|
159
|
+
|
|
160
|
+
pattern = SiteMaps::IncrementalLocation.new(adapter.config.url, location).pattern
|
|
161
|
+
stored.select { |item| pattern.match?(item.loc) && !urls.include?(item.loc) && item.loc != adapter.config.url }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
stale.each do |item|
|
|
165
|
+
SiteMaps::Notification.instrument("sitemaps.prune_sitemap") do |payload|
|
|
166
|
+
payload[:url] = item.loc
|
|
167
|
+
adapter.delete(item.loc)
|
|
168
|
+
rescue SiteMaps::FileNotFoundError
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
stored - stale
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def index_from_storage?
|
|
177
|
+
adapter.config.respond_to?(:index_from_storage?) && adapter.config.index_from_storage?
|
|
102
178
|
end
|
|
103
179
|
|
|
104
180
|
def ping_search_engines
|
|
@@ -119,6 +195,7 @@ module SiteMaps
|
|
|
119
195
|
end
|
|
120
196
|
|
|
121
197
|
def may_preload_sitemap_index
|
|
198
|
+
return if index_from_storage?
|
|
122
199
|
return unless preload_sitemap_index_links?
|
|
123
200
|
|
|
124
201
|
adapter.fetch_sitemap_index_links.each do |item|
|
|
@@ -126,8 +203,10 @@ module SiteMaps
|
|
|
126
203
|
end
|
|
127
204
|
end
|
|
128
205
|
|
|
206
|
+
# A failure stops the remaining processes, unless the index is rebuilt from
|
|
207
|
+
# storage: then every independent process still gets its chance to publish.
|
|
129
208
|
def wrap_process_execution(process)
|
|
130
|
-
return if failed.true?
|
|
209
|
+
return if failed.true? && !index_from_storage?
|
|
131
210
|
|
|
132
211
|
yield
|
|
133
212
|
rescue => e
|
|
@@ -4,8 +4,11 @@ module SiteMaps
|
|
|
4
4
|
class SitemapBuilder
|
|
5
5
|
extend Forwardable
|
|
6
6
|
|
|
7
|
+
attr_reader :written_urls
|
|
8
|
+
|
|
7
9
|
def initialize(adapter:, location: nil, notification_payload: {})
|
|
8
10
|
@adapter = adapter
|
|
11
|
+
@written_urls = []
|
|
9
12
|
@url_set = build_url_set
|
|
10
13
|
@location = location
|
|
11
14
|
@mutex = Mutex.new
|
|
@@ -43,10 +46,12 @@ module SiteMaps
|
|
|
43
46
|
if adapter.maybe_inline_urlset? && sitemap_index.empty?
|
|
44
47
|
payload[:url] = repo.main_url
|
|
45
48
|
adapter.write(repo.main_url, raw_data, last_modified: url_set.last_modified)
|
|
49
|
+
written_urls << repo.main_url
|
|
46
50
|
else
|
|
47
51
|
sitemap_url = repo.generate_url(location)
|
|
48
52
|
payload[:url] = sitemap_url
|
|
49
53
|
adapter.write(sitemap_url, raw_data, last_modified: url_set.last_modified)
|
|
54
|
+
written_urls << sitemap_url
|
|
50
55
|
add_sitemap_index(sitemap_url, lastmod: url_set.last_modified)
|
|
51
56
|
end
|
|
52
57
|
end
|
|
@@ -67,6 +72,7 @@ module SiteMaps
|
|
|
67
72
|
payload[:news_count] = url_set.news_count
|
|
68
73
|
payload[:last_modified] = url_set.last_modified
|
|
69
74
|
adapter.write(sitemap_url, raw_data, last_modified: url_set.last_modified)
|
|
75
|
+
written_urls << sitemap_url
|
|
70
76
|
add_sitemap_index(sitemap_url, lastmod: url_set.last_modified)
|
|
71
77
|
end
|
|
72
78
|
@url_set = build_url_set
|
data/lib/site_maps/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: site_maps
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marcos G. Zimmermann
|
|
@@ -108,6 +108,25 @@ files:
|
|
|
108
108
|
- docs/README.md
|
|
109
109
|
- docs/adapters.md
|
|
110
110
|
- docs/api.md
|
|
111
|
+
- docs/brand/BRAND.md
|
|
112
|
+
- docs/brand/apple-touch-icon.png
|
|
113
|
+
- docs/brand/favicon-16.png
|
|
114
|
+
- docs/brand/favicon-32.png
|
|
115
|
+
- docs/brand/favicon.ico
|
|
116
|
+
- docs/brand/favicon.svg
|
|
117
|
+
- docs/brand/icon-512.png
|
|
118
|
+
- docs/brand/lockup-sitemaps-mono.svg
|
|
119
|
+
- docs/brand/lockup-sitemaps-on-dark.svg
|
|
120
|
+
- docs/brand/lockup-sitemaps-on-light.svg
|
|
121
|
+
- docs/brand/lockup-sitemaps.svg
|
|
122
|
+
- docs/brand/mark-mono.svg
|
|
123
|
+
- docs/brand/mark-on-dark.svg
|
|
124
|
+
- docs/brand/mark-on-light.svg
|
|
125
|
+
- docs/brand/mark-small-mono.svg
|
|
126
|
+
- docs/brand/mark-small-on-dark.svg
|
|
127
|
+
- docs/brand/mark-small-on-light.svg
|
|
128
|
+
- docs/brand/mark-small.svg
|
|
129
|
+
- docs/brand/mark.svg
|
|
111
130
|
- docs/cli.md
|
|
112
131
|
- docs/events.md
|
|
113
132
|
- docs/extensions.md
|