This commit is contained in:
Cheney
2024-02-27 16:54:41 +08:00
commit 76f10255fe
51 changed files with 14240 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
module.exports = class CliCmdLine {
/**
* 带值的命令格式
* @param parser 解析器
* @param cmdLine 命令描述格式
* @param flags 标记
* @param params 参数
* @param position 位置参数
*/
constructor(parser, cmdLine, flags, params, position) {
this.parser = parser
this.cmdline = cmdLine
this.flags = new Set([])
this.params = {}
let i = 0;
for ( let pname in cmdLine.params ) {
let param = cmdLine.params[pname];
if ( param.isFlag ) {
if ( param.needValue ){
if ( params[pname] ) {
this.params[pname] = params[pname]
} else {
if ( ! param.isOptional ) {
throw "命令解析错误,缺少必填标记参数。" + pname
}
}
} else {
if ( flags.has(pname) ) {
this.flags.add( pname )
} else {
if ( ! param.isOptional ) {
throw "命令解析错误,缺少必填标记。" + pname
}
}
}
} else {
if ( i < position.length ) {
this.params[ pname ] = position[ i++ ] ;
} else {
if ( ! param.isOptional ) {
throw "命令解析错误,缺少必填位置参数。" + pname;
}
}
}
}
}
/**
* 是否已经绑定了回调函数?
*/
canEval(){
return this.parser.canEval( this )
}
async eval(params){
return this.parser.eval( this )
}
getParamValue(name, def){
if ( name.startsWith("--") ) {
name = name.substring(2);
}
let v = this.params[ name ];
if ( null == v ) {
return def
} else {
return v;
}
}
hasFlag(name){
return this.flags.has(name)
}
}
+9
View File
@@ -0,0 +1,9 @@
module.exports = class CliParam {
constructor( name, isOptional, isFlag, needValue, options) {
this.name = name;
this.isOptional = isOptional;
this.isFlag = isFlag;
this.needValue = needValue;
this.options = options;
}
}
+96
View File
@@ -0,0 +1,96 @@
const CliParam = require("./CliParam")
/**
* 特色命令行交互格式
* 有且只有一个命令码
* 可以有多个参数
* 可以有多个标记,标记分无参/单参/多参
*/
module.exports = class CmdLine {
constructor(line, cmd, info) {
// 命令
this.cmd = cmd
// 说明
this.info = info
// 参数
this.params = {}
// 完整命令
this.line = line
}
addParams(name, param){
this.params[name] = param
}
/**
* 将命令描述解释成命令格式
* @param cmdLine
* @param info
*/
static parse(cmdLine, info) {
let pp = cmdLine.trim().split(/\s+/);
let cl = new CmdLine(cmdLine, pp[0], info);
let canFlagOptionalMode = true;
let canParamOptionalMode = true;
for ( let i = 1 ;i < pp.length; i++ ) {
let p = pp[i];
let isOptional = true;
let isFlag = false;
if ( p.startsWith("[") && p.endsWith("]")){
isOptional = true;
}
else if ( p.startsWith("<") && p.endsWith(">")){
isOptional = false;
}
else {
throw "不合法的命令格式 - " + cmdLine;
}
// 去掉两端标记
p = p.substring( 1, p.length -1 );
if( p.startsWith("--") ) {
if ( ! isOptional && ! canFlagOptionalMode ) {
throw "参数解析错误 - 必选参数必须在可选参数前面 - " + cmdLine;
}
if ( isOptional ) {
canFlagOptionalMode = false;
}
p = p.substring(2);
isFlag = true;
}
else if( p.startsWith("-") ) {
if ( ! isOptional && ! canFlagOptionalMode ) {
throw "参数解析错误 - 必选参数必须在可选参数前面 - " + cmdLine;
}
if ( isOptional ) {
canFlagOptionalMode = false;
}
// 无值
p = p.substring(1);
cl.addParams( p, new CliParam( p, isOptional , true) );
continue;
} else {
if ( ! isOptional && ! canParamOptionalMode ) {
throw "参数解析错误 - 必选参数必须在可选参数前面 - " + cmdLine;
}
if ( isOptional ) {
canParamOptionalMode = false;
}
}
cl.addParams( p, new CliParam( p, isOptional , isFlag, true ) );
}
return cl;
}
}
+7
View File
@@ -0,0 +1,7 @@
const os = require('os');
module.exports = class Config {
constructor() {
// 获取系统换行符
this.crlf = os.EOL;
}
}
+94
View File
@@ -0,0 +1,94 @@
# argv
Cli 命令解析工具
- 支持标记、参数、位置参数三种输入。建议用法:标记和参数可以认为带有默认值,位置参数不带有默认值。位置参数又分为<必须>和[可选]两种,<必须>参数需要在[可选]参数前面。
- 支持输出 help
- 支持命令参数转发。即将 --- 之后的命令完全转发给其他命令,兼容其他形式的参数格式。
java 版本:https://github.com/CheneyWong/argv.java
## 使用示例
1. 最简使用
```shell
function main() {
// 注册命令
const { CliCmdParser } = require("./index")
const parser = new CliCmdParser("测试程序");
parser.addCmdLine("version", "查看版本");
parser.addCmdLine("help", "查看帮助");
// 解析用户输入命令
let cmd = parser.parse(args);
// 执行命令
if ("help".equals(cmd.getCmd())) {
// 辅助打印命令提示符
System.out.println(parser.help());
} else {
// 其他命令执行需自行实现
System.out.println(cmd);
}
}
```
2. 添加一个待参数的命令
```shell
parser.addCmdLine("config <key> [value]", "查询/设置配置");
```
```
[] 表示可选参数
<> 表示必选参数
```
在程序中获取参数
```shell
String key = line.getParamValue("key");
String value = line.getParamValue("value");
```
获取程序时附带默认值
```shell
String value = line.getParamValue("value", "xxx");
```
3. 增加一个参数标记
```shell
parser.addCmdLine("tcp <--ip> <--port> <--headlen> [--timeout] <filepath>", "tcp 报文发送");
```
```
[] 表示可选参数
<> 表示必选参数
- 表示标记,即无值属性
-- 表示属性
```
- 必选参数必须在可选参数前面
# TODO
- [x] getParamValue 基本类型自动转换
- [ ] 可选参数
# 暂时无法实现
- [x] 参数说明,因为参数中目前不能有空格。
- [x] 参数默认值,因为参数中目前不能有空格。临时方法,在代码中实现。
## 使用
### Maven
```
<dependency>
<groupId>club.fullstack</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>argv</artifactId>
</dependency>
```
+7
View File
@@ -0,0 +1,7 @@
/**
* 自定义命名格式解析器
*/
const CliCmdParser = require("./parser")
module.exports = {
CliCmdParser
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "argv",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
}
}
+110
View File
@@ -0,0 +1,110 @@
const CmdLine = require("./CmdLine")
const CliCmdLine = require("./CliCmdLine")
const Config = require("./config")
const gConfig = new Config();
module.exports = class CliCmdParser {
constructor(name) {
// 软件名称
this.name = name
this.cmdLines = {}
this.funcs = {}
}
/**
* 声明一条命令
* @param cmdLine 命令模板
* @param info 命令说明
* @param func 响应函数
*/
addCmdLine(cmdLine, info, func) {
let cl = CmdLine.parse(cmdLine, info);
if( this.cmdLines[ cl.cmd ] ) {
throw "重复注册的命令 " + cl.cmd
}
this.cmdLines[ cl.cmd ] = cl;
if ( func ) {
this.funcs[ cl.cmd ] = func;
}
return cl;
}
/**
* 打印命令帮助
*/
help(){
let bs = ""
if( this.name ) {
bs += this.name + gConfig.crlf
}
for ( let cmd in this.cmdLines ) {
bs = bs.concat("\t")
.concat( this.cmdLines[ cmd ].line )
.concat("\t")
.concat( this.cmdLines[ cmd ].info )
.concat( gConfig.crlf );
}
return bs;
}
canEval( cliCmdLine ){
return !! this.funcs[ cliCmdLine.cmdline.cmd ];
}
async eval( cliCmdLine ){
let func = this.funcs[ cliCmdLine.cmdline.cmd ];
if ( null == func ) {
throw "没找到命令相应函数";
}
return await func( cliCmdLine );
}
parse(argv){
if ( ! argv || argv.length < 1 ) {
return null;
}
if ( argv[0].startsWith("-") ) {
throw "入参命令格式错误 : 没有入参 cmd"
}
if ( ! this.cmdLines[ argv[0] ] ) {
return null;
}
// 标记: 去除 - 之后的值
const flags = new Set([])
// 命名参数: 去除 -- 之后的值为 key,之后值为 value
const params = {}
// 位置参数:值
const position = []
for (let i = 1; i < argv.length; i++ ) {
if ( argv[i].startsWith("--") ) {
if ( i + 1 == argv.length || argv[ i + 1 ].startsWith("-") ) {
throw "flag 格式错误" + argv[i];
}
params[ argv[i].substring(2) ] = argv[++i];
} else if ( argv[i].startsWith("-") ) {
flags.add( argv[i].substring(1) );
} else {
position.push( argv[i] );
}
}
let line = this.cmdLines[ argv[0] ];
// 整理
// line 是藐视
// flags, params, position 是值
return new CliCmdLine( this, line, flags, params, position);
}
}
+20
View File
@@ -0,0 +1,20 @@
const { CliCmdParser } = require("./index")
const parser = new CliCmdParser("测试程序");
parser.addCmdLine("config <key> [value]", "查询/设置配置");
parser.addCmdLine("killport <port>", "释放端口", function (params) {
console.log("killport", params)
});
let cmd = parser.parse(process.argv.slice(2))
if ( ! cmd ) {
console.log( parser.help() )
} else {
if ( cmd.canEval() ){
cmd.eval()
} else {
console.log( parser.help() )
}
}