Ensure there's folder docs/ in root directory. Then:
- Copy paste the code
generate-toc.php. - Run script
php generate-toc.php
| #!/usr/bin/env php | |
| <?php | |
| /** | |
| * Generate Table of Contents for Documentation | |
| * | |
| * Usage: php generate-toc.php [path] | |
| * Example: php generate-toc.php docs/ | |
| */ | |
| class TocGenerator | |
| { | |
| protected array $ignoredFiles = ['README.md']; | |
| protected array $ignoredFolders = ['.git', 'vendor', 'node_modules']; | |
| protected int $maxDepth = 5; | |
| public function generate(string $path, int $currentDepth = 0): string | |
| { | |
| if ($currentDepth > $this->maxDepth) { | |
| return ''; | |
| } | |
| $path = rtrim($path, '/'); | |
| if (!is_dir($path)) { | |
| echo "Error: Path '{$path}' is not a directory.\n"; | |
| exit(1); | |
| } | |
| $items = $this->scanDirectory($path); | |
| $output = ''; | |
| foreach ($items as $item) { | |
| $fullPath = $path . '/' . $item; | |
| if (is_dir($fullPath)) { | |
| // Skip ignored folders | |
| if (in_array($item, $this->ignoredFolders)) { | |
| continue; | |
| } | |
| // Add folder header | |
| $indent = str_repeat(' ', $currentDepth); | |
| $folderName = $this->formatName($item); | |
| $sectionNumber = $this->extractSectionNumber($item); | |
| if ($sectionNumber) { | |
| $output .= "{$indent}- **{$sectionNumber}. {$folderName}**\n"; | |
| } else { | |
| $output .= "{$indent}- **{$folderName}**\n"; | |
| } | |
| // Recursively process subfolder | |
| $subContent = $this->generate($fullPath, $currentDepth + 1); | |
| if ($subContent) { | |
| $output .= $subContent; | |
| } | |
| } elseif (is_file($fullPath) && pathinfo($fullPath, PATHINFO_EXTENSION) === 'md') { | |
| // Skip ignored files | |
| if (in_array($item, $this->ignoredFiles)) { | |
| continue; | |
| } | |
| // Add markdown file | |
| $indent = str_repeat(' ', $currentDepth); | |
| $fileName = $this->formatName(pathinfo($item, PATHINFO_FILENAME)); | |
| $relativePath = str_replace(getcwd() . '/', '', $fullPath); | |
| $output .= "{$indent} - [{$fileName}]({$relativePath})\n"; | |
| } | |
| } | |
| return $output; | |
| } | |
| protected function scanDirectory(string $path): array | |
| { | |
| $items = scandir($path); | |
| // Remove . and .. | |
| $items = array_diff($items, ['.', '..']); | |
| // Sort: folders first, then files, both alphabetically | |
| usort($items, function($a, $b) use ($path) { | |
| $aIsDir = is_dir($path . '/' . $a); | |
| $bIsDir = is_dir($path . '/' . $b); | |
| if ($aIsDir && !$bIsDir) { | |
| return -1; | |
| } | |
| if (!$aIsDir && $bIsDir) { | |
| return 1; | |
| } | |
| return strnatcasecmp($a, $b); | |
| }); | |
| return $items; | |
| } | |
| protected function formatName(string $name): string | |
| { | |
| // Remove section number prefix (e.g., "01-getting-started" -> "Getting Started") | |
| $name = preg_replace('/^\d+-/', '', $name); | |
| // Replace hyphens and underscores with spaces | |
| $name = str_replace(['-', '_'], ' ', $name); | |
| // Capitalize words | |
| return ucwords($name); | |
| } | |
| protected function extractSectionNumber(string $name): ?string | |
| { | |
| if (preg_match('/^(\d+)-/', $name, $matches)) { | |
| return ltrim($matches[1], '0') ?: '0'; | |
| } | |
| return null; | |
| } | |
| public function generateWithHeader(string $path): string | |
| { | |
| $toc = "## π Documentation Structure\n\n"; | |
| $toc .= "This documentation is organized into the following sections:\n\n"; | |
| $toc .= $this->generate($path); | |
| return $toc; | |
| } | |
| } | |
| // Main execution | |
| $path = $argv[1] ?? 'docs'; | |
| if (!file_exists($path)) { | |
| echo "Error: Path '{$path}' does not exist.\n"; | |
| echo "Usage: php generate-toc.php [path]\n"; | |
| echo "Example: php generate-toc.php docs/\n"; | |
| exit(1); | |
| } | |
| $generator = new TocGenerator(); | |
| $toc = $generator->generateWithHeader($path); | |
| echo $toc; | |
| echo "\n"; | |
| echo "β Table of Contents generated successfully!\n"; | |
| echo "π Copy the output above and paste it into your README.md\n"; |