@ainc/script
Version:
Script compiler for typescript
230 lines (186 loc) • 5.49 kB
text/typescript
/**
*****************************************
* 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('');
}
}