Skip to content

Commit fd521f5

Browse files
changed docs:links:check
1 parent 800e2d4 commit fd521f5

File tree

8 files changed

+73
-110
lines changed

8 files changed

+73
-110
lines changed

.github/workflows/checks.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ jobs:
3939
run: |
4040
poetry run -- nox -s docs:build
4141
42+
Documentation-Links:
43+
name: Doc Links Check
44+
runs-on: ubuntu-24.04
45+
permissions:
46+
contents: read
47+
steps:
48+
- name: SCM Checkout
49+
uses: actions/checkout@v4
50+
51+
- name: Setup Python & Poetry Environment
52+
uses: ./.github/actions/python-environment
53+
54+
- name: Link Check
55+
run: |
56+
poetry run -- nox -s docs:links:check
57+
4258
Changelog:
4359
name: Changelog Update Check
4460
runs-on: ubuntu-24.04

doc/changes/unreleased.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ permissions to be increased for specific jobs.
2525
## Security
2626

2727
* [#420](https://github.com/exasol/python-toolbox/issues/420): Replaced 3rd party action with GitHub actions for gh-pages
28-
* [#422](https://github.com/exasol/python-toolbox/issues/422): Set permissions within the GitHub workflows to restrict usage of the default GitHub token
28+
* [#422](https://github.com/exasol/python-toolbox/issues/422): Set permissions within the GitHub workflows to restrict usage of the default GitHub token
29+
30+
## ✨ Features
31+
32+
* [#409](https://github.com/exasol/python-toolbox/issues/409): Doc link & checks

doc/conf.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,4 @@
7979
"accent_color": "grass",
8080
}
8181
# -- Configure link checking behavior ----------------------------------------
82-
extra_linkcheck_ignores = os.getenv("SPHINX_EXTRA_LINKCHECK_IGNORES")
83-
linkcheck_ignore = (
84-
[] if not extra_linkcheck_ignores else extra_linkcheck_ignores.split(",")
85-
)
82+
linkcheck_rate_limit_timeout = 15

doc/github_actions/security_issues.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,4 @@ Ideas
112112
.. todo::
113113

114114
Consider adapting common CVE report format as input, for additional details
115-
`see here <https://github.com/CVEProject/cve-schema/blob/master/schema/v5.0/CVE_JSON_5.0_schema.json>`_.
115+
`see here <https://github.com/CVEProject/cve-schema/blob/main/schema/CVE_Record_Format.json>`_.

exasol/toolbox/nox/_documentation.py

Lines changed: 47 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
Optional,
1717
Tuple,
1818
)
19+
import argparse
1920

2021
import nox
22+
import requests # type: ignore
2123
from nox import Session
22-
from requests import (
23-
get,
24-
head,
25-
)
26-
from requests.exceptions import Timeout
2724

2825
from exasol.toolbox.nox._shared import DOCS_OUTPUT_DIR
2926
from noxconfig import (
@@ -34,8 +31,6 @@
3431

3532
def _build_docs(session: nox.Session, config: Config) -> None:
3633
session.run(
37-
"poetry",
38-
"run",
3934
"sphinx-build",
4035
"-W",
4136
"-b",
@@ -47,15 +42,34 @@ def _build_docs(session: nox.Session, config: Config) -> None:
4742

4843
def _build_multiversion_docs(session: nox.Session, config: Config) -> None:
4944
session.run(
50-
"poetry",
51-
"run",
5245
"sphinx-multiversion",
5346
f"{config.doc}",
5447
DOCS_OUTPUT_DIR,
5548
)
5649
session.run("touch", f"{DOCS_OUTPUT_DIR}/.nojekyll")
5750

5851

52+
def _check_failed_links(results: list[str]):
53+
errors = []
54+
for line, result in enumerate(results):
55+
if result.startswith("{") and "}" in result:
56+
data = json.loads(result)
57+
if not (data["status"] == "working") or (data["status"] == "ignored"):
58+
match = re.search(r"https?://[^\s\"\'<>]+", data["uri"])
59+
if match:
60+
try:
61+
request = requests.head(match.group(), timeout=15)
62+
if request.status_code == 200:
63+
data["status"] = "working"
64+
data["code"] = request.status_code
65+
results[line] = json.dumps(data)
66+
except requests.exceptions.Timeout:
67+
pass
68+
if (data["status"] == "broken") or data["status"] == "timeout":
69+
errors.append(result)
70+
return results, errors
71+
72+
5973
def _git_diff_changes_main() -> int:
6074
"""
6175
Check if doc/changes is changed and return the exit code of command git diff.
@@ -108,25 +122,18 @@ def clean_docs(_session: Session) -> None:
108122
@nox.session(name="docs:links", python=False)
109123
def docs_list_links(session: Session) -> None:
110124
"""List all the links within the documentation."""
111-
ignore = [r".*"]
112-
env = os.environ.copy()
113-
env["SPHINX_EXTRA_LINKCHECK_IGNORES"] = ",".join(ignore)
114125
with tempfile.TemporaryDirectory() as path:
115126
tmpdir = Path(path)
116127
sp = subprocess.run(
117128
[
118-
"poetry",
119-
"run",
120-
"--",
121129
"sphinx-build",
122130
"-b",
123131
"linkcheck",
132+
"-D",
133+
"linkcheck_ignore=.*",
124134
PROJECT_CONFIG.root / "doc",
125135
tmpdir,
126136
],
127-
capture_output=True,
128-
text=True,
129-
env=env,
130137
)
131138
print(sp.returncode)
132139
if sp.returncode >= 2:
@@ -151,65 +158,43 @@ def docs_list_links(session: Session) -> None:
151158
@nox.session(name="docs:links:check", python=False)
152159
def docs_links_check(session: Session) -> None:
153160
"""Checks whether all links in the documentation are accessible."""
154-
ignore = [r"https?://"]
155-
env = os.environ.copy()
156-
env["SPHINX_EXTRA_LINKCHECK_IGNORES"] = ",".join(ignore)
161+
parser = argparse.ArgumentParser(
162+
prog="nox -s release:prepare",
163+
usage="nox -s release:prepare -- [-h] [-o |--output]",
164+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
165+
)
166+
parser.add_argument(
167+
"-o",
168+
"--output",
169+
type=Path,
170+
help="path to output file",
171+
default="",
172+
)
173+
args = parser.parse_args(session.posargs)
157174
with tempfile.TemporaryDirectory() as path:
158175
tmpdir = Path(path)
159176
sp = subprocess.run(
160177
[
161-
"poetry",
162-
"run",
163-
"--",
164178
"sphinx-build",
165179
"-b",
166180
"linkcheck",
167181
PROJECT_CONFIG.root / "doc",
168182
tmpdir,
169183
],
170-
capture_output=True,
171-
text=True,
172-
env=env,
173184
)
174-
print(sp.returncode)
175185
if sp.returncode >= 2:
176186
print(sp.stderr)
177187
session.error(2)
178188
output = tmpdir / "output.json"
179-
results = output.read_text().split("\n")
180-
reslen = len(results)
181-
resstr = results[-1]
182-
if (reslen == 0) or ((reslen == 1) and (resstr == "")):
183-
return
184-
elif resstr == "":
185-
results.pop()
186-
for line_nr, result in enumerate(results):
187-
resdict = json.loads(result)
188-
if resdict["status"] == "ignored" and resdict["uri"].startswith("http"):
189-
try:
190-
match = re.search(r"https?://[^\s\"\'<>]+", resdict["uri"])
191-
if match:
192-
resdict["uri"] = match.group()
193-
print(f"{line_nr}/{reslen}")
194-
request = head(resdict["uri"], timeout=5)
195-
if request.status_code != 200:
196-
request = get(resdict["uri"], timeout=5, stream=True)
197-
request.close()
198-
if request.status_code >= 400:
199-
resdict["status"] = "broken"
200-
resdict["code"] = request.status_code
201-
if request.status_code < 400:
202-
resdict["status"] = "working"
203-
resdict["code"] = request.status_code
204-
except Timeout:
205-
resdict["status"] = "timeout"
206-
results[line_nr] = json.dumps(resdict)
207-
output.write_text("\n".join(f"{r}" for r in results))
208-
errors = []
209-
for result in results:
210-
data = json.loads(result)
211-
if (data["status"] == "broken") or data["status"] == "timeout":
212-
errors.append(result)
189+
out = output.read_text().split("\n")
190+
results, errors = _check_failed_links(out)
191+
if hasattr(args, "output"):
192+
outputfile = Path(args.output) / "link-check-output.json"
193+
if not outputfile.exists():
194+
outputfile.parent.mkdir(parents=True, exist_ok=True)
195+
outputfile.touch()
196+
outputfile.write_text("\n".join(result for result in results))
197+
print(f"file generated at path: {outputfile.resolve()}")
213198
if errors:
214199
print("Error" + "s" if len(errors) > 1 else "")
215200
print("\n".join(error for error in errors))

poetry.lock

Lines changed: 1 addition & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

project-template/{{cookiecutter.repo_name}}/doc/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,5 @@
7676
"github_url": "https://github.com/exasol/{{cookiecutter.repo_name}}",
7777
"accent_color": "grass",
7878
}
79+
# -- Configure link checking behavior ----------------------------------------
80+
linkcheck_rate_limit_timeout = 15

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ bandit = {extras = ["toml"], version = "^1.7.9"}
6464
jinja2 = "^3.1.6"
6565
pip-licenses = "^5.0.0"
6666
pip-audit = "^2.7.3"
67-
urlscan = "^1.0.6"
6867

6968
[tool.poetry.group.dev.dependencies]
7069
autoimport = "^1.4.0"

0 commit comments

Comments
 (0)