Skip to content
Mauro Sampietro edited this page Apr 15, 2022 · 24 revisions

UltraMapper.CommandLine

This library helps you with the process of parsing and mapping commandline arguments to strongly-typed objects.

Mapping is supported for primitive and built-in types (eg: bool, int, double, string, etc..), complex-types such as collections (eg: array IList, IEnumerable) and user-defined types.

Both properties and methods defined in a 'Commands' class can be called/assigned using a specific command-line syntax.

Example:

    static void Main( string[] args )
    {
        CommandLine.Instance.Parse<Commands>( args );
    }
    
    public class Commands
    {
        //--opendir "C:\my folder"
        public void OpenDir( string path )
        {
            Process.Start( path );
        }
    }

UltraMapper.CommandLine is powered by UltraMapper which uses Expressions, to generate the code needed to deal with your commands, in order to guarantee good performances.

Getting started

All of the examples use the default built-in syntax.

Commands class

The main object you will map on it's called the 'Commands' class.
A Commands class is a normal class containing all of the properties you need to set and all of the methods you need to call from the commandline.

As a consequence of supporting calls to methods directly, a 'Commands' class could/should only define methods.
Those methods would be directly related to the operations you intend to make available at commandline level.

You can then execute your commands like this:

    static void Main( string[] args )
    {
        //Creates a new instance of type UserInfo and writes on it
        var userInfo = CommandLine.Instance.Parse<Commands>( args );
    }
    
    public class Commands
    {
        public void Open(string path){...}
        public void Quit(){...}
    }

You can work with the same 'Commands' class instance preserving state.
(Subsequent calls from commandline write on the same Commands instance instead of creating a new one each time)

This can be particularly useful if working with properties.

    static void Main( string[] args )
    {
        //--name "John Smith"
        var userInfo = new UserInfo()
        CommandLine.Instance.Parse<UserInfo>( args, userInfo );
        
        //--age 26
        string newArgs = Console.Readline()
        CommandLine.Instance.Parse<UserInfo>( newArgs, userInfo );
        
        Assert.IsTrue( userInfo.Name == "John Smith" && userInfo.Age == 26)
    }
    
    public class UserInfo
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

You can call the following utility method to setup an infinite loop reading and parsing args:

  • Creating a new instance each time:
        ConsoleLoop.Start<UserInfo>( args, parsed =>
        {
            //do what you want with your strongly-type parsed args
        } );
  • Writing on the same instance:
        static void Main( string[] args )
        {
            var userInfo = new UserInfo();
            ConsoleLoop.Start( args, userInfo, parsed =>
            {
                //do what you want with your strongly-type parsed args
            } );
        }

Default parser syntax:

To take full advantage of the features provided by UltraMapper.Command Line, a new syntax, fundamentally different from Unix geopt, had to be developed.

  • -- a double dash indentifies a command (a command is a method or property defined at Commands class level)
    example: --close

  • Whitespaces characters delimit commmands and values
    example: --move C:\Temp\file.txt C:\Archive\file.txt

    In the example above we have a command called 'move' and 2 parameters 'C:\temp\file.txt' and 'C:\Archive\file.txt'

  • Double quotes escape special characters, including whitespaces.
    example: --move "C:\folder with spaces in the name\file.txt" C:\Archive\file.txt

    In the example above we have a command called 'move' and 2 parameters 'C:\folder with spaces in the name\file.txt' and 'C:\Archive\file.txt'

  • if your param is a complex type, round brackets identifies the object
    example: --sum ()

  • If your param is a collection square brackets identifies the collection
    example: --sum [1 2 3 4 5]

  • Collections of complex types are supported, recursively, without limits
    example: --sum [ (a b) (c d) (e f) ]

  • Parameters can be specified indicating the param name.
    Non-named params must be provided in a exact order, before any named param.
    Named params can be provived in any order, but they all must appear after non-named params.
    The default order is:

    • for complex-types: members definition order
    • for methods: params definition order

    You can override the definition order by setting the property 'Order' via the 'OptionAttribute' of each member.

