Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 47 additions & 19 deletions src/MkDocsGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public function generate(array $documentationNodes, string $docsBaseDir): void
$navIdMap = $this->buildNavIdMap($processedNodes);
$usedBy = $this->buildUsedByMap($processedNodes);

// Build reverse registry for file path -> owner lookup (needed for navigation hierarchy)
$reverseRegistry = $this->buildReverseRegistry($registry);

// Build cross-reference maps for bi-directional linking
$referencedBy = $this->buildReferencedByMap($processedNodes, $registry, $navPathMap, $navIdMap);

Expand All @@ -79,7 +82,7 @@ public function generate(array $documentationNodes, string $docsBaseDir): void
$this->generateFiles($docTree, $docsOutputDir);

// Generate navigation structure with title mapping
$navStructure = $this->generateNavStructure($docTree, '', $navPathMap, $processedNodes);
$navStructure = $this->generateNavStructure($docTree, '', $navPathMap, $processedNodes, $reverseRegistry);
array_unshift($navStructure, ['Home' => 'index.md']);

// Generate config
Expand Down Expand Up @@ -398,6 +401,18 @@ private function buildRegistry(array $documentationNodes): array
return $registry;
}

private function buildReverseRegistry(array $registry): array
{
// Build reverse mapping: file path -> owner
// This allows us to look up which node generated a specific file path
$reverseRegistry = [];
foreach ($registry as $owner => $filePath) {
$reverseRegistry[$filePath] = $owner;
}

return $reverseRegistry;
}

private function buildNavPathMap(array $documentationNodes): array
{
$navPathMap = [];
Expand Down Expand Up @@ -1016,7 +1031,7 @@ private function generateFiles(array $tree, string $currentPath): void
}
}

