UNPKG

@ry-krystal/kicad-converter

Version:

专业的KiCad符号文件与JSON互转工具

277 lines (276 loc) 8.02 kB
/** * CLI工具函数集 * 提供命令行解析、日志记录、格式化输出等功能 */ import { existsSync, readdirSync, statSync } from 'fs'; import { join, extname } from 'path'; /** * 解析命令行参数 */ export function parseCliArgs() { const args = process.argv.slice(2); const options = {}; const positionalArgs = []; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { const key = arg.slice(2); const nextArg = args[i + 1]; if (nextArg && !nextArg.startsWith('-')) { options[key] = nextArg; i++; // 跳过下一个参数 } else { options[key] = true; } } else if (arg.startsWith('-')) { const flags = arg.slice(1); // 处理短选项 for (const flag of flags) { switch (flag) { case 'i': options.input = args[i + 1]; i++; break; case 'o': options.output = args[i + 1]; i++; break; case 'm': options.mode = args[i + 1]; i++; break; case 'e': options.engine = args[i + 1]; i++; break; case 'b': options.batch = true; break; case 'r': options.recursive = true; break; case 'v': options.verbose = true; break; case 'q': options.quiet = true; break; case 'h': options.help = true; break; default: console.warn(`未知的选项: -${flag}`); } } } else { positionalArgs.push(arg); } } // 处理位置参数 if (positionalArgs.length > 0 && !options.input) { options.input = positionalArgs[0]; } if (positionalArgs.length > 1 && !options.output) { options.output = positionalArgs[1]; } return options; } /** * 创建日志记录器 */ export function createLogger(verbose = false, quiet = false) { return { info: (msg) => { if (!quiet) { console.log(`ℹ️ ${msg}`); } }, success: (msg) => { if (!quiet) { console.log(`✅ ${msg}`); } }, warn: (msg) => { if (!quiet) { console.warn(`⚠️ ${msg}`); } }, error: (msg) => { console.error(`❌ ${msg}`); }, debug: (msg) => { if (verbose && !quiet) { console.log(`🔍 ${msg}`); } }, verbose: (msg) => { if (verbose && !quiet) { console.log(`📝 ${msg}`); } } }; } /** * 格式化输出 */ export function formatOutput(data, format = 'json') { switch (format) { case 'json': return JSON.stringify(data, null, 2); case 'compact': return JSON.stringify(data); case 'table': // 简单的表格格式化 if (Array.isArray(data)) { const headers = Object.keys(data[0] || {}); let result = headers.join('\\t') + '\\n'; result += headers.map(() => '---').join('\\t') + '\\n'; data.forEach(item => { result += headers.map(h => item[h] || '').join('\\t') + '\\n'; }); return result; } return JSON.stringify(data, null, 2); default: return String(data); } } /** * 文件大小格式化 */ export function formatFileSize(bytes) { const sizes = ['B', 'KB', 'MB', 'GB']; if (bytes === 0) return '0 B'; const i = Math.floor(Math.log(bytes) / Math.log(1024)); const size = (bytes / Math.pow(1024, i)).toFixed(2); return `${size} ${sizes[i]}`; } /** * 进度条显示 */ export class ProgressBar { total; current = 0; width = 30; lastUpdate = 0; constructor(total, width = 30) { this.total = total; this.width = width; } update(current, label = '') { this.current = current; const now = Date.now(); // 限制更新频率 if (now - this.lastUpdate < 100) return; this.lastUpdate = now; const percentage = Math.min(current / this.total, 1); const filled = Math.floor(percentage * this.width); const empty = this.width - filled; const bar = '█'.repeat(filled) + '░'.repeat(empty); const percent = (percentage * 100).toFixed(1); process.stdout.write(`\\r[${bar}] ${percent}% ${label}`); if (current >= this.total) { process.stdout.write('\\n'); } } finish(message = '完成') { this.update(this.total, message); } } /** * 颜色输出工具 */ const colorCodes = { reset: '\\x1b[0m', bright: '\\x1b[1m', dim: '\\x1b[2m', red: '\\x1b[31m', green: '\\x1b[32m', yellow: '\\x1b[33m', blue: '\\x1b[34m', magenta: '\\x1b[35m', cyan: '\\x1b[36m', white: '\\x1b[37m' }; export const colors = { ...colorCodes, // 便捷方法 error: (text) => `${colorCodes.red}${text}${colorCodes.reset}`, success: (text) => `${colorCodes.green}${text}${colorCodes.reset}`, warning: (text) => `${colorCodes.yellow}${text}${colorCodes.reset}`, info: (text) => `${colorCodes.blue}${text}${colorCodes.reset}`, dimText: (text) => `${colorCodes.dim}${text}${colorCodes.reset}`, brightText: (text) => `${colorCodes.bright}${text}${colorCodes.reset}` }; /** * 验证文件路径 */ export function validatePath(path, mustExist = true) { if (!path || typeof path !== 'string') { return { valid: false, message: '路径不能为空' }; } if (mustExist) { if (!existsSync(path)) { return { valid: false, message: `文件或目录不存在: ${path}` }; } } return { valid: true }; } /** * 检查文件扩展名 */ export function checkFileExtension(filename, validExtensions) { const ext = filename.toLowerCase().split('.').pop(); return validExtensions.includes(ext || ''); } /** * 安全的文件名生成 */ export function sanitizeFilename(filename) { return filename.replace(/[^a-zA-Z0-9._-]/g, '_'); } /** * 递归查找文件 */ export function findFiles(dir, extensions, recursive = false) { const results = []; if (!existsSync(dir)) { return results; } const items = readdirSync(dir); for (const item of items) { const fullPath = join(dir, item); const stat = statSync(fullPath); if (stat.isDirectory() && recursive) { results.push(...findFiles(fullPath, extensions, recursive)); } else if (stat.isFile()) { const ext = extname(item).toLowerCase(); if (extensions.includes(ext)) { results.push(fullPath); } } } return results; } /** * 时间格式化 */ export function formatDuration(ms) { if (ms < 1000) { return `${ms.toFixed(2)}ms`; } else if (ms < 60000) { return `${(ms / 1000).toFixed(2)}s`; } else { const minutes = Math.floor(ms / 60000); const seconds = ((ms % 60000) / 1000).toFixed(2); return `${minutes}m ${seconds}s`; } }