Easy Command Line Parser is a simple way to use self defined verbs to parse your command line with to its appropriate type.
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:
- Does use a self type parsing
- Uses two ways of parsing, Regex by default and a side by side check to parse
- 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.
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.
Reading Verbs
Manually
Here is a way to display all data by fetching them one by one:
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 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 ...
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;
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();
}
}
}
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
.
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)
{
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)
{
Console.WriteLine(e.Property.Key);
if (e.Property.Key == "driver")
Console.WriteLine(e.Property.Value);
}
private static void ECLP_AddedCollections
(object sender, The_Morpher.Events.AddedCollectionsEvents e)
{
Console.WriteLine(e.Collections.Key);
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)
{
Console.WriteLine(e.ExCollections.Key);
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.
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();
}
}
}
Side by Side Words Check
Is another way of parsing more pertinent but use a long code behind the scenes.
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