markmv
Version:
TypeScript CLI for markdown file operations with intelligent link refactoring
149 lines • 5.03 kB
JavaScript
import remarkParse from 'remark-parse';
import { unified } from 'unified';
import { visit } from 'unist-util-visit';
/**
* Utility for generating table of contents from markdown content.
*
* This class extracts headings from markdown content and generates formatted table of contents with
* proper indentation and anchor links.
*
* @category Utils
*
* @example
* Basic usage
* ```typescript
* const generator = new TocGenerator();
* const content = `# Title\n## Section 1\n### Subsection\n## Section 2`;
* const result = await generator.generateToc(content);
*
* console.log(result.toc);
* // Output:
* // - [Title](#title)
* // - [Section 1](#section-1)
* // - [Subsection](#subsection)
* // - [Section 2](#section-2)
* ```
*
* @example
* With custom options
* ```typescript
* const generator = new TocGenerator();
* const options = {
* minDepth: 2,
* maxDepth: 4,
* includeLineNumbers: true
* };
* const result = await generator.generateToc(content, options);
* ```
*/
export class TocGenerator {
processor = unified().use(remarkParse);
/**
* Generate table of contents from markdown content.
*
* @param content - Markdown content to analyze
* @param options - Configuration options
*
* @returns Promise resolving to TOC result
*/
async generateToc(content, options = {}) {
const { minDepth = 1, maxDepth = 6, includeLineNumbers = false, slugify = this.defaultSlugify, } = options;
const tree = this.processor.parse(content);
const headings = [];
// Extract headings from AST
visit(tree, 'heading', (node) => {
if (!node.position)
return;
// Skip headings outside depth range
if (node.depth < minDepth || node.depth > maxDepth)
return;
// Extract text from all child nodes recursively
const text = this.extractTextFromNodes(node.children);
if (text.trim()) {
headings.push({
level: node.depth,
text: text.trim(),
slug: slugify(text.trim()),
line: node.position.start.line,
});
}
});
const toc = this.formatToc(headings, includeLineNumbers);
return {
toc,
headings,
};
}
/**
* Extract headings from markdown content without generating TOC.
*
* @param content - Markdown content to analyze
* @param options - Configuration options
*
* @returns Promise resolving to array of headings
*/
async extractHeadings(content, options = {}) {
const result = await this.generateToc(content, options);
return result.headings;
}
/**
* Format headings into a table of contents string.
*
* @param headings - Array of extracted headings
* @param includeLineNumbers - Whether to include line numbers
*
* @returns Formatted TOC markdown
*/
formatToc(headings, includeLineNumbers) {
if (headings.length === 0) {
return '';
}
const lines = [];
const minLevel = Math.min(...headings.map((h) => h.level));
for (const heading of headings) {
const indent = ' '.repeat(heading.level - minLevel);
const link = `[${heading.text}](#${heading.slug})`;
const lineInfo = includeLineNumbers ? ` (line ${heading.line})` : '';
lines.push(`${indent}- ${link}${lineInfo}`);
}
return lines.join('\n');
}
/**
* Extract text content from AST nodes recursively.
*
* @param nodes - Array of AST nodes
*
* @returns Combined text content
*/
extractTextFromNodes(nodes) {
return nodes
.map((node) => {
if (node.type === 'text') {
return node.value || '';
}
else if (node.children && Array.isArray(node.children)) {
// Recursively extract text from children, filtering for valid node structure
const childNodes = node.children.filter((child) => typeof child === 'object' && child !== null && 'type' in child);
return this.extractTextFromNodes(childNodes);
}
return '';
})
.join('');
}
/**
* Default slugify function that converts text to URL-friendly anchors.
*
* @param text - Text to slugify
*
* @returns URL-friendly slug
*/
defaultSlugify(text) {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '-') // Replace special characters with hyphens
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Replace multiple hyphens with single
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
}
}
//# sourceMappingURL=toc-generator.js.map