Skip to content

Commit 761aeb8

Browse files
authored
CXX-3097 add patch-apidocs-current-redirects.py (#1238)
* hugo: change API Documentation logo to fa-book * hugo: disable generating categories/ and tags/ * hugo: avoid overwriting sitemap.xml during hugo deploy * hugo: avoid double-slashes in Hugo generated URLs * doxygen: patch latest API doc pages with redirect from /api/current * doxygen: avoid leaking local directory information in Doxygen pages * Update release instructions to include sitemap index updates
1 parent 265d9b1 commit 761aeb8

File tree

6 files changed

+217
-10
lines changed

6 files changed

+217
-10
lines changed

Doxyfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ FULL_PATH_NAMES = YES
180180
# will be relative from the directory where doxygen is started.
181181
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
182182

183-
STRIP_FROM_PATH = src/bsoncxx/include \
183+
STRIP_FROM_PATH = . \
184+
src/bsoncxx/include \
184185
src/mongocxx/include
185186

186187
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
@@ -190,7 +191,8 @@ STRIP_FROM_PATH = src/bsoncxx/include \
190191
# specify the list of include paths that are normally passed to the compiler
191192
# using the -I flag.
192193

193-
STRIP_FROM_INC_PATH = src/bsoncxx/include \
194+
STRIP_FROM_INC_PATH = . \
195+
src/bsoncxx/include \
194196
src/mongocxx/include
195197

196198
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but

docs/config.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ theme = "mongodb"
55
canonifyurls = false
66
relativeurls = false
77
publishdir = "../build/hugo"
8+
disableKinds = ["taxonomy", "term"]
89

910
[blackfriday]
1011
plainIdAnchors = true
@@ -18,7 +19,7 @@ publishdir = "../build/hugo"
1819

1920
[[menu.main]]
2021
name = "API Documentation"
21-
pre = "<i class='fa fa-file-text-o'></i>"
22+
pre = "<i class='fa fa-book'></i>"
2223
weight = 90
2324
identifier = "apiDocs"
2425
url = "https://mongocxx.org/api/current"

docs/themes/mongodb/theme.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name = "MongoDB"
22
license = "Creative Commons Attribution NonCommercial ShareAlike 3.0 Unported"
33
licenselink = "http://creativecommons.org/licenses/by-nc-sa/3.0/"
44
description = "A standalone mongodb docs theme, for individual driver homepages"
5-
homepage = "https://mongocxx.org/"
5+
homepage = "https://mongocxx.org"
66

77
[author]
88
name = "MongoDB C++ driver authors"
9-
homepage = "https://mongocxx.org/"
9+
homepage = "https://mongocxx.org"
1010

1111
[original]
1212
name = "MongoDB Docs"

etc/deploy-to-ghpages.pl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ sub _pushd {
2929

3030
sub _hugo_rsync {
3131
my $tmpdir = shift;
32-
_try_run( qw{rsync -Cavz --delete --exclude=/api --exclude=/.git* --exclude=CNAME build/hugo/},
32+
_try_run( qw{rsync -Cavz --delete --exclude=/api --exclude=/.git* --exclude=CNAME --exclude=sitemap.xml build/hugo/},
3333
$tmpdir );
3434
}
3535

@@ -42,7 +42,8 @@ sub _doxygen_rsync {
4242
"build/docs/api/", "$tmpdir/api/"
4343
);
4444
$ENV{APIDOCSPATH} = "$tmpdir/api";
45-
_try_run(qw{etc/patch-apidocs-index-pages.py})
45+
_try_run(qw{etc/patch-apidocs-index-pages.py});
46+
_try_run(qw{etc/patch-apidocs-current-redirects.py});
4647
}
4748

4849
sub main {
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright 2009-present MongoDB, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
"""
18+
Patches HTML files within the latest API doc directory (under APIDOCSPATH) to
19+
redirect users from `/api/current` to canonical URLs under `/api/mongocxx-X.Y.Z`.
20+
"""
21+
22+
from concurrent.futures import ProcessPoolExecutor
23+
from packaging.version import Version, InvalidVersion
24+
from pathlib import Path
25+
from typing import List, Tuple
26+
27+
import re
28+
import os
29+
30+
31+
def find_api_docs_path() -> str:
32+
"""
33+
Return an absolute path to the directory containing the API docs.
34+
"""
35+
api_docs_path: str | None = os.environ.get('APIDOCSPATH')
36+
if not api_docs_path:
37+
raise RuntimeError('APIDOCSPATH environment variable is not set!')
38+
39+
if not os.path.exists(api_docs_path):
40+
raise RuntimeError('path to API docs does not exist!')
41+
42+
return os.path.abspath(api_docs_path)
43+
44+
45+
def find_api_docs(api_docs_path: str) -> List[str]:
46+
"""
47+
Return a list of API doc directories by name.
48+
"""
49+
api_docs: List[str] = []
50+
for dir in os.scandir(api_docs_path):
51+
if dir.is_dir() and not dir.is_symlink():
52+
api_docs.append(dir.name)
53+
54+
# Sort by legacy vs. modern, then by SemVer. Example:
55+
# - legacy-0.1.0
56+
# - legacy-0.2.0
57+
# - legacy-0.10.0
58+
# - mongocxx-3.1.0
59+
# - mongocxx-3.2.0
60+
# - mongocxx-3.10.0
61+
# Skip directories with a version suffix, e.g. `mongocxx-1.2.3-rc0`.
62+
def by_version(p: str) -> Tuple[bool, Version] | None:
63+
is_legacy: bool = p.startswith('legacy-')
64+
try:
65+
version = p.removeprefix('legacy-') if is_legacy else p.removeprefix('mongocxx-')
66+
if version.find('-') != -1:
67+
print(f' - Skipping: {p}')
68+
return None
69+
return (not is_legacy, Version(version))
70+
except InvalidVersion:
71+
raise RuntimeError(f'unexpected API doc name "{p}": APIDOCSPATH may not be correct!') from None
72+
73+
api_docs = [doc for doc in api_docs if by_version(doc) is not None]
74+
api_docs.sort(key=by_version)
75+
76+
return api_docs
77+
78+
79+
def patch_redirect_current_pages(apidocspath, latest):
80+
"""
81+
Patch all HTML files under the latest API doc directory.
82+
"""
83+
84+
pages: List[Path] = []
85+
86+
for (dirpath, _, filenames) in os.walk(os.path.join(apidocspath, latest)):
87+
for filename in filenames:
88+
page = Path(os.path.join(dirpath, filename))
89+
if page.suffix == '.html':
90+
pages.append(page)
91+
92+
futures = []
93+
94+
with ProcessPoolExecutor() as executor:
95+
for page in pages:
96+
futures.append(executor.submit(insert_current_redirect, apidocspath, page, latest))
97+
98+
for future in futures:
99+
future.result()
100+
101+
102+
def insert_current_redirect(apidocspath, page, latest):
103+
"""
104+
Insert a <link> and <script> at the end of the <head> section.
105+
Skip modifying the document if the patch tag is found.
106+
"""
107+
108+
path = str(Path(page).relative_to(os.path.join(apidocspath, latest)))
109+
110+
patch_tag = f'patch-apidocs-current-redirects: {latest}'
111+
112+
is_patched = re.compile(patch_tag)
113+
end_of_head_re = re.compile(r'^(\s*)</head>$')
114+
115+
with open(page, "r+") as file:
116+
lines = [line for line in file]
117+
118+
idx = None
119+
indent = ''
120+
121+
for idx, line in enumerate(lines):
122+
if is_patched.search(line):
123+
# This file has already been patched.
124+
return
125+
126+
m = end_of_head_re.match(line)
127+
if m:
128+
# Patched index.html has 1-space indentation. The rest have none.
129+
indent = '' if m.group(1) == '' else ' '
130+
end_of_head = idx
131+
break
132+
133+
if idx is None:
134+
raise RuntimeError(f'could not find end of `<head>` in {path}')
135+
136+
# Insert patch tag to avoid repeated patch of the same file.
137+
lines.insert(end_of_head, indent + f'<!-- {patch_tag} -->\n')
138+
end_of_head += 1
139+
140+
# Canonical URL. Inform search engines about the redirect.
141+
lines.insert(
142+
end_of_head,
143+
indent + f'<link rel="canonical" href="https://mongocxx.org/api/{latest}/{path}"/>\n')
144+
end_of_head += 1
145+
146+
# Redirect script. Avoid generating history for the `/current` page during the redirect.
147+
script = ''
148+
script += indent + '<script type="text/javascript">\n'
149+
script += indent + 'if (window.location.pathname.startsWith("/api/current/")) {\n'
150+
script += indent + ' window.location.replace(\n'
151+
script += indent + f' window.location.href.replace("/api/current/", "/api/{latest}/")\n'
152+
script += indent + ' )\n'
153+
script += indent + '}\n'
154+
script += indent + '</script>\n'
155+
lines.insert(end_of_head, script)
156+
end_of_head += 1
157+
158+
file.seek(0)
159+
for line in lines:
160+
file.write(line)
161+
file.truncate()
162+
163+
164+
def main():
165+
api_docs_path: str = find_api_docs_path()
166+
167+
print(f'Patching API docs in: {api_docs_path}')
168+
169+
print('Finding API docs...')
170+
api_docs = find_api_docs(api_docs_path)
171+
if len(api_docs) == 0:
172+
raise RuntimeError(f'no API docs found: APIDOCSPATH may not be correct!')
173+
print('Finding API docs... done.')
174+
175+
print(f' - Found {len(api_docs)} API docs: {api_docs[0]} ... {api_docs[-1]}')
176+
177+
latest_doc = api_docs[-1]
178+
print(f' - Using {latest_doc} as the latest API doc.')
179+
180+
print(f'Patching latest API doc pages to redirect from /current to /{latest_doc}...')
181+
patch_redirect_current_pages(api_docs_path, latest_doc)
182+
print(f'Patching latest API doc pages to redirect from /current to /{latest_doc}... done.')
183+
184+
185+
if __name__ == '__main__':
186+
main()

etc/releasing.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ These commands will update the `gh-pages` branch and push the changes to the rem
646646
> [!WARNING]
647647
> Build and release artifacts may still be present in the repository after this step. Do not accidentally commit these files into the repository in the following steps!
648648
649-
### Update Symlinks
649+
### Update gh-pages
650650

651651
> [!IMPORTANT]
652652
> Symlink updates only apply to stable releases! Release candidates and other unstable releases do not require updating symlinks.
@@ -673,10 +673,27 @@ current -> mongocxx-v3
673673
mongocxx-v3 -> mongocxx-X.Y.Z
674674
```
675675

676-
Commit and push this change to the `gh-pages` branch:
676+
Add a new entry to the `sitemap_index.xml` file referencing the sitemap for `api/mongocxx-X.Y.Z`.
677+
Set `<lastmod>` for both the new entry and the `/current` sitemap entry to the current date:
678+
679+
```xml
680+
...
681+
<!-- API Documentation Pages. -->
682+
<sitemap>
683+
<loc>https://mongocxx.org/api/current/sitemap.xml</loc>
684+
<lastmod>YYYY-MM-DD</lastmod>
685+
</sitemap>
686+
<sitemap>
687+
<loc>https://mongocxx.org/api/mongocxx-X.Y.Z/sitemap.xml</loc>
688+
<lastmod>YYYY-MM-DD</lastmod>
689+
</sitemap>
690+
...
691+
```
692+
693+
Commit and push these change to the `gh-pages` branch:
677694

678695
```bash
679-
git commit -m "Update symlink for rX.Y.Z"
696+
git commit -m "Update symlink and sitemap for rX.Y.Z"
680697
```
681698

682699
Wait for [GitHub Actions](https://github.com/mongodb/mongo-cxx-driver/actions) to finish deploying the updated pages.

0 commit comments

Comments
 (0)