Click here to Skip to main content
Click here to Skip to main content
Go to top

Updating the Command Line Parser

, 23 Aug 2009
Rate this:
Please Sign up or sign in to vote.
Staring from the original Command Line Parser v1.0 code, I wanted to be able to add multiple commands, or even nest commands. The result is a nice simple commanding architecture conducive to creating multiple commands.

I had previously created a Command Line Parser from Ray Hayes CodeProject article Automatic Command Line Parsing in C#. I had adapted it to VB.NET and upgraded it to .NET 3.5 but I recently ran into the problem with wanting a single command prompt application to handle multiple processes and multiple parameters. This would allow you to group all of a particular tasks commands into a single application. With the advent of Power Shell, this format is increasingly less relevant, but with the proliferation of Power Shell many people still prefer to use the good old command line.

So, staring from the original Command Line Parser v1.0 code, I wanted to be able to add multiple commands, or even nest commands. The result is a nice simple commanding architecture conducive to creating multiple commands.

image

Using this model, I can create a simple command…

Imports Hinshlabs.CommandLineParser
Imports System.IO
Imports System.Collections.ObjectModel
Imports System.Net

Public Class Demo1Command
    Inherits CommandBase(Of Demo1CommandLine)

    Private m_PortalLocation As Uri

    Public Overrides ReadOnly Property Description() As String
        Get
            Return "demo 1 command demonstrates a single nested command"
        End Get
    End Property

    Public Overrides ReadOnly Property Name() As String
        Get
            Return "Demo1"
        End Get
    End Property

    Protected Overrides Function ValidateCommand() As Boolean
        Return True
    End Function

    Public Overrides ReadOnly Property Title() As String
        Get
            Return "demo 1"
        End Get
    End Property

    Public Overrides ReadOnly Property Synopsis() As String
        Get
            Return "demo 1 command"
        End Get
    End Property

    Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
        Get
            Return CommandLine.Switches
        End Get
    End Property

    Public Overrides ReadOnly Property Qualifications() As String
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overrides Function RunCommand() As Integer
        Try
            CommandOut.Warning("running Demo1")
            Return -1
        Catch ex As Exception
            CommandOut.Error("Failed: {0}", ex.ToString)
            Return -1
        End Try
    End Function

End Class

Or something more substantial:

