123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- 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();
|