From a9759342233e76af257663d65ca5f8f9e7f8eee2 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 29 Apr 2021 23:37:38 +0900 Subject: [PATCH] Close #6525: linkcheck: Add linkcheck_ignore_redirects Add a new confval; linkcheck_ignore_redirects to ignore hyperlinks that are redirected as expected. --- CHANGES | 2 ++ doc/usage/configuration.rst | 6 ++++++ sphinx/builders/linkcheck.py | 30 ++++++++++++++++++++++++++++-- tests/test_build_linkcheck.py | 20 ++++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 2a3ebbf5cbc..a193f3749e7 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index c5397e6c87f..bfb692dcb55 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -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. diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 568687a1084..e96b45d253e 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -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:')): @@ -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) @@ -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, diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 2314006e5b5..604856308d7 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -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")