87 lines
3 KiB
PHP
87 lines
3 KiB
PHP
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Automatically generates a Table of Contents and injects breadcrumbs for Markdown files.
|
||
|
|
*/
|
||
|
|
|
||
|
|
function generateToc(string $filePath, string $name): void
|
||
|
|
{
|
||
|
|
if (!is_file($filePath)) {
|
||
|
|
echo "$name not found.\n";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$content = file_get_contents($filePath);
|
||
|
|
$lines = explode("\n", $content);
|
||
|
|
|
||
|
|
$inToc = false;
|
||
|
|
$headers = [];
|
||
|
|
$bodyLines = [];
|
||
|
|
|
||
|
|
foreach ($lines as $line) {
|
||
|
|
if (trim($line) === '## Table of Contents') {
|
||
|
|
$inToc = true;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// We assume the TOC ends at the next header or double newline
|
||
|
|
if ($inToc && (str_starts_with($line, '## ') || (str_contains($content, 'This document outlines') && str_starts_with($line, 'This document outlines')))) {
|
||
|
|
$inToc = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!$inToc) {
|
||
|
|
if (preg_match('/^(##+) (.*)/', $line, $matches)) {
|
||
|
|
$level = strlen($matches[1]) - 1; // ## is level 1 in TOC
|
||
|
|
if ($level > 0) {
|
||
|
|
$anchor = strtolower(trim($matches[2]));
|
||
|
|
$anchor = str_replace('~~', '', $anchor);
|
||
|
|
$anchor = preg_replace('/[^a-z0-9]+/', '-', $anchor);
|
||
|
|
$anchor = trim($anchor, '-');
|
||
|
|
$headers[] = [
|
||
|
|
'level' => $level,
|
||
|
|
'title' => trim($matches[2]),
|
||
|
|
'anchor' => $anchor
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add "Back to Top" breadcrumb before level 2 headers, except for the first one or if already present
|
||
|
|
if ($level === 1 && !empty($bodyLines)) {
|
||
|
|
$lastLine = end($bodyLines);
|
||
|
|
if ($lastLine !== '' && !str_contains($lastLine, '[↑ Back to Top]')) {
|
||
|
|
$bodyLines[] = '';
|
||
|
|
$bodyLines[] = '[↑ Back to Top](#table-of-contents)';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$bodyLines[] = $line;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate TOC text
|
||
|
|
$tocText = "## Table of Contents\n";
|
||
|
|
foreach ($headers as $header) {
|
||
|
|
if ($header['title'] === 'Table of Contents') continue;
|
||
|
|
$indent = str_repeat(' ', $header['level'] - 1);
|
||
|
|
$tocText .= "{$indent}- [{$header['title']}](#{$header['anchor']})\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reconstruct file
|
||
|
|
$finalLines = [];
|
||
|
|
$tocInserted = false;
|
||
|
|
foreach ($bodyLines as $line) {
|
||
|
|
if (!$tocInserted && (str_starts_with($line, '## ') || (str_contains($content, 'This document outlines') && str_starts_with($line, 'This document outlines')))) {
|
||
|
|
$finalLines[] = $tocText;
|
||
|
|
$tocInserted = true;
|
||
|
|
}
|
||
|
|
$finalLines[] = $line;
|
||
|
|
}
|
||
|
|
|
||
|
|
file_put_contents($filePath, implode("\n", $finalLines));
|
||
|
|
echo "$name TOC and breadcrumbs regenerated successfully.\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
$root = __DIR__ . '/../..';
|
||
|
|
generateToc($root . '/SPECS.md', 'SPECS.md');
|
||
|
|
generateToc($root . '/MILESTONES.md', 'MILESTONES.md');
|