Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:50:16 +08:00
commit 60cd68dbbc
8 changed files with 876 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
// Get available fonts via npx figlet
function getAvailableFonts() {
try {
const output = execSync('npx figlet --list', { encoding: 'utf-8' });
// Skip the "Available fonts:" header and filter out empty lines
return output.trim().split('\n').slice(1).map(line => line.trim()).filter(line => line);
} catch (error) {
throw new Error(`Failed to fetch available fonts: ${error.message}`);
}
}
// Generate ASCII art via npx figlet
function generateAsciiArt(text, font) {
try {
// If no font specified, let figlet use its default (standard)
const fontFlag = font ? `-f "${font}"` : '';
const output = execSync(`npx figlet ${fontFlag} "${text}"`, { encoding: 'utf-8' });
return output;
} catch (error) {
throw new Error(`Failed to generate ASCII art: ${error.message}`);
}
}
// Parse command line arguments
function parseArguments() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: node process-file.js <file-path>');
console.error('');
console.error('This script processes <figlet> tags in files and replaces them with ASCII art.');
console.error('');
console.error('Syntax: <figlet font="font-name">Text to convert</figlet>');
console.error(' <figlet>Text to convert</figlet> (uses standard font by default)');
process.exit(1);
}
return args[0];
}
// Detect comment style from the line
function detectCommentStyle(line) {
const trimmed = line.trimStart();
if (trimmed.startsWith('//')) {
return '//';
} else if (trimmed.startsWith('#')) {
return '#';
} else if (trimmed.startsWith('--')) {
return '--';
} else if (trimmed.startsWith('/*')) {
return '/*';
}
return null; // plain text, no comment style
}
// Format ASCII art with comment prefixes
function formatWithComments(asciiArt, commentStyle) {
// Remove trailing empty lines
const lines = asciiArt.split('\n').filter((line, index, arr) => {
// Keep the line if it's not empty, or if it's not the last line
return line.trim().length > 0 || index < arr.length - 1;
});
if (!commentStyle) {
return asciiArt.trimEnd();
}
switch (commentStyle) {
case '//':
return lines.map(line => `// ${line}`).join('\n');
case '#':
return lines.map(line => `# ${line}`).join('\n');
case '--':
return lines.map(line => `-- ${line}`).join('\n');
case '/*':
// For block comments, each line gets " * " prefix
return lines.map(line => ` * ${line}`).join('\n');
default:
return asciiArt.trimEnd();
}
}
// Main function
function main() {
const filePath = parseArguments();
// Verify file exists
if (!fs.existsSync(filePath)) {
console.error(`❌ Error: File not found: ${filePath}`);
process.exit(1);
}
// Read file content
let content = fs.readFileSync(filePath, 'utf-8');
// Get available fonts for validation
let availableFonts;
try {
availableFonts = getAvailableFonts();
} catch (err) {
console.error(`❌ Error fetching available fonts: ${err.message}`);
process.exit(1);
}
// Find all figlet tags: <figlet font="...">text</figlet> or <figlet>text</figlet>
const tagRegex = /<figlet(?:\s+font="([^"]+)")?>(.+?)<\/figlet>/g;
let match;
const replacements = [];
// Add 'standard' to available fonts since figlet always accepts it as the default
const validFonts = new Set([...availableFonts, 'standard']);
while ((match = tagRegex.exec(content)) !== null) {
const fullTag = match[0];
const fontName = match[1] || null; // null means use figlet's default
const textToConvert = match[2];
const tagIndex = match.index;
// Validate font if specified
if (fontName && !validFonts.has(fontName)) {
console.error(`❌ Error: Invalid font "${fontName}" in tag: ${fullTag}`);
console.error(` Run 'node list-fonts.js' to see available fonts.`);
process.exit(1);
}
// Detect comment style from the line containing the tag
const lineStart = content.lastIndexOf('\n', tagIndex) + 1;
const lineEnd = content.indexOf('\n', tagIndex);
const endPos = lineEnd === -1 ? content.length : lineEnd;
const line = content.substring(lineStart, endPos);
const commentStyle = detectCommentStyle(line);
replacements.push({
tag: fullTag,
font: fontName,
text: textToConvert,
commentStyle
});
}
// If no tags found, inform user
if (replacements.length === 0) {
console.log(` No figlet tags found in: ${filePath}`);
return;
}
// Process each replacement
for (const replacement of replacements) {
try {
const asciiArt = generateAsciiArt(replacement.text, replacement.font);
const formatted = formatWithComments(asciiArt, replacement.commentStyle);
// If there's a comment style and the tag is preceded by that comment on the same line,
// we need to replace the comment prefix + tag together (not just the tag).
// This prevents double comment prefixes (e.g., "# #" instead of "#")
if (replacement.commentStyle) {
const escapedComment = replacement.commentStyle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const escapedTag = replacement.tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Match optional leading whitespace, the comment prefix, optional space, then the tag
const commentPrefixPattern = new RegExp(`(^|\\n)\\s*${escapedComment}\\s*${escapedTag}`);
if (commentPrefixPattern.test(content)) {
// Replace both the comment prefix and tag with the formatted ASCII art
content = content.replace(commentPrefixPattern, `$1${formatted}`);
} else {
// No leading comment prefix, just replace the tag
content = content.replace(replacement.tag, formatted);
}
} else {
// No comment style, just replace the tag
content = content.replace(replacement.tag, formatted);
}
} catch (err) {
console.error(`❌ Error generating ASCII art for "${replacement.text}": ${err.message}`);
process.exit(1);
}
}
// Write modified content back to file
fs.writeFileSync(filePath, content, 'utf-8');
console.log(`✅ Successfully processed ${replacements.length} figlet tag(s) in: ${filePath}`);
}
main();