Example:

        public class Commands
        {
            public void Move( string from, string to ){ ... }
        }

        //--move from="C:\text.txt" to="C:\subfolder\text.txt"";   

Set property

Conversion from string to any primitive type is supported. Complex types, collections and collections of complex types are supported too.

      static void Main( string[] args )
      {
          var userInfo = CommandLine.Instance.Parse<UserInfo>( args );
      }
    
      public class UserInfo
      {
          public string Name { get; set; }
          public int Age { get; set; }
          public bool IsEmployed { get; set; }

          public class BankAccountInfo
          {
              public string AccountNumber { get; set; }
              public double Balance { get; set; }
          }
          public BankAccountInfo BankAccount { get; set; }
      }

Bool properties

Bool propeties are special: if you want to set a boolean property to 'true', you can omit to write 'true' as value

example:

--isEmployed true   

is equivalent to

--IsEmployed

Method call

Other than setting properties, UltraMapper.CommandLine also allows you to call methods.

Methods can be called only if returning void, non abstract, non generic.
For security reasons only methods defined at Commands class level can be called.

    public class Commands
    {
        //call example from commandline: --opendir C:\
        public void OpenDir( string path )
        {
            Process.Start( path );
        }

        //call example from commandline: --opendirs [C:\ C:\windows\]
        public void OpenDirs( string[] paths )
        {
            foreach( var path in paths )
                Process.Start( path );
        }       
    }

Complex types and collections of complex types are fully supported. You can organize your parameters optimally.

    public class Commands
    {
        public class MoveParams
        {
            public string From { get; set; }
            public string To { get; set; }
        }

        //call example from commandline: --movefile (C:\temp\file.txt C:\file.txt")
        public void MoveFile( MoveParams moveParams )
        {
            Console.WriteLine( $"You want to move from:{moveParams.From} to:{moveParams.To}" );
        }

        //call example from commandline: --movefiles [(C:\temp\file1.txt C:\file1.txt") (C:\temp\file2.txt C:\file2.txt")]
        public void MoveFiles( IEnumerable<MoveParams> moveParams )
        {
            foreach( var item in moveParams )
                Console.WriteLine( $"moving file from '{item.From}' to '{item.To}'" );
        }
    }

Multiple advancements compared to other similar projects include:

  • The ability to invoke a method directly:
        public class Commands
        {
            public void Move( string from, string to ){ ... }
        }

        var args = "--move C:\text.txt C:\subfolder\text.txt";   
        //Move method gets executed on parse
        AutoParser.Instance.Parse<Commands>( args );       
  • Map values to complex types, instantiating null instances. No hierarchy limits in depth.
        public class Commands
        {
            public class MoveCommand
            {
                public string From { get; set; }
                public string To { get; set; }
            }

            public MoveCommand Move{get; set;}
        }

        var args = "--move C:\text.txt C:\subfolder\text.txt";   
        var parsedObj = AutoParser.Instance.Parse<Commands>( args );       
        Assert.IsTrue( parsedObj.Move.From = "C:\text.txt" );
        Assert.IsTrue( parsedObj.Move.To = "C:\subfolder\text.txt" );
  Complex types support make UltraMapper.CommandLine especially useful when trying to organize
  a non trivial number of parameters in a natural way.
  • Array and IEnumerable support
        public class Commands
        {
            public void Open( IEnumerable<string> paths ){ ... }
        }

        var args = "--open [C:\ C:\subfolder1 C:\subfolder2]";   
        //Open method gets executed on parse
        AutoParser.Instance.Parse<Commands>( args );      

OptionAttribute

--help command and IHelpProvider:

If you manually define a help command in your commands class, it will be invoked when invoking --help

If you do not define a help command, a help command is automatically generated for you.
The default help provider used in this latter case will analyze your commands class and generate adequate usage documentation keeping into account both operations and parameters.

You can also provide a new helper by implementing an IHelpProvider. A few rules apply to it. Read more here.

Remarks:

  • Works with with properties and methods but not with fields.
  • Value types are not supported.