Click here to Skip to main content
14,971,280 members
Articles / Programming Languages / Markdown
Article
Posted 17 Oct 2020

Stats

5.4K views
163 downloads
6 bookmarked

Easy Command Line Parser

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
18 Oct 2020CPOL6 min read
ECLP in C#
Easy Command Line Parser is a simple way to use self defined verbs to parse your command line with to its appropriate type.

Image 1

ECLP {Easy Command Line Parser}

Introduction

I am glad to share with you my Command Line Parser that I call ECLP for (Easy Command Line Parser).

Background

Yet another command line script ?!!

Well, actually, there's dozens of it, I have to say, but as far as I know, none suited me or at least I did not find something that matched my criteria.

I just looked for something that WORKED and is easy to understand and configure, and especially for my game development scene.

So I just made my own version of command line parser and I wanted to share it with you that has some particular points:

  1. Does use a self type parsing
  2. Uses two ways of parsing, Regex by default and a side by side check to parse
  3. Easy to use

So let's see.

Using the Code

Description

This command line parser uses two ways of parsing.

Either from a string command or from an array like the one passed to the main method as parameters.

Just reference ECLP by nuget or by downloading the latest release and add it to your references.

Install-Package ECLP -Version 1.4.2

Licensed with MIT.

Verbs

ECLP defines five types of arguments as Verbs.

Means that a command line can have five different patterns of arguments as below:

Type 1) Args are common arguments, could be any referenced primitive type like string, int, bool, float, and char.

Example: "hello 5 true 3,5 T"

Argument is parsed to its appropriate type, otherwise, it returns the default value "string".

Pay attention that positions of arguments is important so you can identify them in the Args list by its index.

A float value should have a comma as separator and not a dot, 3,6 is good, 3.5 is wrong.

Type 2) Flags are string type arguments without values, prefixed with double dash -- "--verbose --start --friendlyfire".

Value is not parsed to a specific type and it's always a string type.

Usually considered as if they are a boolean, I mean if a flag is set, then it's equivalent to true by meaning.

Type 3) Properties are a property with value prefixed with -p "-p driver=steave".

Value is parsed to its appropriate type, otherwise, the default format is string.

Type 4) Collections is a property with a collection of values separated by Pipe |.

Should be prefixed with -c "-c players=steave|john|clark -c ages=21|15|30" players is the name of the property, and steave, john, clark are a list of object values.

Type 5) ExCollections for Extended Collections, it's a property with a collection of sub properties that have a value.

Should be prefixed with -xc "-xc players=steave:21|john:15|clark:30 -xc adresses=Japan:Tokyo|USA:Washington".

Properties are separated by Pipe |, and sub property name and its value are separated by double point :.

01. Verbs

A command line can have as many different verbs as you like but by preference, better keep the Args in the first of the command so that you can't identify them easily.

C#
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();
        }
    }
}

Assuming it's a console project, the cr variable contains five collections, each one is dedicated to a specific verb.

Image 2

Reading Verbs

Manually

Here is a way to display all data by fetching them one by one:

C#
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();
        }
    }
}

Image 3

Args

Args verb is different from the other patterns, because others are identified by a name like Properties, Collection, ExCollection and Flags are a string types and then we can easily associate it since the value is known (a check value is sufficient).

But Args are different because they don't have the same type and don't have an identification name, they could be any type, like string, int, bool, float or char.

So there is no way to identify an arg unless you fix their positions and make each index for a specific task.

Assuming this command line "James 29 false a".

If we parse this command line, we will get four arguments typed like so : string, int, bool and char.

You have to make your application ready to associate the first index for the username which will equal to James.

Second argument to his age which will equal to 29.

Third for activated or not for example which will be false "suspended or wait activation or whatever".

Fourth for a user rank, in this case is a that could be for Admin, you can imagine other scenarios like m for moderator ...

C#
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();
        }
    }
}

Image 4

If you remember, I told you that it's better to keep Args verbs in the beginning of the command line.

Well it's not an obligation, assuming this command line:

"James 29 false -p language=En a"

As before, we used the same command line as before but we just add another verb as a property before the last argument.

In this case, the order is not affected by the none arg verb, I mean that the last argument "a" has the index of 3 not 4 (first argument has the 0 index) as you may expect, because the -p language=En is not accounted in the arguments's index.

So nothing will be changed in the last code we wrote because the 'a' will always have the four index in total.

In the next chapter, we will see another way to read the command using events.

02. EventHandler

In the previous chapter, you have seen a way to read arguments by fetching them one by one.

This approach could be expensive especially when you have many arguments as you may see the code in the previous chapter with many loops.

So maybe we want to execute a code especially when an argument is set.

This is what we are going to do using EventHandler.

C#
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 function can use 2 ways of parsing, either using Regex or a Side by side words check algorithm.

Using Regex

Is the default way of parsing when you Parse() function without specifying usingRegex bool parameter inside the constructor because it has a true value by default.

C#
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();
        }
    }
}

Side by Side Words Check

Is another way of parsing more pertinent but use a long code behind the scenes.

C#
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);
        }
    }
}

The way there's two ways of parsing is because Regex is not all the time used by some third party as it was the case for me.

I used ECLP in a third party application that uses its own mono modified assembly which doesn't use regex at all and then I need to find another way to handle parsing.

Regex sometimes can't match result in some weird scenarios as maybe it needs to improve regex pattern which is not my best.

Hope this article will be helpful for some and if you want a more elegant presentation, I will let my original post below

https://melharfi.github.io/repos/ECLP/description.html

Points of Interest

  • .NET technologies and game development scenes

History

  • 18th October, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mohssine EL HARFI
Software Developer (Junior)
Morocco Morocco
I am someone fascinated by gaming, new technologies and programming.
My goal is to advance in programming skills.

Comments and Discussions

 
QuestionFYI - Command Line API - Maybe not easy one :) Pin
Vaso Elias19-Oct-20 0:29
MemberVaso Elias19-Oct-20 0:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.