@ry-krystal/kicad-converter
Version:
专业的KiCad符号文件与JSON互转工具
277 lines (276 loc) • 8.02 kB
JavaScript
/**
* 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`;
}
}