Archive

Posts Tagged ‘command line arguments’

C#: Simple Command Line Arguments Parser

March 22nd, 2012 5 comments

    Working with SharePoint applications I very often develop small console applications for adjusting lists, list items, content types and so on. To pass some parameters into those utilities I employ the command line arguments. Then inside applications I deal with an array of arguments – string[] args:

class Program
{
    static void Main(string[] args)
    {
        // get passed parameters from args
    }
}

I usually use the following notation: -paramName paramValue. Here a parameter name with the leading minus sign (‘-‘) is followed by a parameter value. If the presence of value isn’t assumed, only -paramName is used. For example, the following command line

-url "http://dotnetfollower.com" -useElevatedPrivileges

directs an utility to “process the web site http://dotnetfollower.com and use the elevated objects for that”.

To simplify parameters fetching from the args array I’ve implemented an auxiliary class – InputArguments, which parses the arguments and fills a dictionary out with proper key-value pairs. The dictionary allows accessing a parameter value in the way like this: InputArguments[“-url”] or InputArguments[“url”]. The source code of the InputArguments class is listed below:

using System;
using System.Collections.Generic;

namespace Common
{
    public class InputArguments
    {
        #region fields & properties
        public const string DEFAULT_KEY_LEADING_PATTERN = "-";

        protected Dictionary<string, string> _parsedArguments   = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        protected readonly string            _keyLeadingPattern;

        public string this [string key]
        {
            get { return GetValue(key); }
            set
            {
                if (key != null)
                    _parsedArguments[key] = value;
            }
        }
        public string KeyLeadingPattern
        {
            get { return _keyLeadingPattern; }
        }
        #endregion

        #region public methods
        public InputArguments(string[] args, string keyLeadingPattern)
        {
            _keyLeadingPattern = !string.IsNullOrEmpty(keyLeadingPattern) ? keyLeadingPattern : DEFAULT_KEY_LEADING_PATTERN;

            if (args != null && args.Length > 0)
                Parse(args);
        }
        public InputArguments(string[] args) : this(args, null)
        {
        }

        public bool Contains(string key)
        {
            string adjustedKey;
            return ContainsKey(key, out adjustedKey);
        }

        public virtual string GetPeeledKey(string key)
        {
            return IsKey(key) ? key.Substring(_keyLeadingPattern.Length) : key;
        }
        public virtual string GetDecoratedKey(string key)
        {
            return !IsKey(key) ? (_keyLeadingPattern + key) : key;
        }
        public virtual bool IsKey(string str)
        {
            return str.StartsWith(_keyLeadingPattern);
        }
        #endregion

        #region internal methods
        protected virtual void Parse(string[] args)
        {
            for (int i = 0; i < args.Length; i ++)
            {
                if(args[i] == null) continue;
                
                string key = null;
                string val = null;

                if(IsKey(args[i])) 
                {
                    key = args[i];

                    if(i + 1 < args.Length && !IsKey(args[i + 1]))
                    {
                        val = args[i + 1];
                        i ++;
                    }
                }
                else
                    val = args[i];

                // adjustment
                if (key == null)
                {
                    key = val;
                    val = null;
                }
                _parsedArguments[key] = val;
            }
        }

        protected virtual string GetValue(string key)
        {
            string adjustedKey;
            if(ContainsKey(key, out adjustedKey))
                return _parsedArguments[adjustedKey];

            return null;
        }

        protected virtual bool ContainsKey(string key, out string adjustedKey)
        {
            adjustedKey = key;

            if (_parsedArguments.ContainsKey(key))
                return true;

            if (IsKey(key))
            {
                string peeledKey = GetPeeledKey(key);
                if(_parsedArguments.ContainsKey(peeledKey))
                {
                    adjustedKey = peeledKey;
                    return true;
                }
                return false;
            }

            string decoratedKey = GetDecoratedKey(key);
            if(_parsedArguments.ContainsKey(decoratedKey))
            {
                adjustedKey = decoratedKey;
                return true;
            }
            return false;
        }
        #endregion
    }
}

As you can see, the InputArguments stores the parsed parameters and theirs values in the _parsedArguments dictionary, which is accessible through the special indexer. Pay attention to the ContainsKey methods, which attempts different variations (with or without leading symbols) of parameter name in case the passed name isn’t presented among the stored arguments. So, passing the

-url "http://dotnetfollower.com" -useElevatedPrivileges

arguments into an application, the InputArguments can be used as follows:

static void Main(string[] args)
{
    InputArguments arguments = new InputArguments(args);

    Console.WriteLine(arguments["-url"]);
    if (arguments.Contains("useElevatedPrivileges"))
        Console.WriteLine("useElevatedPrivileges is set");
}

The above mentioned command line arguments will be turned into the following key-value pairs:

{["-url", "http://dotnetfollower.com"]}
{["-useElevatedPrivileges", null]}

Below is the more complex command line borrowed from the WSPBuilder and slightly modified to have orphaned values without a foregoing parameter name:

"some orphaned value" -ExpandTypes false -BuildSafeControls true  
-WSPName mySPSolution.wsp  -Outputpath "C:\WSPDeployment\myApp" 
-SolutionId d403bb18-c5f2-4b43-9d55-12b256a6295a 
-SolutionPath "C:\WSPDeployment\myApp" -TraceLevel Verbose 
-DLLReferencePath "C:\WSPDeployment\ReferencedAssemblies"

This arguments will be turned into the following:

{["some orphaned value", null]}
{["-ExpandTypes", "false"]}
{["-BuildSafeControls", "true"]}
{["-WSPName", "mySPSolution.wsp"]}
{["-Outputpath", "C:\WSPDeployment\myApp"]}
{["-SolutionId", "d403bb18-c5f2-4b43-9d55-12b256a6295a"]}
{["-SolutionPath", "C:\WSPDeployment\myApp"]}
{["-TraceLevel", "Verbose"]}
{["-DLLReferencePath", "C:\WSPDeployment\ReferencedAssemblies"]}

Frankly speaking, I don’t use the InputArguments class directly. For every utility I create a derived class to provide an easy access to the values of parameters specific for that particular application. For example, the derived class employing the -url and -useElevatedPrivileges may look like the following:

public class UtilityArguments : InputArguments
{
    public bool UseElevatedPrivileges
    {
        get { return GetBoolValue("-useElevatedPrivileges"); }
    }

    public string Url
    {
        get { return GetValue("url"); }
    }

    public UtilityArguments(string[] args) : base(args)
    {
    }

    protected bool GetBoolValue(string key)
    {
        string adjustedKey;
        if (ContainsKey(key, out adjustedKey))
        {
            bool res;
            bool.TryParse(_parsedArguments[adjustedKey], out res);
            return res;
        }
        return false;
    }
}

It can be used as follows:

static void Main(string[] args)
{
    UtilityArguments arguments = new UtilityArguments(args);

    Console.WriteLine("Url: " + arguments.Url);
    Console.WriteLine("UseElevatedPrivileges: " + arguments.UseElevatedPrivileges);
}

If you prefer using another sign or group of signs before parameter name in command line, use the proper constructor of the InputArguments class

static void Main(string[] args)
{
    InputArguments arguments = new InputArguments(args, "/");
    // ...
}

or

public class MyArguments : InputArguments
{
    // ...
    public MyArguments(string[] args) : base(args, "--")
    {
    }
    // ...
}

Categories: C# Tags: ,