65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的命令行解析器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2020年10月18日

CPOL

6分钟阅读

viewsIcon

16522

downloadIcon

339

C# 中的 ECLP。

ECLP {Easy Command Line Parser}

引言

我很高兴与您分享我的命令行解析器,我称之为 ECLP(Easy Command Line Parser)。

背景

又一个命令行脚本?!

嗯,实际上,我得说有几十个这样的工具,但据我所知,没有一个适合我,或者至少我没有找到符合我标准的。

我只是在寻找一个能用、易于理解和配置的东西,尤其适合我的游戏开发场景。

所以,我制作了我自己的命令行解析器版本,我想与您分享它的一些特殊之处。

  1. 使用自类型解析
  2. 使用两种解析方式,默认使用正则表达式,并进行并排检查以进行解析。
  3. 易于使用

那么,我们来看看。

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日:初始版本
© . All rights reserved.