SkillAgentSearch skills...

RunInfoBuilder

A unique command line parser for .NET that utilizes object trees for commands.

Install / Use

/learn @rushfive/RunInfoBuilder
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Command Line Parser for NetStandard

Build status Nuget

This library provides a clean and simple API for parsing program arguments into a RunInfo object.

Targets NET Standard 2.0

Core Features at a Glance

  • Supports core command line abstractions such as Commands, Subcommands, Arguments, Options, etc.
  • Comes with many different Argument types to handle common use cases, such as 1-to-1 property mappings, binding values to a sequence, mutually-exclusive sets, etc.
  • Comes with a Parser that handles the most common system types out of the box. Easily configurable to handle any arbitrary types.
  • Provides a cleanly formatted help menu by default, with options to configure further.
  • Several hooks which provide custom extensibility points in various stages of the build process.

Code Configuration over Attributes

There are no attributes used to mark the resulting RunInfo class properties. Rather, you configure commands by providing a Command object as a representation of an object tree. C# provides a clean way to express nested objects through its object initializers so RunInfoBuilder makes use of that.

Using attributes to tell a command line parser how to interpret things works for very simple binding schemes. But if you need to go beyond that, for example, by using custom callbacks for validations or as extensibility points, using pure code to define the commands works better.


Getting Started

Install via NuGet or DotNet:

Install-Package R5.RunInfoBuilder
dotnet add package R5.RunInfoBuilder

A Simple Example

A program is desired that can read some message from the program arguments, and then do one of many things as determined by a command.

For example, it may take the message and send it off to some HTTP endpoint. Also, the user can optionally specify that the request should be retried on fail.

The required information for this has been collected into this RunInfo class:

public class SendRequestRunInfo
{
    public string RequestUrl { get; set; }
    public string Message { get; set; }
    public int DelayMinutes { get; set; }
    public bool RetryOnFail { set; set; }
}

The program should take three program arguments and simply bind them to the properties. To do this, a Command called sendhttp is added to the CommandStore:

// initialize a builder instance
var builder = new RunInfoBuilder();

// add the 'sendhttp' command to the store
builder.Commands.Add(new Command<SendRequestRunInfo>
{
    Key = "sendhttp",
    Arguments =
    {
        new PropertyArgument<SendRequestRunInfo, string>
        {
            Property = ri => ri.RequestUrl
        },
        new PropertyArgument<SendRequestRunInfo, string>
        {
            Property = ri => ri.Message
        },
        new PropertyArgument<SendRequestRunInfo, int>
        {
            Property = ri => ri.DelayMinutes
        }
    },
    Options =
    {
        new Option<SendRequestRunInfo, bool>
        {
            Key = "retry | r",
            Property = ri => ri.RetryOnFail
        }
    }
});

// build the run info object by passing program arguments (led by the command's key)
var args = new string[] { "sendhttp", "http://www.somewhere.com", "hello from program!", "3", "--retry" };
var runInfo = builder.Build(args);

The resulting runInfo variable will be of type SendRequestRunInfo with the expected values:

{
    RequestUrl: 'http://www.somewhere.com',
    Message: 'hello from program!',
    DelayMinutes: 3,
    RetryOnFail: true
}

The values were parsed from the program arguments and bound to the RunInfo properties as configured. Also, the RetryOnFail property was set to true because the option was specified (--retry). The option could also have been specified by -r instead because a short key was configured for the option.

This is a very simple example, illustrating the most basic of binding requirements: simple 1-to-1 mappings of program arguments to properties.

There's a lot more that can be done and configured, but hopefully you can at least see how simple and expressive defining commands through an object is. You can take a quick look at any command configuration and immediately know how it parses the program arguments.

If this has captured your interest, keep reading below for a deeper dive into all the features and areas of RunInfoBuilder.


In-Depth Documentation

Topics covered below:


Command Processing Overview

Before diving into Command configuration, we need to understand the order in which commands, and their child items like subcommands and options are processed.

It doesn't matter if you have just a single Command or one with many levels of SubCommands nested within it. When the builder begins processing a Command or SubCommand, it will always go through the same steps in order as depicted below:

alt text

1. Arguments

Arguments are processed first, and in the same order they're defined in the Command object added to the store.

They are also all required, so an exception will be thrown if no more program arguments are found.

2. Options

Any Options are processed immediately after the command's Arguments are, and can appear in any order. They are optional and any number of them can be specified.

3. SubCommands

A Command can contain nested SubCommands in a list, which are processed after Options. If any are configured, it is required that one is matched by the next program argument.

For example, if a command called search has two subCommands configured, outside and inside, then the two valid methods of calling the search command would be

search outside or search inside (we're ignoring any arguments or options for brevity here)

Here's a few examples of the search command being called incorrectly:

search - an exception will be thrown because subCommands have been configured but one wasn't specified.

search everywhere - an exception will be thrown because everywhere doesn't match a valid subCommand.

search outside inside - this simply makes no sense. It's not possible to call more than one subCommand from the list. One, and only one, must be matched.

A SubCommand is essentially the same type as a Command (just without the GlobalOptions property, more on that later). This results in a Command definition being a recursive tree structure, which can be nested arbitrarily deep:

alt text

Observing the command tree diagram above, when you create a Command, a valid processing will always start at the root node and traverse downwards (think DFS-like) until it hits and finishes processing the last SubCommand (a leaf node).

Although it's technically possible to create a Command with an arbitrary number of layers, it's probably best to limit it. Else, you risk the program having a confusing API.

To recap: All Arguments and Options for a given Command are processed first, in that order. After which, if any SubCommands exist, the matching one will be processed in the same manner. And so on and so forth.


Custom Callbacks

When configuring Commands, there are several places where you can provide custom callbacks. Most of these are Funcs that must return a ProcessStageResult. The type that is returned will determine whether the builder continues processing or stops early.

A static helper class exists with some members that makes returning the correct type easier.

To continue: return ProcessResult.Continue.

To end early: return ProcessResult.End.


Commands and the Default Command

Command Store

All Commands are configured on the builder's CommandStore object. The store provides two methods with the following interface:

CommandStore Add(Command<TRunInfo> command, Action<TRunInfo> postBuildCallback = null);
CommandStore AddDefault(DefaultCommand<TRunInfo> defaultCommand), Action<TRunInfo> postBuildCallback = null;

If the optional postBuildCallback action is set, it will be called after the program arguments are done processing, receiving the resolved RunInfo object as its single argument:

builder.Commands.Add(command, runInfo =>
{
    // do something with the resolved runInfo
});

Command

Type: Command<TRunInfo>

  • TRunInfoparameter is the RunInfo class the command is associated to.

The Command is the core entity, as everything else is nested within it.

Properties:

  • Key - string - A unique keyword that represents the Command. This only needs to be unique within a given Command. For example, both a Command and one of its nested SubCommands can have the same key.
  • Description - string - Text that's displayed in the help menu.
  • Arguments - List<ArgumentBase<TRunInfo>> - A list of Arguments required by the Command. Details of the different Argument types are discussed later.
  • Options - List<OptionBase<TRunInfo>> - A list of Options associated to th
View on GitHub
GitHub Stars44
CategoryDevelopment
Updated11mo ago
Forks2

Languages

C#

Security Score

87/100

Audited on Apr 5, 2025

No findings