UNPKG

@xpack/docusaurus-plugin-doxygen

Version:

A Docusaurus plugin to integrate Doxygen into a Docusaurus project.

697 lines 31.1 kB
/* * This file is part of the xPack project (http://xpack.github.io). * Copyright (c) 2025 Liviu Ionescu. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software * for any purpose is hereby granted, under the terms of the MIT license. * * If a copy of the license was not distributed with this file, it can * be obtained from https://opensource.org/licenses/MIT. */ import assert from 'node:assert'; import crypto from 'node:crypto'; import { CompoundBase } from './compound-base-vm.js'; import { CollectionBase } from './collection-base.js'; import { escapeHtml, escapeMdx, flattenPath, sanitizeHierarchicalPath } from '../utils.js'; import { IndexEntry } from './indices-vm.js'; // ---------------------------------------------------------------------------- const kindsPlurals = { class: 'Classes', struct: 'Structs', union: 'Unions' }; // ---------------------------------------------------------------------------- export class Classes extends CollectionBase { constructor() { super(...arguments); // compoundsById: Map<string, Class> this.topLevelClasses = []; } // -------------------------------------------------------------------------- // constructor (workspace: Workspace) { // super(workspace) // // this.compoundsById = new Map() // } // -------------------------------------------------------------------------- addChild(compoundDef) { const classs = new Class(this, compoundDef); this.collectionCompoundsById.set(classs.id, classs); return classs; } // -------------------------------------------------------------------------- createCompoundsHierarchies() { // Recreate classes hierarchies. for (const [classId, base] of this.collectionCompoundsById) { const classs = base; for (const baseClassId of classs.baseClassIds) { // console.log(classId, baseClassId) const baseClass = this.collectionCompoundsById.get(baseClassId); if (baseClass !== undefined) { // console.log('baseClassId', baseClassId, 'has child', classId) baseClass.children.push(classs); classs.baseClasses.push(baseClass); } else { console.warn(baseClassId, 'ignored as base class for', classId); } } } for (const [classId, base] of this.collectionCompoundsById) { const classs = base; if (classs.baseClassIds.size === 0) { // console.log('topLevelClassId:', classId) this.topLevelClasses.push(classs); } } } // -------------------------------------------------------------------------- createSidebarItems() { // Add classes to the sidebar. // Top level classes are added below a Class category const classesCategory = { type: 'category', label: 'Classes', link: { type: 'doc', id: `${this.workspace.sidebarBaseId}classes/index` }, collapsed: true, items: [] }; classesCategory.items.push({ type: 'category', label: '#Index', link: { type: 'doc', id: `${this.workspace.sidebarBaseId}index/classes/all` }, collapsed: true, items: [ { type: 'doc', label: 'All', id: `${this.workspace.sidebarBaseId}index/classes/all` }, { type: 'doc', label: 'Classes', id: `${this.workspace.sidebarBaseId}index/classes/classes` }, { type: 'doc', label: 'Functions', id: `${this.workspace.sidebarBaseId}index/classes/functions` }, { type: 'doc', label: 'Variables', id: `${this.workspace.sidebarBaseId}index/classes/variables` }, { type: 'doc', label: 'Typedefs', id: `${this.workspace.sidebarBaseId}index/classes/typedefs` } ] }); for (const classs of this.topLevelClasses) { const item = this.createSidebarItemRecursively(classs); if (item !== undefined) { classesCategory.items.push(item); } } return [classesCategory]; } createSidebarItemRecursively(classs) { if (classs.sidebarLabel === undefined) { return undefined; } if (classs.children.length === 0) { const docItem = { type: 'doc', label: classs.sidebarLabel, id: `${this.workspace.sidebarBaseId}${classs.docusaurusId}` }; return docItem; } else { const categoryItem = { type: 'category', label: classs.sidebarLabel, link: { type: 'doc', id: `${this.workspace.sidebarBaseId}${classs.docusaurusId}` }, collapsed: true, items: [] }; for (const child of classs.children) { const item = this.createSidebarItemRecursively(child); if (item !== undefined) { categoryItem.items.push(item); } } return categoryItem; } } // -------------------------------------------------------------------------- createMenuItems() { const menuItem = { label: 'Classes', to: `${this.workspace.menuBaseUrl}classes/` }; return [menuItem]; } // -------------------------------------------------------------------------- async generateIndexDotMdxFile() { const filePath = `${this.workspace.outputFolderPath}classes/index.mdx`; const permalink = 'classes'; const frontMatter = { title: 'The Classes Reference', slug: `${this.workspace.slugBaseUrl}${permalink}`, // description: '...', // TODO custom_edit_url: null, keywords: ['doxygen', 'classes', 'reference'] }; const lines = []; lines.push('The classes, structs, union and interfaces used by this project are:'); lines.push(''); lines.push('<TreeTable>'); for (const classs of this.topLevelClasses) { lines.push(...this.generateIndexMdxFileRecursively(classs, 1)); } lines.push(''); lines.push('</TreeTable>'); console.log(`Writing classes index file ${filePath}...`); await this.workspace.writeMdxFile({ filePath, frontMatter, bodyLines: lines }); } generateIndexMdxFileRecursively(classs, depth) { // console.log(util.inspect(classs, { compact: false, depth: 999 })) const lines = []; const permalink = this.workspace.getPagePermalink(classs.id); assert(permalink !== undefined && permalink.length > 1); const iconLetters = { class: 'C', struct: 'S', union: 'U' }; let iconLetter = iconLetters[classs.kind]; if (iconLetter === undefined) { console.error('Icon kind', classs.kind, 'not supported yet in', this.constructor.name, '(using ?)'); iconLetter = '?'; } const label = escapeMdx(classs.unqualifiedName); lines.push(''); lines.push('<TreeTableRow'); lines.push(` itemIconLetter="${iconLetter}"`); lines.push(` itemLabel="${label}"`); lines.push(` itemLink="${permalink}"`); lines.push(` depth = "${depth}" >`); if (classs.briefDescriptionMdxText !== undefined && classs.briefDescriptionMdxText.length > 0) { lines.push(classs.briefDescriptionMdxText.replace(/[.]$/, '')); } lines.push('</TreeTableRow>'); if (classs.children.length > 0) { for (const childClass of classs.children) { lines.push(...this.generateIndexMdxFileRecursively(childClass, depth + 1)); } } return lines; } async generatePerInitialsIndexMdxFiles() { const allUnorderedEntriesMap = new Map(); for (const [compoundId, compound] of this.collectionCompoundsById) { const compoundEntry = new IndexEntry(compound); allUnorderedEntriesMap.set(compoundEntry.id, compoundEntry); for (const section of compound.sections) { for (const member of section.definitionMembers) { const memberEntry = new IndexEntry(member); allUnorderedEntriesMap.set(memberEntry.id, memberEntry); } } } // ------------------------------------------------------------------------ const outputFolderPath = this.workspace.outputFolderPath; { const filePath = `${outputFolderPath}index/classes/all.mdx`; const permalink = 'index/classes/all'; const frontMatter = { title: 'The Classes and Members Index', slug: `${this.workspace.slugBaseUrl}${permalink}`, // description: '...', // TODO custom_edit_url: null, keywords: ['doxygen', 'classes', 'index'] }; const lines = []; lines.push('The classes, structs, union interfaces and their members, variables, types used by this project are:'); const orderedEntriesMap = this.orderPerInitials(allUnorderedEntriesMap); lines.push(...this.outputEntries(orderedEntriesMap)); console.log(`Writing index file ${filePath}...`); await this.workspace.writeMdxFile({ filePath, frontMatter, bodyLines: lines }); } // ------------------------------------------------------------------------ { const filePath = `${outputFolderPath}index/classes/classes.mdx`; const permalink = 'index/classes/classes'; const frontMatter = { title: 'The Classes Index', slug: `${this.workspace.slugBaseUrl}${permalink}`, // description: '...', // TODO custom_edit_url: null, keywords: ['doxygen', 'classes', 'index'] }; const lines = []; lines.push('The classes, structs, union interfaces used by this project are:'); const classesUnorderedMap = new Map(); for (const [id, entry] of allUnorderedEntriesMap) { if (entry.objectKind === 'compound') { classesUnorderedMap.set(id, entry); } } const orderedEntries = this.orderPerInitials(classesUnorderedMap); lines.push(...this.outputEntries(orderedEntries)); console.log(`Writing index file ${filePath}...`); await this.workspace.writeMdxFile({ filePath, frontMatter, bodyLines: lines }); } // ------------------------------------------------------------------------ { const filePath = `${outputFolderPath}index/classes/functions.mdx`; const permalink = 'index/classes/functions'; const frontMatter = { title: 'The Class Functions Index', slug: `${this.workspace.slugBaseUrl}${permalink}`, // description: '...', // TODO custom_edit_url: null, keywords: ['doxygen', 'classes', 'index'] }; const lines = []; lines.push('The class member functions used by this project are:'); const classesUnorderedMap = new Map(); for (const [id, entry] of allUnorderedEntriesMap) { if (entry.kind === 'function') { classesUnorderedMap.set(id, entry); } } const orderedEntries = this.orderPerInitials(classesUnorderedMap); lines.push(...this.outputEntries(orderedEntries)); console.log(`Writing index file ${filePath}...`); await this.workspace.writeMdxFile({ filePath, frontMatter, bodyLines: lines }); } // ------------------------------------------------------------------------ { const filePath = `${outputFolderPath}index/classes/variables.mdx`; const permalink = 'index/classes/variables'; const frontMatter = { title: 'The Class Variables Index', slug: `${this.workspace.slugBaseUrl}${permalink}`, // description: '...', // TODO custom_edit_url: null, keywords: ['doxygen', 'classes', 'index'] }; const lines = []; lines.push('The class member variables used by this project are:'); const classesUnorderedMap = new Map(); for (const [id, entry] of allUnorderedEntriesMap) { if (entry.kind === 'variable') { classesUnorderedMap.set(id, entry); } } const orderedEntries = this.orderPerInitials(classesUnorderedMap); lines.push(...this.outputEntries(orderedEntries)); console.log(`Writing index file ${filePath}...`); await this.workspace.writeMdxFile({ filePath, frontMatter, bodyLines: lines }); } // ------------------------------------------------------------------------ { const filePath = `${outputFolderPath}index/classes/typedefs.mdx`; const permalink = 'index/classes/typedefs'; const frontMatter = { title: 'The Class Type Definitions Index', slug: `${this.workspace.slugBaseUrl}${permalink}`, // description: '...', // TODO custom_edit_url: null, keywords: ['doxygen', 'classes', 'index'] }; const lines = []; lines.push('The class member type definitions used by this project are:'); const classesUnorderedMap = new Map(); for (const [id, entry] of allUnorderedEntriesMap) { if (entry.kind === 'typedef') { classesUnorderedMap.set(id, entry); } } const orderedEntries = this.orderPerInitials(classesUnorderedMap); lines.push(...this.outputEntries(orderedEntries)); console.log(`Writing index file ${filePath}...`); await this.workspace.writeMdxFile({ filePath, frontMatter, bodyLines: lines }); } // ------------------------------------------------------------------------ } orderPerInitials(entriesMap) { const entriesPerInitialsMap = new Map(); for (const [id, entry] of entriesMap) { const initial = entry.name.charAt(0); let mapArray = entriesPerInitialsMap.get(initial); if (mapArray === undefined) { mapArray = []; entriesPerInitialsMap.set(initial, mapArray); } mapArray.push(entry); } const orderedMap = new Map(); const orderedInitials = Array.from(entriesPerInitialsMap.keys()).sort(); for (const initial of orderedInitials) { const unorderedArray = entriesPerInitialsMap.get(initial); assert(unorderedArray !== undefined); const orderedArray = unorderedArray.sort((a, b) => { let nameComparison = a.name.localeCompare(b.name); if (nameComparison !== 0) { return nameComparison; } nameComparison = a.longName.localeCompare(b.longName); return nameComparison; }); orderedMap.set(initial, orderedArray); } return orderedMap; } outputEntries(entriesPerInitialsMap) { const lines = []; for (const initial of entriesPerInitialsMap.keys()) { lines.push(''); lines.push(`## - ${escapeMdx(initial)} -`); lines.push(''); const mapArray = entriesPerInitialsMap.get(initial); assert(mapArray !== undefined); for (const entry of mapArray) { let kind = ''; if (entry.objectKind === 'compound') { kind = `${entry.kind} `; } if (entry.permalink !== undefined && entry.permalink.length > 0) { lines.push(`- ${escapeMdx(entry.name)}: <a href="${entry.permalink}">${kind}${escapeMdx(entry.longName)}</a>`); } else { lines.push(`- ${escapeMdx(entry.name)}: ${kind}${escapeMdx(entry.longName)}`); } } } return lines; } } // ---------------------------------------------------------------------------- export class Class extends CompoundBase { // -------------------------------------------------------------------------- constructor(collection, compoundDef) { super(collection, compoundDef); // Due to multiple-inheritance, there can be multiple parents. this.baseClassIds = new Set(); this.baseClasses = []; this.fullyQualifiedName = '?'; this.unqualifiedName = '?'; this.templateParameters = ''; this.classFullNameMdxText = '?'; // console.log('Class.constructor', util.inspect(compoundDef)) if (Array.isArray(compoundDef.baseCompoundRefs)) { for (const ref of compoundDef.baseCompoundRefs) { // console.log('component', compoundDef.id, 'has base', ref.refid) if (ref.refid !== undefined) { this.baseClassIds.add(ref.refid); } } } // Remove the template parameters. this.fullyQualifiedName = compoundDef.compoundName.replace(/<.*>/, ''); // Remove the namespaces(s). this.unqualifiedName = this.fullyQualifiedName.replace(/.*::/, ''); const index = compoundDef.compoundName.indexOf('<'); let indexNameTemplateParameters = ''; if (index >= 0) { indexNameTemplateParameters = compoundDef.compoundName.substring(index).replace(/^< /, '<').replace(/ >$/, '>'); this.templateParameters = indexNameTemplateParameters; } else if (compoundDef.templateParamList !== undefined) { indexNameTemplateParameters = this.renderTemplateParameterNamesToMdxText(compoundDef.templateParamList); } this.sidebarLabel = this.unqualifiedName; this.indexName = `${this.unqualifiedName}${indexNameTemplateParameters}`; const kind = compoundDef.kind; const kindCapitalised = kind.charAt(0).toUpperCase() + kind.slice(1).toLowerCase(); this.pageTitle = `The \`${escapeHtml(this.unqualifiedName)}\` ${kindCapitalised}`; if (compoundDef.templateParamList !== undefined) { this.pageTitle += ' Template'; } this.pageTitle += ' Reference'; assert(kindsPlurals[kind] !== undefined); const pluralKind = kindsPlurals[kind].toLowerCase(); // Turn the namespace into a hierarchical path. Keep the dot. let sanitizedPath = sanitizeHierarchicalPath(this.fullyQualifiedName.replaceAll(/::/g, '/')); if (this.templateParameters?.length > 0) { // sanitizedPath += sanitizeName(this.templateParameters) sanitizedPath += `-${crypto.hash('md5', this.templateParameters)}`; } this.relativePermalink = `${pluralKind}/${sanitizedPath}`; // Replace slash with dash. this.docusaurusId = `${pluralKind}/${flattenPath(sanitizedPath)}`; this.createSections(this.unqualifiedName); // console.log('0', compoundDef.id) // console.log('1', compoundDef.compoundName) // console.log('2', this.relativePermalink) // console.log('3', this.docusaurusId) // console.log('4', this.sidebarLabel) // console.log('5', this.indexName) // console.log('6', this.templateParameters) // console.log() } initializeLate() { super.initializeLate(); const compoundDef = this._private._compoundDef; assert(compoundDef !== undefined); let classFullName = this.fullyQualifiedName; if (this.templateParameters.length > 0) { classFullName += escapeMdx(this.templateParameters); } else { classFullName += escapeMdx(this.renderTemplateParameterNamesToMdxText(compoundDef.templateParamList)); } this.classFullNameMdxText = classFullName; if (compoundDef.templateParamList?.params !== undefined) { this.templateMdxText = escapeMdx(this.renderTemplateParametersToMdxText({ templateParamList: compoundDef.templateParamList, withDefaults: true })); } this.baseCompoundRefs = compoundDef.baseCompoundRefs; this.derivedCompoundRefs = compoundDef.derivedCompoundRefs; this.templateParamList = compoundDef.templateParamList; } // -------------------------------------------------------------------------- renderToMdxLines(frontMatter) { const lines = []; frontMatter.toc_max_heading_level = 3; const descriptionTodo = `@${this.kind} ${escapeMdx(this.compoundName)}`; const morePermalink = this.renderDetailedDescriptionToMdxLines !== undefined ? '#details' : undefined; lines.push(this.renderBriefDescriptionToMdxText({ briefDescriptionMdxText: this.briefDescriptionMdxText, todo: descriptionTodo, morePermalink })); lines.push(''); lines.push('## Declaration'); const classs = this.collection.collectionCompoundsById.get(this.id); assert(classs !== undefined); // const classFullName = this.classFullNameMdxText if (this.templateMdxText !== undefined) { lines.push(''); // Intentionally on two lines. lines.push(`<CodeBlock>template ${this.templateMdxText}`); lines.push(`${this.kind} ${this.classFullNameMdxText}</CodeBlock>`); } else { lines.push(''); lines.push(`<CodeBlock>${this.kind} ${this.classFullNameMdxText}</CodeBlock>`); } lines.push(...this.renderIncludesIndexToMdxLines()); if (this.kind === 'class' || this.kind === 'struct') { if (this.baseCompoundRefs !== undefined) { const baseCompoundRefs = new Map(); for (const baseCompoundRef of this.baseCompoundRefs) { if (!baseCompoundRefs.has(baseCompoundRef.text)) { baseCompoundRefs.set(baseCompoundRef.text, baseCompoundRef); } } lines.push(''); if (baseCompoundRefs.size > 1) { lines.push(`## Base ${kindsPlurals[this.kind]?.toLowerCase()}`); } else { lines.push(`## Base ${this.kind}`); } lines.push(''); lines.push('<MembersIndex>'); for (const baseCompoundRef of baseCompoundRefs.values()) { // console.log(util.inspect(baseCompoundRef, { compact: false, depth: 999 })) if (baseCompoundRef.refid !== undefined) { const baseClass = this.collection.collectionCompoundsById.get(baseCompoundRef.refid); if (baseClass !== undefined) { lines.push(...baseClass.renderIndexToMdxLines()); continue; } } const itemName = escapeMdx(baseCompoundRef.text); lines.push(''); lines.push('<MembersIndexItem'); lines.push(` type="${this.kind}"`); lines.push(` name={<>${itemName}</>}>`); lines.push('</MembersIndexItem>'); } lines.push(''); lines.push('</MembersIndex>'); } else if ('baseClassIds' in classs && classs.baseClassIds.size > 0) { lines.push(''); if (classs.baseClassIds.size > 1) { lines.push(`## Base ${kindsPlurals[this.kind]?.toLowerCase()}`); } else { lines.push(`## Base ${this.kind}`); } lines.push(''); lines.push('<MembersIndex>'); for (const baseClassId of classs.baseClassIds) { const baseClass = this.collection.collectionCompoundsById.get(baseClassId); if (baseClass !== undefined) { // console.log(util.inspect(derivedCompoundDef, { compact: false, depth: 999 })) lines.push(...baseClass.renderIndexToMdxLines()); } } lines.push(''); lines.push('</MembersIndex>'); } if (this.derivedCompoundRefs !== undefined) { lines.push(''); lines.push(`## Derived ${kindsPlurals[this.kind]}`); lines.push(''); lines.push('<MembersIndex>'); for (const derivedCompoundRef of this.derivedCompoundRefs) { // console.log(util.inspect(derivedCompoundRef, { compact: false, depth: 999 })) if (derivedCompoundRef.refid !== undefined) { const derivedClass = this.collection.collectionCompoundsById.get(derivedCompoundRef.refid); if (derivedClass !== undefined) { lines.push(...derivedClass.renderIndexToMdxLines()); } else { if (this.collection.workspace.pluginOptions.verbose) { console.warn('Derived class id', derivedCompoundRef.refid, 'not a defined class'); } const itemName = escapeMdx(derivedCompoundRef.text.trim()); lines.push(''); lines.push('<MembersIndexItem'); lines.push(` type="${this.kind}"`); lines.push(` name={<>${itemName}</>}>`); lines.push('</MembersIndexItem>'); } } else { const itemName = escapeMdx(derivedCompoundRef.text.trim()); lines.push(''); lines.push('<MembersIndexItem'); lines.push(` type="${this.kind}"`); lines.push(` name={<>${itemName}</>}>`); lines.push('</MembersIndexItem>'); } } lines.push(''); lines.push('</MembersIndex>'); } else if ('derivedClassIds' in classs && classs.childrenIds.length > 0) { lines.push(''); lines.push(`## Derived ${kindsPlurals[this.kind]}`); lines.push(''); lines.push('<MembersIndex>'); for (const derivedClassId of classs.childrenIds) { const derivedClass = this.collection.collectionCompoundsById.get(derivedClassId); if (derivedClass !== undefined) { // console.log(util.inspect(derivedCompoundDef, { compact: false, depth: 999 })) lines.push(...derivedClass.renderIndexToMdxLines()); } else { console.warn('Derived class id', derivedClassId, 'not a class'); } } lines.push(''); lines.push('</MembersIndex>'); } } lines.push(...this.renderInnerIndicesToMdxLines({ suffixes: [] })); lines.push(...this.renderSectionIndicesToMdxLines()); lines.push(...this.renderDetailedDescriptionToMdxLines({ briefDescriptionMdxText: this.briefDescriptionMdxText, detailedDescriptionMdxText: this.detailedDescriptionMdxText, todo: descriptionTodo, showHeader: true, showBrief: !this.hasSect1InDescription })); if (this.locationMdxText !== undefined) { lines.push(this.locationMdxText); } lines.push(...this.renderSectionsToMdxLines()); lines.push(...this.renderGeneratedFromToMdxLines()); return lines; } renderIndexToMdxLines() { // console.log(util.inspect(this, { compact: false, depth: 999 })) const lines = []; const workspace = this.collection.workspace; const permalink = workspace.getPagePermalink(this.id); const itemType = this.kind; const itemName = `<a href="${permalink}">${escapeMdx(this.indexName)}</a>`; lines.push(''); lines.push('<MembersIndexItem'); lines.push(` type="${itemType}"`); if (itemName.includes('<') || itemName.includes('&')) { lines.push(` name={<>${itemName}</>}>`); } else { lines.push(` name="${itemName}">`); } const morePermalink = this.renderDetailedDescriptionToMdxLines !== undefined ? `${permalink}/#details` : undefined; const briefDescriptionMdxText = this.briefDescriptionMdxText; if ((briefDescriptionMdxText ?? '').length > 0) { lines.push(this.renderBriefDescriptionToMdxText({ briefDescriptionMdxText, morePermalink })); } lines.push('</MembersIndexItem>'); return lines; } } // ---------------------------------------------------------------------------- //# sourceMappingURL=classes-vm.js.map