Application Arguments. Fluent. CodePlex

I have decided to get a deal with annoying application arguments. Those arguments you work with when creating various console applications. For your utilities, or for long-term tests and attempts. It is great to make them easy to maintain, validate and so on. It made me to create a library which I published at codeplex. The one thing which is nice for me – I have tried to implement a fluent api. It is a littleĀ overabundant (my colleague said) for now but there is a plan to make it more compact in future. The general example of the library:

        [Test]
        public void ExampleShouldNotFails()
        {
            /*Hi, this is a general test with description of the API*/

            //args simulates the input we may receive into an application entry point method. Argument names are case insensitive for this API
            var args = new List
                           {
                               "--useproxy",
                               "--address",
                               "http://example.com",
                               "--proxyusername",
                               "test",
                               "--proxypassword",
                               "test",
                               "--timeout",
                               "00:00:15",
                               "--optional",
                               "hey"
                           };

            ArgumentParserResult result = null;

            /*
             * The try/catch block is put here to show that you can handle ArgumentsException of API
             * There are some for type conversation fail and invalid mapping situations.
             * Look for "ArgumentsException" at the source code for details
             *
             */

            try
            {
                /*
                 * The arguments map is created using fluent API's.
                 * We can make arguments to be:
                 *  - required
                 *  - required when something is switched
                 *  - switchable
                 */
                var map = new ArgumentsMap()
                               .BuildStart()
                                   .AddKnownArgument("useProxy")
                                       .Switch()
                                   .End()
                                   .AddKnownArgument("address")
                                       .Required()
                                   .End()
                                   .AddKnownArgument("proxyUsername")
                                       .RequiredWhenSwitched("useProxy", "Provide a username for the proxy")
                                   .End()
                                   .AddKnownArgument("proxyPassword")
                                       .RequiredWhenSwitched("useProxy", "Provide a password for the proxy")
                                   .End()
                                   .AddKnownArgument("timeout")
                                    .Type<TimeSpan>() // you can specify the type explicitly. It should be convertible from a string representation
                                   .End()
                               .BuildFinish();

                // provide a map to a parser instance
                var parser = new ArgumentParser(map);

                //parse
                result = parser.Parse(args);
            }
            catch (ArgumentsException custom)
            {
                Assert.Fail(custom.Message);
            }

            Assert.IsTrue(result.IsValid); //shows that rules have passed successfully
            Assert.IsFalse(result.IsEmpty); //shows that nothing was provided (args were empty)

            //value checks
            Assert.AreEqual(true, result.GetValue("UseProxy")); // GetValue method casts the underlying value to desired. API user is responsible for type-safety
            Assert.AreEqual("http://example.com", result["Address"]); //indexer provides string values by default
            Assert.AreEqual("test", result["ProxyUsername"]);
            Assert.AreEqual("test", result["ProxyPassword"]);
            Assert.AreEqual(TimeSpan.FromSeconds(15), result.GetValue("timeout"));
            Assert.AreEqual("hey", result["optional"]); //all unmapped args are still provided as strings

        }

Update:

Fluent mapping was improved as I promised. So currently ArgumentsMap class implements IArgumentsMapBuilder and IArgumentsMapResult. The AddKnownArgument method returns IArgumentMapBuilder (watch, they are different in ‘s’ at the middle, this is not the best example of naming, I know). The IArgumentMapBuilder implements IArgumentsMapBuilder. So we got a rid of BuildStart() and End(). The BuildFinish method was renamed to GetResult and could be called by a client (ArgumentParser) which can accept both IArgumentsMapBuilder and IArgumentsMapResult in its turn.

var map = new ArgumentsMap()
                                   .AddKnownArgument("address")
                                        .Required()
                                   .AddKnownArgument("useProxy")
                                        .Switch()
                                   .AddKnownArgument("proxyUsername")
                                        .RequiredWhenSwitched("useProxy", "Provide a username for the proxy")
                                   .AddKnownArgument("proxyPassword")
                                        .RequiredWhenSwitched("useProxy", "Provide a password for the proxy")
                                   .AddKnownArgument("timeout")
                                        .Type<TimeSpan>();
Advertisements
This entry was posted in .NET, C#, Codeplex. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s