Skip to content

Commit

Permalink
Close sphinx-doc#6525: linkcheck: Add linkcheck_ignore_redirects
Browse files Browse the repository at this point in the history
Add a new confval; linkcheck_ignore_redirects to ignore hyperlinks
that are redirected as expected.
  • Loading branch information
tk0miya committed May 15, 2021
1 parent 05eb2ca commit a975934
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -38,6 +38,8 @@ Features added
:confval:`locale_dirs`
* #6525: linkcheck: Add :confval:`linkcheck_warn_redirects` to emit a warning
when the hyperlink is redirected
* #6525: linkcheck: Add :confval:`linkcheck_ignore_redirects` to ignore
hyperlinks that are redirected to expected URLs
* #9097: Optimize the paralell build
* #9131: Add :confval:`nitpick_ignore_regex` to ignore nitpicky warnings using
regular expressions
Expand Down
6 changes: 6 additions & 0 deletions doc/usage/configuration.rst
Expand Up @@ -2522,6 +2522,12 @@ Options for the linkcheck builder

.. versionadded:: 1.1

.. confval:: linkcheck_ignore_redirects

TODO

.. versionadded:: 4.1

.. confval:: linkcheck_request_headers

A dictionary that maps baseurls to HTTP request headers.
Expand Down
30 changes: 28 additions & 2 deletions sphinx/builders/linkcheck.py
Expand Up @@ -498,13 +498,23 @@ def check_uri() -> Tuple[str, str, int]:
new_url = response.url
if anchor:
new_url += '#' + anchor
# history contains any redirects, get last
if response.history:

if ignored_redirect(req_url, new_url):
return 'working', '', 0
elif response.history:
# history contains any redirects, get last
code = response.history[-1].status_code
return 'redirected', new_url, code
else:
return 'redirected', new_url, 0

def ignored_redirect(url: str, new_url: str) -> bool:
for from_url, to_url in self.config.linkcheck_ignore_redirects.items():
if from_url.match(url) and to_url.match(new_url):
return True

return False

def check(docname: str) -> Tuple[str, str, int]:
# check for various conditions without bothering the network
if len(uri) == 0 or uri.startswith(('#', 'mailto:', 'tel:')):
Expand Down Expand Up @@ -646,11 +656,25 @@ def run(self, **kwargs: Any) -> None:
hyperlinks[uri] = uri_info


def compile_linkcheck_ignore_redirects(app: Sphinx, config: Config) -> None:
"""Compile patterns in linkcheck_ignore_redirects to the regexp objects."""
for url, pattern in list(app.config.linkcheck_ignore_redirects.items()):
try:
app.config.linkcheck_ignore_redirects[re.compile(url)] = re.compile(pattern)
except re.error as exc:
logger.warning(__('Failed to compile regex in linkcheck_ignore_redirects: %r %s'),
exc.pattern, exc.msg)
finally:
# Remove the original regexp-string
app.config.linkcheck_ignore_redirects.pop(url)


def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(CheckExternalLinksBuilder)
app.add_post_transform(HyperlinkCollector)

app.add_config_value('linkcheck_ignore', [], None)
app.add_config_value('linkcheck_ignore_redirects', {}, None)
app.add_config_value('linkcheck_auth', [], None)
app.add_config_value('linkcheck_request_headers', {}, None)
app.add_config_value('linkcheck_retries', 1, None)
Expand All @@ -663,6 +687,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('linkcheck_rate_limit_timeout', 300.0, None)
app.add_config_value('linkcheck_warn_redirects', False, None)

app.connect('config-inited', compile_linkcheck_ignore_redirects, priority=800)

return {
'version': 'builtin',
'parallel_read_safe': True,
Expand Down
20 changes: 20 additions & 0 deletions tests/test_build_linkcheck.py
Expand Up @@ -298,6 +298,26 @@ def test_linkcheck_warn_redirects(app, warning):
"http://localhost:7777/?redirected=1\n" in strip_escseq(warning.getvalue()))


@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True,
confoverrides={
'linkcheck_ignore_redirects': {'http://localhost:.*/': '.*'}
})
def test_linkcheck_ignore_redirects(app):
with http_server(make_redirect_handler(support_head=False)):
app.build()

with open(app.outdir / 'output.json') as fp:
content = json.load(fp)
assert content == {
"code": 0,
"status": "working",
"filename": "index.rst",
"lineno": 1,
"uri": "http://localhost:7777/",
"info": "",
}


class OKHandler(http.server.BaseHTTPRequestHandler):
def do_HEAD(self):
self.send_response(200, "OK")
Expand Down

0 comments on commit a975934

Please sign in to comment.