2025-12-06 22:25:15 +00:00
|
|
|
import re
|
2025-12-26 22:49:22 +00:00
|
|
|
import argparse
|
|
|
|
|
import os
|
2025-12-06 22:25:15 +00:00
|
|
|
|
2026-08-05 15:08:04 +00:00
|
|
|
|
|
|
|
|
def strip_leading_html_tag(text: str) -> str:
|
|
|
|
|
"""Remove a leading HTML element if the first non-space character is '<'."""
|
|
|
|
|
trimmed = text.lstrip()
|
|
|
|
|
#if not trimmed.startswith('<'):
|
|
|
|
|
# return text
|
|
|
|
|
|
|
|
|
|
match = re.match(
|
|
|
|
|
r'^\s*<\s*(?P<tag>[A-Za-z][A-Za-z0-9:-]*)(?:\s+[^<>]*)?>'
|
|
|
|
|
r'(?P<body>.*?)(?:</\s*(?P=tag)\s*>)?',
|
|
|
|
|
trimmed,
|
|
|
|
|
re.DOTALL,
|
|
|
|
|
)
|
|
|
|
|
if not match:
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
return trimmed[match.end():]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_sections(md_text: str) -> dict[str, tuple[str, str]]:
|
2025-12-06 22:25:15 +00:00
|
|
|
"""
|
|
|
|
|
Extracts sections based on headings (#...).
|
|
|
|
|
Returns {heading_text: section_content}
|
|
|
|
|
Works for simple Markdown, not fully strict.
|
2026-08-05 15:08:04 +00:00
|
|
|
|
|
|
|
|
If strip_first_heading is True, omit the first heading/section from the output.
|
2025-12-06 22:25:15 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# regex captures: heading marks (###...), heading text, and the following content
|
|
|
|
|
pattern = re.compile(
|
|
|
|
|
r'^(#{1,6})\s+(.*?)\s*$' # heading level + heading text
|
2025-12-27 16:38:17 +00:00
|
|
|
r'(.*?(?:```.*?```.*?)*?)' # section content (lazy)
|
2025-12-06 22:25:15 +00:00
|
|
|
r'(?=^#{1,6}\s+|\Z)', # stop at next heading or end of file
|
|
|
|
|
re.MULTILINE | re.DOTALL
|
|
|
|
|
)
|
|
|
|
|
|
2026-08-05 15:08:04 +00:00
|
|
|
sections: dict[str, tuple[str, str]] = {}
|
|
|
|
|
matched = list(pattern.findall(md_text))
|
|
|
|
|
|
|
|
|
|
for prefix, title, content in matched:
|
2025-12-22 14:39:17 +00:00
|
|
|
assert isinstance(content, str)
|
2026-08-05 15:08:04 +00:00
|
|
|
content = strip_leading_html_tag(content)
|
|
|
|
|
sections[title] = (prefix + ' ' + title, content.strip().replace('](docs/source/media/', '](media/'))
|
2025-12-06 22:25:15 +00:00
|
|
|
|
|
|
|
|
return sections
|
|
|
|
|
|
2026-08-05 15:08:04 +00:00
|
|
|
|
2025-12-06 22:25:15 +00:00
|
|
|
if __name__ == '__main__':
|
2025-12-26 22:49:22 +00:00
|
|
|
parser = argparse.ArgumentParser(description='Extract sections from README.md and generate documentation files')
|
|
|
|
|
parser.add_argument('--readme', type=str, default='README.md', help='README.md path')
|
|
|
|
|
parser.add_argument('--build-dir', type=str, default='docs/source', help='Build directory for output files (default: docs/source)')
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
readme_path = args.readme
|
|
|
|
|
build_dir = args.build_dir
|
|
|
|
|
|
|
|
|
|
with open(readme_path, 'rt') as f:
|
2025-12-06 22:25:15 +00:00
|
|
|
readme = extract_sections(f.read())
|
|
|
|
|
|
2025-12-26 22:49:22 +00:00
|
|
|
with open(os.path.join(build_dir, 'start.md'), 'wt') as f:
|
2026-08-05 15:08:04 +00:00
|
|
|
f.write('\n'.join('\n'.join(readme[s]) if s != 'Copapy' else readme[s][1] for s in [
|
|
|
|
|
'Copapy', 'Current state', 'Install', 'Examples',
|
|
|
|
|
'Basic example', 'Inverse kinematics', 'License']))
|
2025-12-22 14:39:17 +00:00
|
|
|
|
2025-12-26 22:49:22 +00:00
|
|
|
with open(os.path.join(build_dir, 'compiler.md'), 'wt') as f:
|
2026-08-05 15:08:04 +00:00
|
|
|
f.write('\n'.join(readme[s][1] for s in ['How it works']))
|