简单的命令行解析器





5.00/5 (3投票s)
C# 中的 ECLP。
引言
我很高兴与您分享我的命令行解析器,我称之为 ECLP(Easy Command Line Parser)。
背景
又一个命令行脚本?!
嗯,实际上,我得说有几十个这样的工具,但据我所知,没有一个适合我,或者至少我没有找到符合我标准的。
我只是在寻找一个能用、易于理解和配置的东西,尤其适合我的游戏开发场景。
所以,我制作了我自己的命令行解析器版本,我想与您分享它的一些特殊之处。
- 使用自类型解析
- 使用两种解析方式,默认使用正则表达式,并进行并排检查以进行解析。
- 易于使用
那么,我们来看看。
Using the Code
描述
此命令行解析器使用两种解析方式。
可以从 `string` 命令解析,也可以从类似传递给 main 方法的参数数组解析。
只需通过 nuget 引用 ECLP,或下载最新版本并将其添加到您的引用中。
Install-Package ECLP -Version 1.4.2
根据 MIT 许可。
动词
ECLP 将参数定义为五种类型,称为 Verbs。
这意味着命令行可以有五种不同的参数模式,如下所示:
类型 1) Args 是通用参数,可以是任何引用的基本类型,如 `string`、`int`、`bool`、`float` 和 `char`。
示例:“`hello 5 true 3,5 T`”
参数将被解析为其适当的类型,否则将返回默认值“`string`”。
请注意,参数的位置很重要,因此您可以通过索引在 `Args` 列表中识别它们。
`float` 值应使用逗号作为分隔符,而不是点,3,6 是正确的,3.5 是错误的。
类型 2) Flags 是没有值的 `string` 类型参数,前面加上双破折号 `--`(“`--verbose --start --friendlyfire`”)。
值不会解析为特定类型,它始终是 `string` 类型。
通常被视为布尔值,我的意思是,如果一个标志被设置,那么它就等同于 `true`。
类型 3) Properties 是一个带有值的属性,前面加上 `-p`(“`-p driver=steave`”)。
值将被解析为其适当的类型,否则,默认格式为 `string`。
类型 4) Collections 是一个带有由管道符 `|` 分隔的值的集合属性。
应加上前缀 `-c`(“`-c players=steave|john|clark -c ages=21|15|30`”,`players` 是属性名,`steave`、`john`、`clark` 是对象值的列表)。
类型 5) ExCollections(扩展集合)是一个带有由管道符 `|` 分隔的子属性集合的属性,这些子属性都有一个值。
应加上前缀 `-xc`(“`-xc players=steave:21|john:15|clark:30 -xc adresses=Japan:Tokyo|USA:Washington`”)。
属性之间用管道符 `|` 分隔,子属性名和其值之间用双冒号 `:` 分隔。
01. Verbs
命令行可以包含任意数量的不同的 verbs,但建议将 `Args` 放在命令的最前面,以便轻松识别。
using System.Windows.Forms;
using The_Morpher;
namespace ConsoleApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
string commandLine = "start 5 3.6 true T --verbose
--noClip --showStats -p driver=steave -p age=30
-c players=steave|john|clark -xc players=steave:21|john:15|clark:30";
ECLP eCLP = new ECLP(commandLine);
CommandResult cr = eCLP.Parse();
Console.ReadKey();
}
}
}
假设这是一个控制台项目,`cr` 变量包含五个集合,每个集合都用于特定的 verb。
读取 Verbs
手动
以下是通过逐个获取数据来显示所有数据的方法:
using System;
using System.Linq;
using The_Morpher;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string commandLine = "start 5 3,6 true T --verbose --noClip
--showStats -p driver=steave -p age=30 -c players=steave|john|clark
-xc players=steave:21|john:15|clark:30";
ECLP eCLP = new ECLP(commandLine);
CommandResult result = eCLP.Parse();
#region Args
foreach (var arg in result.Args)
Console.WriteLine("Arg " + arg.ToString() +
" has a " + arg.GetType() + " type");
#endregion
Console.WriteLine(Environment.NewLine);
#region Flags
foreach (var flag in result.Flags)
Console.WriteLine("Flag " + flag.ToString() + " is set");
#endregion
#region Properties
foreach (var property in result.Properties)
{
string name = property.Key;
object value = property.Value;
Console.WriteLine("Property " + name + " has a value " +
value + " and value type is " + value.GetType());
}
#endregion
Console.WriteLine(Environment.NewLine);
#region Collections
foreach (var collection in result.Collections)
{
string name = collection.Key;
object[] values = collection.Value;
Console.WriteLine("Collection " + name + " has " +
values.Count() + " value(s)");
int counter = 0;
foreach(object value in values)
Console.WriteLine("\tValue " + ++counter + " is " +
value + " and its type is " + value.GetType());
}
#endregion
Console.WriteLine(Environment.NewLine);
#region ExCollections
foreach (var exCol in result.ExCollections)
{
string name = exCol.Key;
var values = exCol.Value;
Console.WriteLine("ExCollection " + name + " has " +
values.Count() + " value(s)");
foreach (var value in values)
{
string subName = value.Key;
var subValue = value.Value;
Console.WriteLine("\tSub Property " + subName +
" has value " + subValue + " and value type is " +
subValue.GetType());
}
}
#endregion
Console.ReadKey();
}
}
}
Args
`Args` verb 与其他模式不同,因为其他模式由一个名称标识,如 `Properties`、`Collection`、`ExCollection` 和 `Flags` 是 `string` 类型,因此我们可以轻松地将它们关联起来,因为值是已知的(一个检查值就足够了)。
但是 Args 不同,因为它们没有相同的类型,也没有标识名称,它们可以是任何类型,如 `string`、`int`、`bool`、`float` 或 `char`。
因此,除非您固定它们的位置并为每个索引分配特定任务,否则没有办法识别一个 arg。
假设这个命令行是“`James 29 false a`”。
如果我们解析这个命令行,我们将得到四个类型化的参数:`string`、`int`、`bool` 和 `char`。
您需要让您的应用程序准备好将第一个索引与用户名关联,该用户名将等于 `James`。
第二个参数与他的年龄相关,将等于 `29`。
第三个参数用于激活或不激活,例如将是 `false`(“暂停或等待激活或其他什么”)。
第四个参数用于用户级别,在本例中是 `a`,可以代表 `Admin`,您可以想象其他场景,如 `m` 代表 Moderator……
using System;
using System.Collections.Generic;
using System.Linq;
using The_Morpher;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string commandLine = "James 29 false a";
ECLP eCLP = new ECLP(commandLine);
var result = eCLP.Parse();
if(result.Args.Count != 4)
{
Console.WriteLine("Command should have 4 arguments");
return;
}
string username = result.Args[0].ToString();
int age = 20; // default value
if(result.Args[1].GetType() == typeof(int))
{
age = (int)result.Args[1];
}
else
{
Console.WriteLine("Second argument for age should be an integer");
return;
}
bool activated;
if(result.Args[2].GetType() == typeof(bool))
{
activated = (bool)result.Args[2];
}
else
{
Console.WriteLine("Third argument for activation should be a boolean");
return;
}
char rank;
if(result.Args[3].GetType() == typeof(char))
{
rank = (char)result.Args[3];
}
else
{
Console.WriteLine("Fourth argument for account rank should be a char");
return;
}
Console.WriteLine("username is {0}", username);
Console.WriteLine("age is {0}", age);
Console.WriteLine("activated is {0}", activated);
Console.WriteLine("Account Rank {0}", rank);
Console.ReadKey();
}
}
}
如果您还记得,我告诉过您最好将 `Args` verbs 放在命令行开头。
嗯,这并非强制规定,假设这个命令行
“James 29 false -p language=En a”
和之前一样,我们使用了和之前一样的命令行,但我们在最后一个参数之前添加了一个新的属性 verb。
在这种情况下,顺序不受 **非 arg verb** 的影响,我的意思是最后一个参数“`a`”的索引是 3 而不是 4(第一个参数索引为 0),因为 `-p language=En` 不计入参数的索引。
所以我们之前写的代码没有任何改变,因为“`a`”的总索引将始终是四个。
在下一章,我们将看到另一种使用事件读取命令的方式。
02. EventHandler
在上一章中,您已经看到了一种逐个获取参数的方法。
这种方法可能很昂贵,尤其是在您有很多参数的情况下,正如您可能在上一章的代码中看到的那样,有很多循环。
所以,也许我们希望在设置一个参数时执行一段代码。
这就是我们将使用 `EventHandler` 来做的事情。
using System;
using System.Collections.Generic;
using System.Linq;
using The_Morpher;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string commandLine = "start 5 3,6 true T --verbose --noClip
--showStats -p driver=steave -p age=30 -c players=steave|john|clark
-xc players=steave:21|john:15|clark:30";
ECLP eCLP = new ECLP(commandLine);
eCLP.AddedArgs += ECLP_AddedArgs;
eCLP.AddedCollections += ECLP_AddedCollections;
eCLP.AddedExCollections += ECLP_AddedExCollections;
eCLP.AddedFlags += ECLP_AddedFlags;
eCLP.AddedProperties += ECLP_AddedProperties;
eCLP.Parse();
Console.ReadKey();
}
private static void ECLP_AddedArgs
(object sender, The_Morpher.Events.AddedArgsEvents e)
{
Console.WriteLine
("Argument is " + e.Arg + " and its type is " + e.Arg.GetType());
}
private static void ECLP_AddedFlags
(object sender, The_Morpher.Events.AddedFlagsEvents e)
{
// To show value
Console.WriteLine(e.Flag);
if (e.Flag == "--verbose")
Console.WriteLine("Verbose mode is activated");
}
private static void ECLP_AddedProperties
(object sender, The_Morpher.Events.AddedPropertiesEvents e)
{
// To show key
Console.WriteLine(e.Property.Key);
// to do job when a specific property is triggered
if (e.Property.Key == "driver")
Console.WriteLine(e.Property.Value);
// this will show steave
}
private static void ECLP_AddedCollections
(object sender, The_Morpher.Events.AddedCollectionsEvents e)
{
// To show key, Collection name
Console.WriteLine(e.Collections.Key);
// to loop true sub values
foreach (object o in e.Collections.Value)
{
Console.WriteLine
("sub property is " + o + " and its type is " + o.GetType());
}
}
private static void ECLP_AddedExCollections
(object sender, The_Morpher.Events.AddedExCollectionsEvents e)
{
// To show key, ExCollection's name
Console.WriteLine(e.ExCollections.Key);
// loop true the sub properties
foreach (KeyValuePair sub in e.ExCollections.Value)
{
Console.WriteLine("sub property name is " +
sub.Key + " and it's value is " + sub.Value + "
and value type is " + sub.Value.GetType());
}
}
}
}
03. Parse
`Parse` 函数可以使用两种解析方式:使用 **Regex** 或 **Side by side**(并排)单词检查算法。
使用 Regex
当您在构造函数中不指定 `usingRegex` 布尔参数而调用 `Parse()` 函数时,这是默认的解析方式,因为它默认值为 `true`。
using System.Windows.Forms;
using The_Morpher;
namespace ConsoleApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
string commandLine = "start 5 3.6 true T --verbose --noClip
--showStats -p driver=steave -p age=30 -c players=steave|john|clark
-xc players=steave:21|john:15|clark:30";
ECLP eCLP = new ECLP(commandLine);
CommandResult cr = eCLP.Parse();
// or
// CommandResult cr = eCLP.Parse(true);
// is the same suing regex
Console.ReadKey();
}
}
}
并排单词检查
是另一种更相关的解析方式,但它在后台使用了很长的代码。
using System;
using System.Linq;
using The_Morpher;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string commandLine = "start 5 3,6 true T --verbose --noClip
--showStats -p driver=steave -p age=30 -c players=steave|john|clark
-xc players=steave:21|john:15|clark:30";
ECLP eCLP = new ECLP(commandLine);
CommandResult result = eCLP.Parse(false);
}
}
}
之所以存在两种解析方式,是因为 Regex 并非总是被某些第三方使用,就像我曾经遇到的情况一样。
我在一个使用自己修改过的 mono 程序集的第三方应用程序中使用了 ECLP,该程序集根本不使用 regex,因此我需要找到另一种方法来处理解析。
Regex **有时** 在某些奇怪的情况下无法匹配结果,因为可能需要改进正则表达式模式,这并非我的强项。
希望这篇文章对某些人有所帮助,如果您想要一个更优雅的演示,我将把我的原始帖子放在下面:
https://melharfi.github.io/repos/ECLP/description.html
关注点
- .NET 技术和游戏开发场景
历史
- 2020年10月18日:初始版本