Protected Overrides Function RunCommand() As Integer
    Try
        Dim x As New Proxies.MyApp.Configuration.ConfigurationServiceClient_
	("BasicHttpBinding_IConfigurationService", m_PortalLocation.ToString)
        x.ClientCredentials.Windows.AllowedImpersonationLevel = _
	System.Security.Principal.TokenImpersonationLevel.Delegation
        Select Case CommandLine.Action
            Case QuiesceAction.Offline
                x.QuiesceSource(CommandLine.Source, CommandLine.Message, New TimeSpan(0))
            Case QuiesceAction.Online
                x.RestoreSource(CommandLine.Source)
        End Select
        CommandOut.Info("Source {0} has been made {1}", _
		CommandLine.Source, CommandLine.Action.ToString)
        Return 0

    Catch ex As EndpointNotFoundException
        CommandOut.Error("Unable to locate site. _
	Check the value you selected for /Portal:{0}", CommandLine.Portal)
        Return -1
    Catch ex As Exception
        CommandOut.Error("Failed: {0}", ex.ToString)
        Return -1
    End Try
End Function

If you are wondering where the variables come from, you can see form Demo1Command that a generic type of Demo1CommandLine is passed in. The application creates an instance of this which wraps the Ray Hayes parser to provide the values from Environment.CommandLine used on the shared methods on the CommandLineBase class.

''' <span class="code-SummaryComment"><summary>
</span>''' Created a command line object using the Environment.CommandLine information
''' <span class="code-SummaryComment"></summary>
</span>''' <span class="code-SummaryComment"><typeparam name="TCommandLine">The concrete type of object to create</typeparam>
</span>''' <span class="code-SummaryComment"><returns>An instance of the object</returns>
</span>''' <span class="code-SummaryComment"><remarks></remarks>
</span>Public Shared Function CreateCommandLine(Of TCommandLine As _
	{New, CommandLineBase})() As TCommandLine
    Return CreateCommandLine(Of TCommandLine)(Environment.CommandLine)
End Function

''' <span class="code-SummaryComment"><summary>
</span>''' Created a command line object using the Environment.CommandLine information
''' <span class="code-SummaryComment"></summary>
</span>''' <span class="code-SummaryComment"><typeparam name="TCommandLine">The concrete type of object to create</typeparam>
</span>''' <span class="code-SummaryComment"><param name="CommandLine">The command line arguments to parse</param>
</span>''' <span class="code-SummaryComment"><returns></returns>
</span>''' <span class="code-SummaryComment"><remarks></remarks>
</span>Public Shared Function CreateCommandLine(Of TCommandLine As _
	{New, CommandLineBase})(ByVal CommandLine As String) As TCommandLine
    Dim instance As New TCommandLine
    Dim parser As New Parser(CommandLine, instance)
    parser.Parse()
    instance.Parser = parser
    Return instance
End Function

This parser then populates the CommandLine object with values from the CommandLine passed in. For example:

Imports Hinshlabs.CommandLineParser
Imports System.Collections.ObjectModel

Public Class Demo3CommandLine
    Inherits CommandLineBase

    Private m_value1 As String
    Private m_value2 As Value2Values = Value2Values.Value1

    <CommandLineSwitch("Value1", "Adds a string value named value1"), _
	CommandLineAlias("v1")> _
    Public Property Value1() As String
        Get
            Return Me.m_value1
        End Get
        Set(ByVal value As String)
            Me.m_value1 = value
        End Set
    End Property

    <CommandLineSwitch("Value2", "Adds and enum value called value2"), _
	CommandLineAlias("v2")> _
    Public Property Value2() As Value2Values
        Get
            Return Me.m_value2
        End Get
        Set(ByVal value As Value2Values)
            Me.m_value2 = value
        End Set
    End Property

    Public Enum Value2Values
        Enum1
        Enum2
        Enum3
    End Enum

End Class

Would allow you to call [consoleApp] Demo3 /v1:”Any value you like” /Value2:Enum3 and have the correct values populated at runtime.

I have also updated with a DelegateCommand class that would allow you to call a function in the right format from anywhere:

New DelegateCommand(Of Demo3CommandLine)("Demo2", AddressOf OnDemo2Run, "demo 2", _
	"no additional information", "demo 2 command", _
	"This command shows how to delegate the run method using the delegate command")

The delegate command is really easy in .NET 3.5 with the only change being the addition of a variable declared as a Func in the class:

Imports Hinshlabs.CommandLineParser
Imports System.IO
Imports System.Collections.ObjectModel
Imports System.Net

Public Class DelegateCommand(Of TCommandLine As {New, CommandLineBase})
    Inherits CommandBase(Of TCommandLine)

    Private m_Description As String
    Private m_Title As String
    Private m_Synopsis As String
    Private m_Qualifications As String
    Private m_name As String
    Private m_RunCommand As Func(Of Integer)

    Public Overrides ReadOnly Property Description() As String
        Get
            Return m_Description
        End Get
    End Property

    Public Overrides ReadOnly Property Name() As String
        Get
            Return m_name
        End Get
    End Property

    Protected Overrides Function RunCommand() As Integer
        Try
            Return m_RunCommand.Invoke
        Catch ex As Exception
            CommandOut.Error("Failed: {0}", ex.ToString)
            Return -1
        End Try
    End Function

    Protected Overrides Function ValidateCommand() As Boolean
        Return True
    End Function

    Public Overrides ReadOnly Property Title() As String
        Get
            Return m_title
        End Get
    End Property

    Public Overrides ReadOnly Property Synopsis() As String
        Get
            Return Synopsis 
        End Get
    End Property

    Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
        Get
            Return CommandLine.Switches
        End Get
    End Property

    Public Overrides ReadOnly Property Qualifications() As String
        Get
            Return String.Empty
        End Get
    End Property

    Public Sub New(ByVal name As String, ByVal runCommand As Func(Of Integer), _
	ByVal title As String, ByVal qualifications As String, _
	ByVal synopsis As String, ByVal description As String)
        m_name = name
        m_RunCommand = runCommand
        m_Title = title
        m_Qualifications = qualifications
        m_Synopsis = synopsis
        m_Description = description
    End Sub

End Class

If you were wondering why there are so many properties, it is to allow the help to be created automatically. For example, if you call the help function on Demo3Command you will get…

image

With the values coming from the relevant places:

image

It will also support inherited CommandLine objects to minimize duplication.

I hope that if you are building command line apps that you will have a look, just remember not to spend too much effort on cmd, when Power Shell is much more suitable and accessible to non developers.

Technorati Tags: ,,

Download the code here.

License

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

Share

About the Author

Martin Hinshelwood
Instructor / Trainer naked ALM
United States United States

About the Author: Martin has worked with many customers in government, finance, manufacturing, health and technology to help them improve their processes and deliver more. He provides management and technical consulting that intends to expose processes and practices to gain transparency, uncover impediments to value delivery and reduce cycle-time as part of an organisations path to agility. Martin is a Professional Scrum Trainer as well as a Visual Studio ALM MVP and Visual Studio ALM Ranger. He writes regularly on http://nakedalm.com/blog, and speaks often on Scrum, good practices and Visual Studio ALM.

You can get in touch with Martin through naked ALM.

Follow on   Twitter   Google+

Comments and Discussions

 
GeneralGreat! PinmemberAnt210023-Aug-09 0:53 
Generalsounds good, Pinmemberd_camillo21-Aug-09 5:49 
GeneralRe: sounds good, PinmemberMartin Hinshelwood23-Aug-09 21:31 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140922.1 | Last Updated 24 Aug 2009
Article Copyright 2009 by Martin Hinshelwood
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid