Files
docs/normalize-includes.py
Ron Stone a19a907bfa Replace normalize-includes
Replace shell implementation with python script. More flexible solution that adjusts indentation
of included content to offset of directive in host file. This allows greater reusability.

Change-Id: Ic048f2ad950b686127710dee983ef56f688eda65
Signed-off-by: Ron Stone <ronald.stone@windriver.com>
2025-12-04 12:44:32 +00:00

126 lines
3.8 KiB
Python

#!/usr/bin/env python3
import os
import re
import sys
from pathlib import Path
# Detect a pre-include directive
PRE_INCLUDE_RE = re.compile(r'^(\s*)\.\.\s+pre-include::\s+(.+)$')
# Detect options following the directive
OPTION_RE = re.compile(r'^\s*:(\w[\w-]*):\s*(.*)$')
def read_included_content(filepath, options):
"""Read included file and slice per start-after/end-before, literal, and tab-width."""
try:
with open(filepath, "r", encoding="utf-8") as f:
lines = f.readlines()
except FileNotFoundError:
return [f".. (error: file not found: {filepath})\n"]
# Apply start-after
if "start-after" in options:
marker = options["start-after"]
for i, line in enumerate(lines):
if marker in line:
lines = lines[i + 1 :]
break
# Apply end-before
if "end-before" in options:
marker = options["end-before"]
for i, line in enumerate(lines):
if marker in line:
lines = lines[:i]
break
# Handle tab-width (default 8 if not provided)
if "tab-width" in options:
try:
width = int(options["tab-width"]) if options["tab-width"] else 8
lines = [l.expandtabs(width) for l in lines]
except ValueError:
pass # ignore malformed value
return lines
def process_file(path):
"""Process one file and expand all pre-include directives."""
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
output_lines = []
i = 0
changed = False
while i < len(lines):
line = lines[i]
m = PRE_INCLUDE_RE.match(line)
if not m:
output_lines.append(line)
i += 1
continue
indent, filename = m.groups()
filename = filename.strip()
options = {}
# Gather options (start-after, end-before, literal, tab-width, etc.)
j = i + 1
while j < len(lines):
opt_match = OPTION_RE.match(lines[j])
if not opt_match:
break
opt_name, opt_value = opt_match.groups()
options[opt_name] = opt_value
j += 1
include_path = (Path(path).parent / filename).resolve()
included_lines = read_included_content(include_path, options)
# Apply literal or standard indentation
if "literal" in options:
# For literal, ensure a blank line before and after if not present
if output_lines and output_lines[-1].strip():
output_lines.append("\n")
output_lines.extend([indent + l for l in included_lines])
if included_lines and included_lines[-1].strip():
output_lines.append("\n")
else:
# Normal include: indent non-empty lines, preserve blank ones
indented = [indent + l if l.strip() else l for l in included_lines]
output_lines.extend(indented)
changed = True
i = j # Skip over directive and its options
if changed:
# backup_path = path + ".bak"
# os.rename(path, backup_path)
with open(path, "w", encoding="utf-8") as f:
f.writelines(output_lines)
# print(f"Processed {path} (backup saved as {backup_path})")
print(f"\u2705 Processed {path})")
else:
print(f"No pre-include directives in {path}")
def main():
if len(sys.argv) != 2:
print("Usage: normalize_includes.py <directory>")
sys.exit(1)
root = Path(sys.argv[1]).resolve()
if not root.is_dir():
print(f"⚠️ Error: {root} is not a directory")
sys.exit(1)
for ext in ("*.rst", "*.rest"):
for path in root.rglob(ext):
process_file(path)
if __name__ == "__main__":
main()