Spaces:
Sleeping
Sleeping
| const { walk, EXCLUDE_LISTS } = require('../file-walker/test'); | |
| const fs = require('fs').promises; | |
| const path_ = require('node:path'); | |
| const FILE_EXCLUDES = [ | |
| /(^|\/)\.git/, | |
| /^volatile\//, | |
| /^node_modules\//, | |
| /\/node_modules$/, | |
| /^submodules\//, | |
| /^node_modules$/, | |
| /package-lock\.json/, | |
| /^src\/dev-center\/js/, | |
| /src\/backend\/src\/public\/assets/, | |
| /^src\/gui\/src\/lib/, | |
| /^eslint\.config\.js$/, | |
| // translation readme copies | |
| /(^|\/)doc\/i18n/, | |
| // irrelevant documentation | |
| /(^|\/)doc\/graveyard/, | |
| // development logs | |
| /\/devlog\.md$/, | |
| ] | |
| const ROOT_DIR = path_.join(__dirname, '../..'); | |
| const WIKI_DIR = path_.join(__dirname, '../../submodules/wiki'); | |
| const path_to_name = path => { | |
| // Special case for Home.md | |
| if ( path === 'doc/README.md' ) return 'Home'; | |
| // Remove src/ and doc/ components | |
| // path = path.replace(/src\//g, '') | |
| path = path.replace(/doc\//g, '') | |
| // Hyphenate components | |
| path = path.replace(/-/g, '_') | |
| path = path.replace(/\//g, '-') | |
| // Remove extension | |
| path = path.replace(/\.md$/, '') | |
| return path; | |
| } | |
| const fix_relative_links = (content, entry) => { | |
| const originalDir = path_.dirname(entry); | |
| // Markdown links: [text](path/to/file.md), [text](path/to/file#section), etc | |
| return content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, link) => { | |
| // Skip external links | |
| if (link.startsWith('http://') || link.startsWith('https://') || link.startsWith('/')) { | |
| return match; | |
| } | |
| // Anchor links within the same file aren't changed | |
| if (link.startsWith('#')) return match; | |
| // Split the link to separate the path from the anchor | |
| const [linkPath, anchor] = link.split('#'); | |
| // Resolve the relative path | |
| let resolvedPath = path_.normalize(path_.join(originalDir, linkPath)); | |
| // Find the matching wiki path | |
| const wikiPath = path_to_name(resolvedPath); | |
| const newLink = anchor ? `${wikiPath}#${anchor}` : wikiPath; | |
| return `[${text}](${newLink})`; | |
| }); | |
| }; | |
| const main = async () => { | |
| const walk_iter = walk({ | |
| excludes: FILE_EXCLUDES, | |
| }, ROOT_DIR); | |
| const documents = []; | |
| for await ( const value of walk_iter ) { | |
| let path = value.path; | |
| path = path_.relative(ROOT_DIR, path); | |
| // File must be under a doc/ directory | |
| if ( ! path.match(/(^|\/)doc\//) ) continue; | |
| // File must be markdown | |
| if ( ! path.match(/\.md/) ) continue; | |
| let outputName = path_to_name(path); | |
| // Read file content | |
| let content = await fs.readFile(value.path, 'utf8'); | |
| // Get the first heading from the file to use as title | |
| const titleMatch = content.match(/^#\s+(.+)$/m); | |
| const title = titleMatch ? titleMatch[1] : outputName.replace(/-/g, ' '); | |
| // Fix internal links | |
| content = fix_relative_links(content, path); | |
| // Write the modified content to the wiki directory | |
| await fs.writeFile(path_.join(WIKI_DIR, outputName + '.md'), content); | |
| // Store information for sidebar | |
| const sidebarPath = outputName.split('-'); | |
| // The original path structure (minus doc/) helps determine the hierarchy | |
| documents.push({ | |
| sidebarPath, | |
| outputName, | |
| title: title | |
| }); | |
| } | |
| // Generate _Sidebar.md | |
| const sidebarContent = generate_sidebar(documents); | |
| await fs.writeFile(path_.join(WIKI_DIR, '_Sidebar.md'), sidebarContent); | |
| } | |
| const format_name = name => { | |
| if ( name === 'api' ) return 'API'; | |
| if ( name === 'contributors' ) return 'For Contributors'; | |
| return name.charAt(0).toUpperCase() + name.slice(1); | |
| } | |
| const generate_sidebar = (documents) => { | |
| // Sort entries by path to group related files together | |
| documents.sort((a, b) => { | |
| const pathA = a.sidebarPath.slice(0, -1).join('/'); | |
| const pathB = b.sidebarPath.slice(0, -1).join('/'); | |
| if ( pathA !== pathB ) { | |
| return pathA.localeCompare(pathB); | |
| } | |
| // README.md always goes first | |
| const isReadmeA = a.outputName.toLowerCase().includes('readme') || | |
| a.outputName.toLowerCase().includes('home'); | |
| const isReadmeB = b.outputName.toLowerCase().includes('readme') || | |
| b.outputName.toLowerCase().includes('home'); | |
| if (isReadmeA) return -1; | |
| if (isReadmeB) return 1; | |
| return a.title.localeCompare(b.title); | |
| }); | |
| // Format a document link the same way everywhere | |
| const formatDocumentLink = (document) => { | |
| let title = document.title; | |
| if ( document.outputName.split('-').slice(-1)[0].toLowerCase() === 'readme' ) { | |
| title = 'Index (README.md)'; | |
| } | |
| if ( document.outputName.split('-').slice(-1)[0].toLowerCase() === 'home' ) { | |
| title = `Home`; | |
| } | |
| return `* [${title}](${document.outputName.replace('.md', '')})\n`; | |
| }; | |
| // Recursive function to build sidebar sections | |
| const buildSection = (docs, depth = 0, prefix = '') => { | |
| let result = ''; | |
| const directDocs = []; | |
| const subSections = new Map(); | |
| // Separate direct documents from those in subsections | |
| for (const doc of docs) { | |
| if (doc.sidebarPath.length <= depth + 1) { | |
| // Direct document at this level | |
| directDocs.push(doc); | |
| } else { | |
| // Document belongs in a subsection | |
| const sectionName = doc.sidebarPath[depth]; | |
| if (!subSections.has(sectionName)) { | |
| subSections.set(sectionName, []); | |
| } | |
| subSections.get(sectionName).push(doc); | |
| } | |
| } | |
| // Add direct documents | |
| for (const doc of directDocs) { | |
| result += formatDocumentLink(doc); | |
| } | |
| // Process subsections recursively | |
| for (const [sectionName, sectionDocs] of subSections.entries()) { | |
| // Generate heading with appropriate level | |
| const headingLevel = '#'.repeat(depth + 2); | |
| const formattedName = format_name(sectionName) | |
| result += `\n${headingLevel} ${formattedName}\n`; | |
| // Process the subsection documents | |
| result += buildSection(sectionDocs, depth + 1, `${prefix}${sectionName}/`); | |
| } | |
| return result; | |
| }; | |
| // Start with the main heading | |
| let sidebar = "## General\n\n"; | |
| // Split documents into top-level and those in sections | |
| const topLevelDocs = documents.filter(doc => doc.sidebarPath.length <= 1); | |
| const sectionDocs = documents.filter(doc => doc.sidebarPath.length > 1); | |
| // Add top-level documents | |
| for (const doc of topLevelDocs) { | |
| sidebar += formatDocumentLink(doc); | |
| } | |
| // Group the remaining documents by their top-level sections | |
| const topLevelSections = new Map(); | |
| for (const doc of sectionDocs) { | |
| const sectionName = doc.sidebarPath[0]; | |
| if (!topLevelSections.has(sectionName)) { | |
| topLevelSections.set(sectionName, []); | |
| } | |
| topLevelSections.get(sectionName).push(doc); | |
| } | |
| // Process each top-level section | |
| for (const [sectionName, sectionDocs] of topLevelSections.entries()) { | |
| const formattedName = format_name(sectionName); | |
| sidebar += `\n## ${formattedName}\n`; | |
| sidebar += buildSection(sectionDocs, 1, `${sectionName}/`); | |
| } | |
| return sidebar; | |
| }; | |
| main(); | |