update doc structure
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Physical acceptance records
|
||||
|
||||
Start with [real-hardware setup](../../docs/hardware-setup.md) on the assembled Pi.
|
||||
Then follow [the complete procedure](../../docs/developer/stress-testing.md), including bay
|
||||
Start with [real-hardware setup](../../fds-os.wiki/Hardware-Setup.md) on the assembled Pi.
|
||||
Then follow [the complete procedure](../../fds-os.wiki/Developer-Stress-Testing.md), including bay
|
||||
calibration, repeatable device populations, explicit SAFE handling and separate
|
||||
external timing measurements.
|
||||
|
||||
|
||||
@@ -28,7 +28,12 @@ for tool in ['fds-cartridge','fds-emulator']:
|
||||
output=subprocess.check_output([str(root/'usr/bin'/tool),'--version'],text=True).strip()
|
||||
assert output==f'{tool} {version}',output
|
||||
subprocess.run([str(root/'usr/bin'/tool),'--help'],check=True,stdout=subprocess.DEVNULL)
|
||||
assert (root/'usr/share/doc/fds-tools/docs/workstation.md').is_file()
|
||||
assert (root/'usr/share/doc/fds-tools/fds-os.wiki/Workstation.md').is_file()
|
||||
manual = root/'usr/share/doc/fds-tools/fds-os.wiki'
|
||||
for source in (project/'fds-os.wiki').iterdir():
|
||||
if source.suffix in ('.md', '.png'):
|
||||
assert (manual/source.name).read_bytes() == source.read_bytes(), source
|
||||
assert not (manual/'.git').exists()
|
||||
assert (root/'usr/share/licenses/fds-tools/RUST-NOTICES.txt').stat().st_size>0
|
||||
installed=subprocess.check_output(['pacman','--config',str(config),'--root',str(root),'--dbpath',str(root/'var/lib/pacman'),'-Q','fds-tools'],text=True).strip()
|
||||
assert installed==f'fds-tools {version}-1',installed
|
||||
|
||||
@@ -32,6 +32,13 @@ lock = {'format': 1, 'version': '0.1.0', 'void_commit': 'a' * 40, 'source_epoch'
|
||||
snapshot = work / 'snapshot'
|
||||
(snapshot / 'project').mkdir(parents=True)
|
||||
(snapshot / 'project/source.txt').write_bytes(b'bounded input fixture\n')
|
||||
# Freeze the real manual as ordinary files, never a pointer to this checkout's
|
||||
# Git metadata. Archive round-trip and lock checks below cover these files too.
|
||||
assert 'fds-os.wiki' in frozen.OWNED
|
||||
frozen.copy(project / 'fds-os.wiki', snapshot / 'project/fds-os.wiki')
|
||||
assert not (snapshot / 'project/fds-os.wiki/.git').exists()
|
||||
for source in (project / 'fds-os.wiki').glob('*.md'):
|
||||
assert (snapshot / 'project/fds-os.wiki' / source.name).read_bytes() == source.read_bytes()
|
||||
(snapshot / 'temporary').mkdir()
|
||||
(snapshot / 'temporary').chmod(0o1777)
|
||||
(snapshot / 'project/executable').write_bytes(b'archive mode fixture\n')
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the authoritative Gitea wiki without generating or publishing files."""
|
||||
from html import unescape
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
PROJECT = Path(__file__).resolve().parents[2]
|
||||
WIKI = PROJECT / 'fds-os.wiki'
|
||||
SOURCE_URL = 'http://gitea.home.arpa/felis/fds-os/src/branch/main/'
|
||||
WIKI_URL = 'http://gitea.home.arpa/felis/fds-os/wiki/'
|
||||
FENCE = re.compile(r'^ {0,3}(`{3,}|~{3,})(.*)$')
|
||||
LINK = re.compile(r'!?\[[^\]]*\]\((<[^>\n]+>|[^\s)]+)(?:\s+["\'][^\n]*?["\'])?\)')
|
||||
REFERENCE = re.compile(r'^ {0,3}\[[^\]\n]+\]:[ \t]*(<[^>\n]+>|\S+)', re.M)
|
||||
|
||||
|
||||
def prose(text):
|
||||
lines, fence = [], None
|
||||
for line in text.splitlines(keepends=True):
|
||||
found = FENCE.match(line)
|
||||
if fence:
|
||||
if found and found[1][0] == fence[0] and len(found[1]) >= len(fence) and not found[2].strip():
|
||||
fence = None
|
||||
continue
|
||||
if found:
|
||||
fence = found[1]
|
||||
elif not line.startswith((' ', '\t')):
|
||||
lines.append(line)
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
def anchors(text):
|
||||
result, counts = set(), {}
|
||||
for heading in re.findall(r'^#{1,6}\s+(.+?)\s*#*$', prose(text), re.M):
|
||||
heading = re.sub(r'\[([^\]]+)\]\([^)]*\)', r'\1', heading)
|
||||
heading = re.sub(r'<[^>]*>', '', heading)
|
||||
slug = re.sub(r'[^\w\- ]', '', unescape(heading).lower()).replace(' ', '-')
|
||||
count = counts.get(slug, 0)
|
||||
counts[slug] = count + 1
|
||||
result.add(slug + (f'-{count}' if count else ''))
|
||||
return result
|
||||
|
||||
|
||||
def links(text):
|
||||
text = re.sub(r'(`+)(?!`)[^\n]*?(?<!`)\1(?!`)', '', prose(text))
|
||||
return [m[1].strip('<>') for pattern in (LINK, REFERENCE) for m in pattern.finditer(text)]
|
||||
|
||||
|
||||
def main():
|
||||
if not (WIKI / 'Home.md').is_file():
|
||||
sys.exit('ERROR: missing wiki; run git submodule update --init fds-os.wiki')
|
||||
pages = {p.stem: p for p in WIKI.glob('*.md')}
|
||||
errors = []
|
||||
require = lambda condition, message: errors.append(message) if not condition else None
|
||||
require(len({name.casefold() for name in pages}) == len(pages), 'Page names collide ignoring case')
|
||||
for name in ('Home', 'Overview', 'Page-Index', '_Sidebar', '_Footer'):
|
||||
require(name in pages, f'Missing required page: {name}.md')
|
||||
for name, page in pages.items():
|
||||
require(bool(re.fullmatch(r'(?:[A-Z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*|_Sidebar|_Footer)', name)),
|
||||
f'Invalid page name: {page.name}')
|
||||
if not name.startswith('_'):
|
||||
require(page.read_text().startswith('---\ninclude_toc: true\n---\n'),
|
||||
f'{page.name}: missing table-of-contents frontmatter')
|
||||
page_anchors = {name: anchors(page.read_text()) for name, page in pages.items()}
|
||||
index_links = set(links(pages['Page-Index'].read_text())) if 'Page-Index' in pages else set()
|
||||
for name in pages.keys() - {'Page-Index', '_Sidebar', '_Footer'}:
|
||||
require(name in index_links, f'{name}.md is missing from Page-Index.md')
|
||||
checked = 0
|
||||
for page in [*pages.values(), PROJECT / 'README.md']:
|
||||
for target in links(page.read_text()):
|
||||
url = urlsplit(target)
|
||||
if target.startswith(SOURCE_URL):
|
||||
relative = unquote(url.path.removeprefix(urlsplit(SOURCE_URL).path))
|
||||
path = (PROJECT / relative).resolve()
|
||||
require(path.is_relative_to(PROJECT) and path.is_file(),
|
||||
f'{page.name}: missing source file: {target}')
|
||||
checked += 1
|
||||
continue
|
||||
if target.startswith(WIKI_URL):
|
||||
url = urlsplit(target[len(WIKI_URL):])
|
||||
elif url.scheme or url.netloc:
|
||||
continue
|
||||
name = unquote(url.path)
|
||||
if name:
|
||||
require('/' not in name and name != '..', f'{page.name}: non-flat wiki link: {target}')
|
||||
if Path(name).suffix:
|
||||
require(Path(name).suffix != '.md', f'{page.name}: wiki page links must omit .md: {target}')
|
||||
require((WIKI / name).is_file(), f'{page.name}: missing asset: {target}')
|
||||
checked += 1
|
||||
continue
|
||||
else:
|
||||
name = page.stem
|
||||
require(name in pages, f'{page.name}: missing wiki page: {target}')
|
||||
if url.fragment:
|
||||
require(unquote(url.fragment) in page_anchors.get(name, set()),
|
||||
f'{page.name}: missing section: {target}')
|
||||
checked += 1
|
||||
if errors:
|
||||
sys.exit('\n'.join('ERROR: ' + message for message in errors))
|
||||
print(f'PASS: {len(pages)} wiki pages; {checked} page, section, asset and source links')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user