init
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const os = require('os');
|
||||
module.exports = class Config {
|
||||
constructor() {
|
||||
// 获取系统换行符
|
||||
this.crlf = os.EOL;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 自定义命名格式解析器
|
||||
*/
|
||||
const CliCmdParser = require("./parser")
|
||||
module.exports = {
|
||||
CliCmdParser
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "argv",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
||||
+110
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() )
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user