sobota, 19 stycznia 2013

C# Command Line Arguments Parser

There is probably several good ways to parse command line arguments in C# console application. But when I encountered such problem in my work first what I thought was to stop inventing wheel once again :-)
After short research I realized that in most cases I need lightweight reusable code which I can easily adapt for current requirements. At first I found ConsoleFx library. After short code analysis I was sure that it looks really good for me but... In my opinion it's heavy: a lot of classes with really reach functionality. So that wasn't exactly what I need because I also required lightweight solution. And then I found sample of small handler which is great and you will find it below.

So how is the magic happens?
 args.Process(
                () => Console.WriteLine("Usage is switch1=value1,value2 switch2=value3"),
                new CommandLine.Switch("switch1",
                    val => Console.WriteLine("switch 1 with value {0}",
                        string.Join(" ", val))),
                new CommandLine.Switch("switch2",
                    val => Console.WriteLine("switch 2 with value {0}",
                        string.Join(" ", val)), "s1"));
        }
All of the magic happens inside the Process method, where the query tries to match the arguments against the ones we want to check for. The .Sum() aggregate method does two things. It enumerates the elements of the IQueryable, actually calling the handlers in the process. Nothing happens till then! Each of the InvokeHandler calls returns 1, so the result of the .Sum() is 0 if there were no matches (which is when we call the supplied printUsage handler).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace SampleConsoleApplication
{
    /* Simple commandline argument parser written by Ananth B.
    http://www.ananthonline.net */
    static class CommandLine
    {
        public class Switch // Class that encapsulates switch data.
        {
            public Switch(string name, Action<IEnumerable<string>> handler, string shortForm)
            {
                Name = name;
                Handler = handler;
                ShortForm = shortForm;
            }

            public Switch(string name, Action<IEnumerable<string>> handler)
            {
                Name = name;
                Handler = handler;
                ShortForm = null;
            }

            public string Name
            {
                get;
                private set;
            }
            public string ShortForm
            {
                get;
                private set;
            }
            public Action<IEnumerable<string>> Handler
            {
                get;
                private set;
            }

            public int InvokeHandler(string[] values)
            {
                Handler(values);
                return 1;
            }
        }

        /* The regex that extracts names and comma-separated values for switches 
        in the form (<switch>[="value 1",value2,...])+ */
        private static readonly Regex ArgRegex =
            new Regex(@"(?<name>[^=]+)=?((?<quoted>\""?)(?<value>(?(quoted)[^\""]+|[^,]+))\""?,?)*",
                RegexOptions.Compiled | RegexOptions.CultureInvariant |
                RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);

        private const string NameGroup = "name"; // Names of capture groups
        private const string ValueGroup = "value";

        public static void Process(this string[] args, Action printUsage, params Switch[] switches)
        {
            /* Run through all matches in the argument list and if any of the switches 
            match, get the values and invoke the handler we were given. We do a Sum() 
            here for 2 reasons; a) To actually run the handlers
            and b) see if any were invoked at all (each returns 1 if invoked).
            If none were invoked, we simply invoke the printUsage handler. */
            if ((from arg in args
                 from Match match in ArgRegex.Matches(arg)
                 from s in switches
                 where match.Success &&
                     ((string.Compare(match.Groups[NameGroup].Value, s.Name, true) == 0) ||
                     (string.Compare(match.Groups[NameGroup].Value, s.ShortForm, true) == 0))
                 select s.InvokeHandler(match.Groups[ValueGroup].Value.Split(','))).Sum() == 0)
                printUsage(); // We didn't find any switches
        }
    }
}
Author Licence
The code above is for you to do whatever you want to. Even if it blows you up (like Dr. Manhattan blows Rorschach up), don’t come back from the dead to haunt me. I would, however appreciate it if you kept the comment attributing that code to me.
My comments So far I haven't found any bugs in such code. Click here to go to my repository with appropriate unit tests

2 komentarze:

  1. In case no arguments are supplied the program should print usage info and terminate. How to achieve this?

    OdpowiedzUsuń
    Odpowiedzi
    1. Hi. Below line prints the usage (You can find it at the beggining of the post)
      () => Console.WriteLine("Usage is switch1=value1,value2 switch2=value3").

      If user won't supply any parameters program will just print usage statement. Regarding the terminate statement it depends on you. You have to check if use put all required parameters and terminate your app if not.

      Usuń