@xpack/docusaurus-plugin-doxygen
Version:
A Docusaurus plugin to integrate Doxygen into a Docusaurus project.
421 lines • 18.3 kB
JavaScript
/*
* 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 { CompoundBase } from './compound-base-vm.js';
import { CollectionBase } from './collection-base.js';
import { escapeMdx, flattenPath, sanitizeHierarchicalPath } from '../utils.js';
// ----------------------------------------------------------------------------
export class FilesAndFolders extends CollectionBase {
// folders: Folders
// --------------------------------------------------------------------------
constructor(workspace) {
super(workspace);
this.topLevelFolders = [];
this.topLevelFiles = [];
// this.compoundsById = new Map()
this.compoundFoldersById = new Map();
this.compoundFilesById = new Map();
this.filesByPath = new Map();
}
// --------------------------------------------------------------------------
addChild(compoundDef) {
if (compoundDef.kind === 'file') {
const file = new File(this, compoundDef);
this.collectionCompoundsById.set(file.id, file);
this.compoundFilesById.set(file.id, file);
return file;
}
else if (compoundDef.kind === 'dir') {
const folder = new Folder(this, compoundDef);
this.collectionCompoundsById.set(folder.id, folder);
this.compoundFoldersById.set(folder.id, folder);
return folder;
}
else {
throw new Error(`kind ${compoundDef.kind} not implemented in ${this.constructor.name}`);
}
}
// --------------------------------------------------------------------------
createCompoundsHierarchies() {
// Recreate files and folders hierarchies.
// console.log(this.compoundsById.size)
for (const [folderId, folder] of this.compoundFoldersById) {
for (const childFolderId of folder.childrenFolderIds) {
const childFolder = this.compoundFoldersById.get(childFolderId);
assert(childFolder !== undefined);
if (this.workspace.pluginOptions.debug) {
console.log('childFolderId', childFolderId, childFolder.compoundName, 'has parent', folderId, folder.compoundName);
}
childFolder.parent = folder;
folder.children.push(childFolder);
}
for (const childFileId of folder.childrenFileIds) {
const childFile = this.compoundFilesById.get(childFileId);
if (childFile !== undefined) {
if (this.workspace.pluginOptions.debug) {
console.log('childFileId', childFileId, childFile.compoundName, 'has parent', folderId, folder.compoundName);
}
childFile.parent = folder;
folder.children.push(childFile);
}
else {
console.warn(childFileId, 'not a child of', folder.id);
}
}
}
for (const [fileId, file] of this.compoundFilesById) {
this.workspace.compoundsById.set(fileId, file);
}
for (const [folderId, folder] of this.compoundFoldersById) {
if (folder.parent === undefined) {
if (this.workspace.pluginOptions.debug) {
console.log('topFolderId:', folderId);
}
this.topLevelFolders.push(folder);
}
}
for (const [fileId, file] of this.compoundFilesById) {
if (file.parent === undefined) {
// console.log('topFileId:', fileId)
this.topLevelFiles.push(file);
}
const path = file.locationFilePath;
assert(path !== undefined);
this.filesByPath.set(path, file);
if (this.workspace.pluginOptions.debug) {
// console.log('filesByPath.set', path, file)
console.log('filesByPath.set', path);
}
}
for (const [folderId, folder] of this.compoundFoldersById) {
let parentPath = '';
if (folder.parent !== undefined) {
parentPath = `${this.getRelativePathRecursively(folder.parent)}/`;
}
// console.log(folder.compoundName)
folder.relativePath = `${parentPath}${folder.compoundName}`;
const sanitizedPath = sanitizeHierarchicalPath(folder.relativePath);
folder.relativePermalink = `folders/${sanitizedPath}`;
folder.docusaurusId = `folders/${flattenPath(sanitizedPath)}`;
// console.log('0', folder.id)
// console.log('1', folder.compoundName)
// console.log('2', folder.relativePermalink)
// console.log('3', folder.docusaurusId)
// console.log('4', folder.sidebarLabel)
// console.log('5', folder.indexName)
// console.log()
}
// Cannot be done in each object, since it needs the hierarchy.
for (const [fileId, file] of this.compoundFilesById) {
let parentPath = '';
if (file.parent !== undefined) {
parentPath = `${this.getRelativePathRecursively(file.parent)}/`;
}
// console.log(file.compoundName)
file.relativePath = `${parentPath}${file.compoundName}`;
const sanitizedPath = sanitizeHierarchicalPath(file.relativePath);
file.relativePermalink = `files/${sanitizedPath}`;
file.docusaurusId = `files/${flattenPath(sanitizedPath)}`;
// console.log('0', file.id)
// console.log('1', file.compoundName)
// console.log('2', file.relativePermalink)
// console.log('3', file.docusaurusId)
// console.log('4', file.sidebarLabel)
// console.log('5', file.indexName)
// console.log()
}
}
getRelativePathRecursively(folder) {
let parentPath = '';
if (folder.parent !== undefined) {
parentPath = `${this.getRelativePathRecursively(folder.parent)}/`;
}
return `${parentPath}${folder.compoundName}`;
}
// --------------------------------------------------------------------------
createSidebarItems() {
// Add folders & files to the sidebar.
// Top level folders & files are added below a Files category
const filesCategory = {
type: 'category',
label: 'Files',
link: {
type: 'doc',
id: `${this.workspace.sidebarBaseId}files/index`
},
collapsed: true,
items: []
};
for (const folder of this.topLevelFolders) {
const item = this.createFolderSidebarItemRecursively(folder);
if (item !== undefined) {
filesCategory.items.push(item);
}
}
for (const file of this.topLevelFiles) {
const item = this.createFileSidebarItem(file);
if (item !== undefined) {
filesCategory.items.push(item);
}
}
return [filesCategory];
}
createFolderSidebarItemRecursively(folder) {
if (folder.sidebarLabel === undefined) {
return undefined;
}
const categoryItem = {
type: 'category',
label: folder.sidebarLabel,
link: {
type: 'doc',
id: `${this.workspace.sidebarBaseId}${folder.docusaurusId}`
},
collapsed: true,
items: []
};
for (const fileOrFolder of folder.children) {
if (fileOrFolder instanceof Folder) {
const item = this.createFolderSidebarItemRecursively(fileOrFolder);
if (item !== undefined) {
categoryItem.items.push(item);
}
}
}
for (const fileOrFolder of folder.children) {
if (fileOrFolder instanceof File) {
const item = this.createFileSidebarItem(fileOrFolder);
if (item !== undefined) {
categoryItem.items.push(item);
}
}
}
return categoryItem;
}
createFileSidebarItem(file) {
if (file.sidebarLabel === undefined) {
return undefined;
}
const docItem = {
type: 'doc',
label: file.sidebarLabel,
id: `${this.workspace.sidebarBaseId}${file.docusaurusId}`
};
return docItem;
}
// --------------------------------------------------------------------------
createMenuItems() {
const menuItem = {
label: 'Files',
to: `${this.workspace.menuBaseUrl}files/`
};
return [menuItem];
}
// --------------------------------------------------------------------------
async generateIndexDotMdxFile() {
const filePath = `${this.workspace.outputFolderPath}files/index.mdx`;
const permalink = 'files';
const frontMatter = {
title: 'The Files & Folders Reference',
slug: `${this.workspace.slugBaseUrl}${permalink}`,
// description: '...', // TODO
custom_edit_url: null,
keywords: ['doxygen', 'files', 'folders', 'reference']
};
const lines = [];
lines.push('The files & folders that contributed content to this site are:');
lines.push('');
lines.push('<TreeTable>');
for (const folder of this.topLevelFolders) {
lines.push(...this.generateIndexMdxFileRecursively(folder, 0));
}
for (const file of this.topLevelFiles) {
lines.push(...this.generateFileIndexMdx(file, 1));
}
lines.push('');
lines.push('</TreeTable>');
console.log(`Writing files index file ${filePath}...`);
await this.workspace.writeMdxFile({
filePath,
frontMatter,
bodyLines: lines
});
}
generateIndexMdxFileRecursively(folder, depth) {
// console.log(util.inspect(folder, { compact: false, depth: 999 }))
const lines = [];
const label = escapeMdx(folder.compoundName);
const permalink = this.workspace.getPagePermalink(folder.id);
assert(permalink !== undefined && permalink.length > 1);
lines.push('');
lines.push('<TreeTableRow');
lines.push(' itemIconClass="doxyIconFolder"');
lines.push(` itemLabel="${label}"`);
lines.push(` itemLink="${permalink}"`);
lines.push(` depth="${depth}">`);
if (folder.briefDescriptionMdxText !== undefined && folder.briefDescriptionMdxText.length > 0) {
lines.push(folder.briefDescriptionMdxText.replace(/[.]$/, ''));
}
lines.push('</TreeTableRow>');
if (folder.children.length > 0) {
for (const childFileOrFolder of folder.children) {
if (childFileOrFolder instanceof Folder) {
lines.push(...this.generateIndexMdxFileRecursively(childFileOrFolder, depth + 1));
}
}
for (const childFileOrFolder of folder.children) {
if (childFileOrFolder instanceof File) {
lines.push(...this.generateFileIndexMdx(childFileOrFolder, depth + 1));
}
}
}
return lines;
}
generateFileIndexMdx(file, depth) {
// console.log(util.inspect(file, { compact: false, depth: 999 }))
const lines = [];
const label = escapeMdx(file.compoundName);
const permalink = this.workspace.getPagePermalink(file.id);
assert(permalink !== undefined && permalink.length > 1);
lines.push('');
lines.push('<TreeTableRow');
lines.push(' itemIconClass="doxyIconFile"');
lines.push(` itemLabel="${label}"`);
lines.push(` itemLink="${permalink}"`);
lines.push(` depth="${depth}">`);
if (file.briefDescriptionMdxText !== undefined && file.briefDescriptionMdxText.length > 0) {
lines.push(file.briefDescriptionMdxText.replace(/[.]$/, ''));
}
lines.push('</TreeTableRow>');
return lines;
}
}
// ----------------------------------------------------------------------------
export class Folder extends CompoundBase {
// --------------------------------------------------------------------------
constructor(collection, compoundDef) {
super(collection, compoundDef);
// childrenIds & children - not used
this.childrenFileIds = [];
this.childrenFiles = [];
this.childrenFolderIds = [];
this.childrenFolders = [];
this.relativePath = '';
// console.log('folder:', util.inspect(compoundDef))
// console.log('folder:', compoundDef.compoundName)
if (Array.isArray(compoundDef.innerDirs)) {
for (const ref of compoundDef.innerDirs) {
// console.log('component', compoundDef.id, 'has child folder', ref.refid)
this.childrenIds.push(ref.refid);
this.childrenFolderIds.push(ref.refid);
}
}
if (Array.isArray(compoundDef.innerFiles)) {
for (const ref of compoundDef.innerFiles) {
// console.log('component', compoundDef.id, 'has child file', ref.refid)
this.childrenIds.push(ref.refid);
this.childrenFileIds.push(ref.refid);
}
}
this.sidebarLabel = compoundDef.compoundName ?? '?';
this.indexName = this.sidebarLabel;
this.pageTitle = `The \`${this.sidebarLabel}\` Folder Reference`;
this.createSections();
}
// --------------------------------------------------------------------------
renderToMdxLines(frontMatter) {
const lines = [];
const descriptionTodo = ` ${escapeMdx(this.relativePath)}`;
const morePermalink = this.renderDetailedDescriptionToMdxLines !== undefined ? '#details' : undefined;
lines.push(this.renderBriefDescriptionToMdxText({
briefDescriptionMdxText: this.briefDescriptionMdxText,
todo: descriptionTodo,
morePermalink
}));
lines.push(...this.renderInnerIndicesToMdxLines({
suffixes: ['Dirs', 'Files']
}));
lines.push(...this.renderSectionIndicesToMdxLines());
lines.push(...this.renderDetailedDescriptionToMdxLines({
briefDescriptionMdxText: this.briefDescriptionMdxText,
detailedDescriptionMdxText: this.detailedDescriptionMdxText,
todo: descriptionTodo,
showHeader: true,
showBrief: !this.hasSect1InDescription
}));
lines.push(...this.renderSectionsToMdxLines());
return lines;
}
}
// ----------------------------------------------------------------------------
export class File extends CompoundBase {
constructor(collection, compoundDef) {
super(collection, compoundDef);
this.relativePath = '';
this.listingLineNumbers = new Set();
// console.log('file:', compoundDef.compoundName)
// The compoundName is the actual file name, without path.
assert(compoundDef.compoundName !== undefined);
this.sidebarLabel = compoundDef.compoundName;
this.indexName = this.sidebarLabel;
this.pageTitle = `The \`${this.sidebarLabel}\` File Reference`;
this.createSections();
}
initializeLate() {
super.initializeLate();
const compoundDef = this._private._compoundDef;
assert(compoundDef !== undefined);
this.programListing = compoundDef.programListing;
// Keep track of line number, since not all lines referred exist and
// this might result in broken links.
if (this.programListing?.codelines !== undefined) {
for (const codeline of this.programListing?.codelines) {
if (codeline.lineno !== undefined) {
this.listingLineNumbers.add(codeline.lineno);
}
}
}
}
// --------------------------------------------------------------------------
renderToMdxLines(frontMatter) {
const lines = [];
const descriptionTodo = ` ${escapeMdx(this.relativePath)}`;
const morePermalink = this.renderDetailedDescriptionToMdxLines !== undefined ? '#details' : undefined;
lines.push(this.renderBriefDescriptionToMdxText({
briefDescriptionMdxText: this.briefDescriptionMdxText,
todo: descriptionTodo,
morePermalink
}));
lines.push(...this.renderIncludesIndexToMdxLines());
lines.push(...this.renderInnerIndicesToMdxLines({
suffixes: ['Namespaces', 'Classes']
}));
lines.push(...this.renderSectionIndicesToMdxLines());
lines.push(...this.renderDetailedDescriptionToMdxLines({
briefDescriptionMdxText: this.briefDescriptionMdxText,
detailedDescriptionMdxText: this.detailedDescriptionMdxText,
todo: descriptionTodo,
showHeader: true,
showBrief: !this.hasSect1InDescription
}));
lines.push(...this.renderSectionsToMdxLines());
if (this.programListing !== undefined) {
lines.push('');
lines.push('## File Listing');
lines.push('');
lines.push('The file content with the documentation metadata removed is:');
lines.push(...this.collection.workspace.renderElementToMdxLines(this.programListing));
}
return lines;
}
}
// ----------------------------------------------------------------------------
//# sourceMappingURL=files-and-folders-vm.js.map