"""README.md relative links must stay inside the repo and point to existing files."""
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
LINK = re.compile(r'!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)')
def bad_links(text, root=ROOT):
bad = []
for target in LINK.findall(text):
if re.match(r'^([a-zA-Z][a-zA-Z0-9+.-]*:|#|//)', target):
continue
path = target.split('#')[0].split('?')[0]
if not path:
continue
p = (root / path).resolve()
if root.resolve() not in (p, *p.parents):
bad.append((target, 'escapes repo'))
elif not p.exists():
bad.append((target, 'missing'))
return bad
class ReadmeLinks(unittest.TestCase):
def test_current_readme(self):
self.assertEqual(bad_links((ROOT / 'README.md').read_text()), [])
def test_detects_escape(self):
self.assertEqual(bad_links('[v](../worldmap-viewer)'),
[('../worldmap-viewer', 'escapes repo')])
def test_detects_missing(self):
self.assertEqual(bad_links(''),
[('nope/none.png', 'missing')])
if __name__ == '__main__':
unittest.main()