170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
import os
|
|
import re
|
|
import sys
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
|
|
filenames = [
|
|
"docs/changes.md",
|
|
"docs/overview.md",
|
|
"docs/lexical_structure.md",
|
|
"docs/primitive_types.md",
|
|
"docs/basic_operations.md",
|
|
"docs/functions.md",
|
|
"docs/control_flow.md",
|
|
"docs/data_structures.md",
|
|
"docs/type_system.md",
|
|
"docs/trait_system.md",
|
|
"docs/generic_programming.md",
|
|
"docs/advanced_topics.md",
|
|
"docs/standard_library.md",
|
|
"docs/complete_trait_reference.md",
|
|
"docs/complete_operator_reference.md",
|
|
"docs/grammar_summary.md",
|
|
"docs/module_system.md",
|
|
"docs/memory_management.md",
|
|
"docs/examples_and_tutorials.md",
|
|
]
|
|
|
|
def remove_front_matter(content):
|
|
"""Remove YAML front matter (--- ... ---) from markdown content."""
|
|
pattern = r'^---\n.*?\n---\n'
|
|
return re.sub(pattern, '', content, flags=re.DOTALL)
|
|
|
|
def extract_front_matter(content):
|
|
"""Extract YAML front matter from markdown content."""
|
|
match = re.match(r'^---\n(.*?)\n---\n', content, flags=re.DOTALL)
|
|
return match.group(1).strip() if match else None
|
|
|
|
def read_file_list(input_arg):
|
|
"""Return list of files either from arguments or a text file of paths."""
|
|
p = Path(input_arg)
|
|
if p.is_file() and p.suffix == '.txt':
|
|
with p.open(encoding='utf-8') as f:
|
|
return [line.strip() for line in f if line.strip()]
|
|
return [input_arg]
|
|
|
|
def combine_markdown(file_inputs, output_combined, output_meta_json):
|
|
"""Combine multiple markdown files into one, skipping front matter."""
|
|
files = []
|
|
for f in file_inputs:
|
|
files.extend(read_file_list(f))
|
|
|
|
combined_parts = []
|
|
meta_info = {"order": [], "files": {}}
|
|
|
|
for file in files:
|
|
file_path = Path(file)
|
|
if not file_path.exists():
|
|
print(f"Skipping missing file: {file}")
|
|
continue
|
|
|
|
text = file_path.read_text(encoding='utf-8')
|
|
front_matter = extract_front_matter(text)
|
|
if front_matter:
|
|
meta_info["files"][file_path.name] = front_matter
|
|
cleaned = remove_front_matter(text).strip()
|
|
combined_parts.append(cleaned)
|
|
meta_info["order"].append(file_path.name)
|
|
|
|
Path(output_combined).write_text("\n\n".join(combined_parts) + "\n", encoding='utf-8')
|
|
Path(output_meta_json).write_text(json.dumps(meta_info, indent=2), encoding='utf-8')
|
|
|
|
print(f"Combined file saved as: {output_combined}")
|
|
print(f"Metadata JSON saved as: {output_meta_json}")
|
|
|
|
def write_with_safety(path, content, force=False, backup=False):
|
|
"""Write a file safely with force/backup options and diff-aware prompt."""
|
|
if path.exists():
|
|
current = path.read_text(encoding='utf-8')
|
|
if current == content:
|
|
print(f"No changes for: {path}")
|
|
return
|
|
|
|
if backup and not force:
|
|
backup_path = path.with_suffix(path.suffix + ".bak")
|
|
shutil.copy2(path, backup_path)
|
|
print(f"Backup created: {backup_path}")
|
|
elif not force:
|
|
while True:
|
|
choice = input(f"{path} has changed. [y] overwrite, [b] backup+overwrite, [n] skip? ").strip().lower()
|
|
if choice in ("y", "yes"):
|
|
break
|
|
elif choice in ("b", "backup"):
|
|
backup_path = path.with_suffix(path.suffix + ".bak")
|
|
shutil.copy2(path, backup_path)
|
|
print(f"Backup created: {backup_path}")
|
|
break
|
|
elif choice in ("n", "no", ""):
|
|
print(f"Skipped: {path}")
|
|
return
|
|
else:
|
|
print("Please choose [y], [b], or [n].")
|
|
path.write_text(content, encoding='utf-8')
|
|
print(f"Wrote: {path}")
|
|
|
|
def split_with_front_matter(input_combined, output_dir, metadata_file, force=False, backup=False):
|
|
"""Split a combined markdown file back into original files, restoring front matter."""
|
|
combined_text = Path(input_combined).read_text(encoding='utf-8')
|
|
|
|
if not Path(metadata_file).exists():
|
|
print(f"Metadata file not found: {metadata_file}")
|
|
sys.exit(1)
|
|
|
|
meta_info = json.loads(Path(metadata_file).read_text(encoding='utf-8'))
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
order = meta_info.get("order", [])
|
|
frontmatters = meta_info.get("files", {})
|
|
|
|
# Split by H2s — each file should start with one or more H2 sections
|
|
# and we assume each original file started with an H2 or higher heading.
|
|
sections = re.split(r'(?=^## )', combined_text, flags=re.MULTILINE)
|
|
sections = [s.strip() for s in sections if s.strip()]
|
|
|
|
if len(sections) != len(order):
|
|
print(f"Warning: {len(sections)} sections found but {len(order)} files listed. "
|
|
f"Splitting by simple proportion instead.")
|
|
approx_size = len(combined_text) // len(order)
|
|
chunks = [combined_text[i*approx_size:(i+1)*approx_size] for i in range(len(order)-1)]
|
|
chunks.append(combined_text[(len(order)-1)*approx_size:])
|
|
else:
|
|
chunks = sections
|
|
|
|
for i, filename in enumerate(order):
|
|
output_path = Path(output_dir, filename)
|
|
body = chunks[i].strip() if i < len(chunks) else ""
|
|
|
|
# Restore front matter if available
|
|
front_matter = frontmatters.get(filename)
|
|
if front_matter:
|
|
content = f"---\n{front_matter}\n---\n\n{body}\n"
|
|
else:
|
|
content = body + "\n"
|
|
|
|
write_with_safety(output_path, content, force=force, backup=backup)
|
|
|
|
print(f"Split complete. {len(order)} files processed.")
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage:")
|
|
print(" Combine: python manage_docs.py combine")
|
|
print(" Split: python manage_docs.py split [--force|--backup]")
|
|
sys.exit(1)
|
|
|
|
command = sys.argv[1].lower()
|
|
force = "--force" in sys.argv
|
|
backup = "--backup" in sys.argv
|
|
|
|
if command == "combine":
|
|
combine_markdown(filenames, "stack_lang_spec.md", "metadata.json")
|
|
|
|
elif command == "split":
|
|
split_with_front_matter("stack_lang_spec.md", "docs", "metadata.json",
|
|
force=force, backup=backup)
|
|
|
|
else:
|
|
print("Unknown command. Use 'combine' or 'split'.")
|