diff --git a/CHANGES b/CHANGES index bc932a7d48c..7af591708c3 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Features added * #7902: html theme: Add a new option :confval:`globaltoc_maxdepth` to control the behavior of globaltoc in sidebar * #7840: i18n: Optimize the dependencies check on bootstrap +* #5208: linkcheck: Support checks for local links * #7052: add ``:noindexentry:`` to the Python, C, C++, and Javascript domains. Update the documentation to better reflect the relationship between this option and the ``:noindex:`` option. diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index dd531708729..ef8f9d90222 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -35,6 +35,8 @@ logger = logging.getLogger(__name__) +uri_re = re.compile('[a-z]+://') + DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8', @@ -210,10 +212,21 @@ def check_uri() -> Tuple[str, str, int]: def check() -> Tuple[str, str, int]: # check for various conditions without bothering the network - if len(uri) == 0 or uri.startswith(('#', 'mailto:', 'ftp:')): + if len(uri) == 0 or uri.startswith(('#', 'mailto:')): return 'unchecked', '', 0 elif not uri.startswith(('http:', 'https:')): - return 'local', '', 0 + if uri_re.match(uri): + # non supported URI schemes (ex. ftp) + return 'unchecked', '', 0 + else: + if path.exists(path.join(self.srcdir, uri)): + return 'working', '', 0 + else: + for rex in self.to_ignore: + if rex.match(uri): + return 'ignored', '', 0 + else: + return 'broken', '', 0 elif uri in self.good: return 'working', 'old', 0 elif uri in self.broken: diff --git a/tests/roots/test-linkcheck/links.txt b/tests/roots/test-linkcheck/links.txt index fa8f11e4cf4..90759ee6369 100644 --- a/tests/roots/test-linkcheck/links.txt +++ b/tests/roots/test-linkcheck/links.txt @@ -11,6 +11,8 @@ Some additional anchors to exercise ignore code * `Example Bar invalid `_ * `Example anchor invalid `_ * `Complete nonsense `_ +* `Example valid local file `_ +* `Example invalid local file `_ .. image:: https://www.google.com/image.png .. figure:: https://www.google.com/image2.png diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index d1fec550f7b..7d85f10c53a 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -30,7 +30,9 @@ def test_defaults(app, status, warning): # images should fail assert "Not Found for url: https://www.google.com/image.png" in content assert "Not Found for url: https://www.google.com/image2.png" in content - assert len(content.splitlines()) == 5 + # looking for local file should fail + assert "[broken] path/to/notfound" in content + assert len(content.splitlines()) == 6 @pytest.mark.sphinx('linkcheck', testroot='linkcheck', freshenv=True) @@ -47,8 +49,8 @@ def test_defaults_json(app, status, warning): "info"]: assert attr in row - assert len(content.splitlines()) == 8 - assert len(rows) == 8 + assert len(content.splitlines()) == 10 + assert len(rows) == 10 # the output order of the rows is not stable # due to possible variance in network latency rowsby = {row["uri"]:row for row in rows} @@ -69,7 +71,7 @@ def test_defaults_json(app, status, warning): assert dnerow['uri'] == 'https://localhost:7777/doesnotexist' assert rowsby['https://www.google.com/image2.png'] == { 'filename': 'links.txt', - 'lineno': 16, + 'lineno': 18, 'status': 'broken', 'code': 0, 'uri': 'https://www.google.com/image2.png', @@ -92,7 +94,8 @@ def test_defaults_json(app, status, warning): 'https://localhost:7777/doesnotexist', 'http://www.sphinx-doc.org/en/1.7/intro.html#', 'https://www.google.com/image.png', - 'https://www.google.com/image2.png'] + 'https://www.google.com/image2.png', + 'path/to/notfound'] }) def test_anchors_ignored(app, status, warning): app.builder.build_all()