Command Line Parsing
Introduction
From time to time, I am still asked to create a console application for someone, and most of the time PowerShell is still not accepted by my clients for various reasons. Just like method parameters can become unwieldy in an under-designed system as requirements change, so can command line arguments. To minimize the mess, regular expressions can be used to access a command/arguments pattern (e.g. EncryptionUtil.exe /Decrypt /File="c:\My Documents\foo.xml"). Furthermore, if argument are passed in as a key/value pairing (e.g. /File="c:\My Documents\foo.xml"), the order of the arguments should not matter.
Determining the Command
Introduction
From time to time, I am still asked to create a console application for someone, and most of the time PowerShell is still not accepted by my clients for various reasons. Just like method parameters can become unwieldy in an under-designed system as requirements change, so can command line arguments. To minimize the mess, regular expressions can be used to access a command/arguments pattern (e.g. EncryptionUtil.exe /Decrypt /File="c:\My Documents\foo.xml"). Furthermore, if argument are passed in as a key/value pairing (e.g. /File="c:\My Documents\foo.xml"), the order of the arguments should not matter.
Determining the Command
////// Parses command line arguments to find the "command" /// that should be executed. /// /// The command line arguments. ////// The command that is expected to be executed. /// Note: The value has been set to lower case (Invariant). /// ////// Throws and exception if more than one command is found. /// static string GetArgumentCommand(string[] args) { const string regex = "^/([^/][^=]+?)$"; var reg = new Regex(regex); string result = null; //Loop through arguments looking for mataches to regex. foreach (var arg in args) { //Argument didn't match, continue to next arg. if (!reg.IsMatch(arg)) continue; //More than one argument matched. Throw an error. if (!string.IsNullOrEmpty(result)) throw new TooManyCommandsException(); //Argument matched var match = reg.Match(arg); //Check for null/empty var validMatch = match.Groups.Count >= 2 && match.Groups[1].Value != null; result = validMatch ? match.Groups[1].Value.ToLowerInvariant() : null; } return result; }
Determining the Arguments
////// Parses command line arguments and creates a dictionary /// of key/value pair arguments. /// Note: If a key is used more than once, the first one is used. /// /// The command line arguments. ////// Dictionary containing arguements. /// Note: All keys have been set to lower case (Invariant). /// static Dictionary<string, string> GetArgumentDictionary(string[] args) { const string regex = "^/([^/][\\S]+?)\\=([\\S\\s]+?)$"; var reg = new Regex(regex); var dic = new Dictionary<string, string>(); //Loop through arguments looking for mataches to regex. foreach (var arg in args) { //Argument didn't match, continue to next arg. if (!reg.IsMatch(arg)) continue; //Argument matched var match = reg.Match(arg); //Determining Key var validMatch = match.Groups.Count >= 3 && match.Groups[1].Value != null; var key = validMatch ? match.Groups[1].Value.ToLowerInvariant() : null; if (string.IsNullOrEmpty(key)) continue; //Check for duplicate key. if (dic.ContainsKey(key)) continue; //Determine value validMatch = match.Groups.Count >= 3; var val = validMatch ? match.Groups[2].Value : null; //Add key/value pair to dictionary. dic.Add(key, val); } return dic; }
Putting it Together
////// Application entry point. /// /// The command line arguments. static void Main(string[] args) { try { //Get the command and dictionary of arguments. var cmd = GetArgumentCommand(args); var dic = GetArgumentDictionary(args); //Execute appropriate command. switch (cmd) //remember commands are lower case { case "foo": ExecuteFooCommand(dic); break; case "bar": ExecuteBarCommand(dic); break; case "?": //redundant, but makes the point. default: DisplayHelpText(dic); break; } } catch (Exception ex) { Console.WriteLine("An exception occured."); Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } } ////// Executes the foo command. /// /// Dictionary containing key/value arguements. static void ExecuteFooCommand(Dictionary<string, string> dic) { //Add appropriate code here. } ////// Executes the bar command. /// /// Dictionary containing key/value arguements. static void ExecuteBarCommand(Dictionary<string, string> dic) { //Add appropriate code here. } ////// Displays help text to the console. /// /// Dictionary containing key/value arguements. static void DisplayHelpText(Dictionary<string, string> dic) { //Add appropriate code here. }
I noticed today that there is some inconsistency in my error conditions. When there is more than one command, an exception is thrown. However, when a duplicate key exists in the key/value pairs, the first one is accepted and the second one is ignored. I believe this can lead to confusion, and have altered the duplicate key scenario to throw an exception as well.
ReplyDelete