"""Extend the supplied matrix with ordered-list cases; no network or installs. Run from the repository root with .venv/bin/python and --lab /lab. HTML is deliberately stored as .txt for the public evidence allowlist. """ import argparse from datetime import datetime, timezone from html.parser import HTMLParser import importlib.metadata import json from pathlib import Path import platform import subprocess import markdown from markdown_it import MarkdownIt SAMPLES = { 'repeat-one': '1. alpha\n1. beta\n1. gamma\n', 'restart-paragraph': '3. alpha\n4. beta\n\nNext phase.\n\n8. restart\n', 'nested-three': '1. Parent\n 1. Child\n2. Next\n', 'nested-four': '1. Parent\n 1. Child\n2. Next\n', 'wide-three': '100. Parent\n 1. Child\n101. Next\n', 'wide-five': '100. Parent\n 1. Child\n101. Next\n', 'interrupt-one': 'Steps:\n1. alpha\n2. beta\n', 'interrupt-three': 'Steps:\n3. alpha\n4. beta\n', 'separated-three': 'Steps:\n\n3. alpha\n4. beta\n', 'parenthesis': '1) alpha\n2) beta\n', 'escaped-number': '1986\\. A year, not a step.\n', 'note-four': '1. Parent\n\n A note inside the step.\n\n2. Next\n', } class Structure(HTMLParser): def __init__(self): super().__init__() self.tags = {} self.stack = [] self.ordered_lists = [] self.max_ol_depth = 0 def handle_starttag(self, tag, attrs): self.tags[tag] = self.tags.get(tag, 0) + 1 if tag == 'ol': self.stack.append(tag) self.max_ol_depth = max(self.max_ol_depth, len(self.stack)) self.ordered_lists.append(dict(attrs)) def handle_endtag(self, tag): if tag == 'ol': self.stack.pop() def structure(html): p = Structure() p.feed(html) return {'tags': p.tags, 'ordered_lists': p.ordered_lists, 'max_ol_depth': p.max_ol_depth} def main(lab): out = lab / 'renderers' report = json.loads((out / 'results.json').read_text()) for name, source in SAMPLES.items(): (out / f'{name}.md').write_text(source) names = ['ordered-list', *SAMPLES] rows = [r for r in report['results'] if r['sample'] not in names] for name in names: source = (out / f'{name}.md').read_text() paths = { 'markdown-it-commonmark': MarkdownIt('commonmark').render(source), 'markdown-it-breaks': MarkdownIt('commonmark', {'breaks': True}).render(source), 'python-markdown': markdown.markdown(source, extensions=['tables', 'fenced_code']), 'python-sane-lists': markdown.markdown(source, extensions=['tables', 'fenced_code', 'sane_lists']), 'pandoc-gfm': subprocess.run(['pandoc', '-f', 'gfm', '-t', 'html'], input=source, text=True, capture_output=True, check=True).stdout, } for renderer, html in paths.items(): path = out / f'{name}--{renderer}.txt' path.write_text(html) rows.append({'sample': name, 'renderer': renderer, 'source': f'lab/renderers/{name}.md', 'output': f'lab/renderers/{path.name}', **structure(html)}) report['results'] = rows report['versions']['python-sane-lists'] = importlib.metadata.version('Markdown') report['ordered_list_extension'] = { 'executed_at': datetime.now(timezone.utc).isoformat(), 'python': platform.python_version(), 'command': '.venv/bin/python ' + str(Path(__file__).relative_to(Path.cwd())) + ' --lab ' + str(lab), 'configurations': { 'markdown-it-commonmark': "MarkdownIt('commonmark')", 'markdown-it-breaks': "MarkdownIt('commonmark', {'breaks': True})", 'python-markdown': "markdown.markdown(source, extensions=['tables', 'fenced_code'])", 'python-sane-lists': "markdown.markdown(source, extensions=['tables', 'fenced_code', 'sane_lists'])", 'pandoc-gfm': 'pandoc -f gfm -t html', }, 'metrics': { 'tags': 'Number of HTML start tags, counted by html.parser.HTMLParser', 'ordered_lists': 'Attributes on each ol in document order; omitted start means the HTML default', 'max_ol_depth': 'Maximum number of simultaneously open ol elements; zero means no ordered list', }, 'sample_count': len(names), 'comparison_count': len(names) * 5, } (out / 'results.json').write_text(json.dumps(report, indent=2) + '\n') lines = ['# Ordered-list structural results', '', '| Sample | Renderer | ol | li | p | max depth | ol attributes |', '| --- | --- | ---: | ---: | ---: | ---: | --- |'] for r in rows: if r['sample'] not in names: continue t = r['tags'] lines.append(f"| {r['sample']} | {r['renderer']} | {t.get('ol',0)} | {t.get('li',0)} | {t.get('p',0)} | {r['max_ol_depth']} | {json.dumps(r['ordered_lists'])} |") (out / 'ordered-results.md').write_text('\n'.join(lines) + '\n') print(json.dumps(report['ordered_list_extension'], indent=2)) if __name__ == '__main__': p = argparse.ArgumentParser() p.add_argument('--lab', type=Path, required=True) main(p.parse_args().lab)