UNPKG

@ainc/script

Version:

Script compiler for typescript

230 lines (186 loc) 5.49 kB
/** ***************************************** * Created by edonet@163.com * Created on 2022-01-01 11:47:45 ***************************************** */ 'use strict'; /** ***************************************** * 加载依赖 ***************************************** */ import { Table } from '@ainc/stdout'; /** ***************************************** * 解析规则 ***************************************** */ interface Rule { type: 'array' | 'string' | 'number' | 'boolean'; name: string; alias?: string; default?: unknown; description?: string; } /** ***************************************** * 解析规则 ***************************************** */ type Rules = TypedRecord<Partial<Rule>> /** ***************************************** * 选项类型 ***************************************** */ interface Types { array: string[]; string: string; number: number; boolean: boolean; } /** ***************************************** * 配置类型 ***************************************** */ type TypedData<T> = { [K in keyof T]?: T[K] extends { type: keyof Types } ? Types[T[K]['type']] : unknown; }; /** ***************************************** * 解数对象 ***************************************** */ export type Argv<T> = TypedData<T> & { argv: string[] }; /** ***************************************** * 信息 ***************************************** */ export interface Info { name: string; usage?: string; version?: string; description?: string; } /** ***************************************** * 遍历参数 ***************************************** */ function each(argv: string[], callback: (key: string, args: string[]) => void): void { let key = ''; let args: string[] = []; for (let idx = 0, len = argv.length; idx < len; idx ++) { const value = argv[idx]; if (value.charAt(0) === '-') { // 执行回调 (key || args.length) && callback(key, args); // 更新值 key = value; args = []; } else { args.push(value); } } // 执行回调 (key || args.length) && callback(key, args); } /** ***************************************** * 参数解析器 ***************************************** */ export class Parser<T extends Rules> { /** 解析器配置 */ private rules: Map<string, Rule>; /** 初始化对象 */ public constructor(options: T) { const names = Object.keys(options); const rules = new Map<string, Rule>(); // 遍历配置 names.forEach(name => { const rule: Rule = { name, type: 'string', ...options[name] }; // 添加规则 rule.name && rules.set(rule.name, rule); rule.alias && rules.set(rule.alias, rule); }); // 更新规则 this.rules = rules; } /** 解析参数 */ public parse(argv: string[]): Argv<T> { const rest: string[] = []; const result = { argv: rest } as TypedRecord; // 遍历参数 each(argv, (key, args) => { // 未命名参数 if (!key) { return rest.push(...args); } // 获取规则 const rule = ( key.startsWith('--') ? this.rules.get(key.slice(2)) : key.charAt(0) === '-' ? this.rules.get(key.slice(1)) : null ); // 未定义解析规则 if (!rule) { return rest.push(...args); } // 解析参数类型 switch (rule.type) { case 'boolean': result[rule.name] = true; break; case 'string': result[rule.name] = args.shift() || ''; break; case 'number': result[rule.name] = parseInt(args.shift() || '0') || 0; break; case 'array': result[rule.name] = (result[rule.name] as string[] || []).concat(args); return; default: break; } // 添加剩余参数 args.length && rest.push(...args); }); // 返回结果 return result as Argv<T>; } /** 打印帮助信息 */ public print(info: Info): void { const { name, usage, version, description } = info; const keys = new Set<string>(['_']); const table = new Table(); // 打印信息 console.log(`\n Usage: ${name} ${usage || ''}`); version && console.log(` Version: ${version}`); description && console.log(`\n ${description}`); // 生成选项列表 this.rules.forEach(({ name, alias, description }) => { if (!name || keys.has(name)) { return; } // 添加映射 keys.add(name); // 获取别名 alias = alias && alias !== '_' ? '-' + alias + ', ' : ' '; // 添加项 table.add(` ${alias}--${name}`, description || ''); }); // 打印选项 if (table.length) { table.print('\n Options:'); } // 结束信息 console.log(''); } }