private function generateNavStructure(array $tree, string $pathPrefix = '', array $navPathMap = [], array $allNodes = []): array
private function generateNavStructure(array $tree, string $pathPrefix = '', array $navPathMap = [], array $allNodes = [], array $reverseRegistry = []): array
{
$navItems = [];

Expand All @@ -1033,7 +1048,7 @@ private function generateNavStructure(array $tree, string $pathPrefix = '', arra
$dirName = ucwords(str_replace(['_', '-'], ' ', $key));
$navItems[] = [
'title' => $dirName,
'content' => $this->generateNavStructure($value, $pathPrefix.$key.'/', $navPathMap, $allNodes),
'content' => $this->generateNavStructure($value, $pathPrefix.$key.'/', $navPathMap, $allNodes, $reverseRegistry),
'type' => $this->getNavItemType($dirName),
'sortKey' => strtolower($dirName),
'isChild' => false,
Expand All @@ -1042,7 +1057,7 @@ private function generateNavStructure(array $tree, string $pathPrefix = '', arra
} else {
// For files, find the display title and node metadata
$displayTitle = $this->findDisplayTitleForFile($filePath, $allNodes);
$nodeMetadata = $this->findNodeMetadataForFile($filePath, $allNodes);
$nodeMetadata = $this->findNodeMetadataForFile($filePath, $allNodes, $reverseRegistry);

if ($displayTitle) {
$title = $displayTitle;
Expand Down Expand Up @@ -1072,7 +1087,7 @@ private function generateNavStructure(array $tree, string $pathPrefix = '', arra
}

// Sort the nav items with new parent-child logic
usort($navItems, function ($a, $b) use ($allNodes) {
usort($navItems, function ($a, $b) use ($allNodes, $reverseRegistry) {
// Apply type priority first: regular -> static -> uncategorised
if ($a['type'] !== $b['type']) {
$typePriority = ['regular' => 1, 'static' => 2, 'uncategorised' => 3];
Expand All @@ -1082,10 +1097,10 @@ private function generateNavStructure(array $tree, string $pathPrefix = '', arra

// Within same type, handle parent-child relationships
// If one is child and the other is parent, parent comes first
if ($a['isChild'] && ! $b['isChild'] && $a['parentKey'] === $this->findParentIdentifier($b, $allNodes)) {
if ($a['isChild'] && ! $b['isChild'] && $a['parentKey'] === $this->findParentIdentifier($b, $allNodes, $reverseRegistry)) {
return 1; // a (child) comes after b (parent)
}
if ($b['isChild'] && ! $a['isChild'] && $b['parentKey'] === $this->findParentIdentifier($a, $allNodes)) {
if ($b['isChild'] && ! $a['isChild'] && $b['parentKey'] === $this->findParentIdentifier($a, $allNodes, $reverseRegistry)) {
return -1; // a (parent) comes before b (child)
}

Expand Down Expand Up @@ -1130,7 +1145,7 @@ private function getNavItemType(string $dirName): string
private function findDisplayTitleForFile(string $filePath, array $allNodes): ?string
{
// Normalize function to handle case and space/underscore differences
$normalize = (fn($path) => strtolower(str_replace(' ', '_', $path)));
$normalize = (fn ($path) => strtolower(str_replace(' ', '_', $path)));

// Simple approach: find the node that generated this file path
foreach ($allNodes as $node) {
Expand Down Expand Up @@ -1162,10 +1177,10 @@ private function findDisplayTitleForFile(string $filePath, array $allNodes): ?st
return null;
}

private function findNodeMetadataForFile(string $filePath, array $allNodes): ?array
private function findNodeMetadataForFile(string $filePath, array $allNodes, array $reverseRegistry = []): ?array
{
// Normalize function to handle case and space/underscore differences
$normalize = (fn($path) => strtolower(str_replace(' ', '_', $path)));
$normalize = (fn ($path) => strtolower(str_replace(' ', '_', $path)));

// Find the node that generated this file path
foreach ($allNodes as $node) {
Expand All @@ -1188,23 +1203,31 @@ private function findNodeMetadataForFile(string $filePath, array $allNodes): ?ar
return $node; // Return the entire node as metadata
}
}
} else {
// For PHPDoc content, we could match based on generated paths
// This would require more complex path matching logic
// For now, we'll skip this and handle only static content
}
}

// For PHPDoc content, use the reverse registry to find the owner
if (! empty($reverseRegistry) && isset($reverseRegistry[$filePath])) {
$owner = $reverseRegistry[$filePath];

// Find the node with this owner
foreach ($allNodes as $node) {
if ($node['owner'] === $owner) {
return $node; // Return the entire node as metadata
}
}
}

return null;
}

private function findParentIdentifier(array $navItem, array $allNodes): ?string
private function findParentIdentifier(array $navItem, array $allNodes, array $reverseRegistry = []): ?string
{
// For navigation items that are files, we need to find their corresponding node
// and return the parent identifier (navId or owner)
if (isset($navItem['content']) && is_string($navItem['content'])) {
$filePath = $navItem['content'];
$nodeMetadata = $this->findNodeMetadataForFile($filePath, $allNodes);
$nodeMetadata = $this->findNodeMetadataForFile($filePath, $allNodes, $reverseRegistry);

if ($nodeMetadata) {
return $nodeMetadata['navId'] ?? $nodeMetadata['owner'] ?? null;
Expand All @@ -1231,9 +1254,14 @@ private function makeRelativePath(string $path, string $base): string
// Calculate proper relative path between two locations in the docs tree
// This handles cross-references between different directory structures
// using proper ../ notation that MkDocs expects

$pathParts = explode('/', $path);
$baseParts = explode('/', dirname($base));
//
// Strip .md extension from both paths before calculating relative path
// because MkDocs serves each .md file as a directory (e.g., main-process.md -> /main-process/)
$path = preg_replace('/\.md$/', '', $path);
$base = preg_replace('/\.md$/', '', $base);

$pathParts = explode('/', (string) $path);
$baseParts = explode('/', (string) $base);

// Remove common path prefix
while (count($pathParts) > 0 && count($baseParts) > 0 && $pathParts[0] === $baseParts[0]) {
Expand Down