Thinkmine.CommandLine 1.2.1

Suggested Alternatives

Zaria.AI

dotnet add package Thinkmine.CommandLine --version 1.2.1
NuGet\Install-Package Thinkmine.CommandLine -Version 1.2.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Thinkmine.CommandLine" Version="1.2.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Thinkmine.CommandLine --version 1.2.1
#r "nuget: Thinkmine.CommandLine, 1.2.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Thinkmine.CommandLine as a Cake Addin
#addin nuget:?package=Thinkmine.CommandLine&version=1.2.1

// Install Thinkmine.CommandLine as a Cake Tool
#tool nuget:?package=Thinkmine.CommandLine&version=1.2.1

"Pidgin" CommandLine Processor

Provides a simple, attribute-driven, low-code API for command-line processing using an approach similar to Web API routing.

Usage

Creating patterns

For any given class -

  1. For each command pattern you want to process add static method with a bool return type. Each of these will serve as a handler for when the user's input matches the pattern this method will advertise.
  2. Now decorate the method with CommandPatternAttribute attribute.
  3. As a catch-all for any input that does not match a pattern you advertise you can optionally provide another method (with a similar signature as described above) and decorate this one with CommandNotFoundAttribute instead. Note that only 1 method, the first encountered with the proper attribute, will be used for the fallback route even if you decorate multiple methods with it.

CommandPatternAttribute accepts a string as an argument. This string represents the pattern, we call Pidgin, that must be matched for the method to be called. This string can contain any sequence of characters except those with special meaning listed below:

char Description
@ Used to define placeholders in the pattern. When a user types in thier input, the parts that match the criteria for the placeholder will be passed into the method hosting the attribute. To add placeholders to the pattern use the @ symbol in front of the placeholder name.
[ Both square brackets are used to denote a section of your pattern that the user can place anywhere in the input string
] Both square brackets are used to denote a section of your pattern that the user can place anywhere in the input string
? Indicates that the given text is optional. It is meant to be placed at the end of the item being matched (so the pattern hello world? will trigger the underlying method if the user passes in hello or hello world. Variables cannot be optional. If a variable has the ? character after it, the request will be ignored.

Some points to note:

The parsing system does a token (word) for token match on the input string using the pattern defined in the CommandPatternAttribute as a psuedo-schema. By default, word positions are respected when matching in this way. This approach is somewhat rigid from a users perspective however; forcing them to enter commands in an exact order can be challenging. To address this, Pidgin also includes a mechanism to that allows for sections of the overall pattern to be entered without respect to position. While position is still maintained within the section, the resulting sections as a whole can be keyed-in in any order.

  • To indicate that a section of the pattern is not tied to a position, encase that section in square brackets ([]).
  • Note that you cannot start a pattern with square brackets (the first part of your pattern maintain its position).
  • Note that you cannot start any part of your pattern with placeholders. Additionally, in all scenarios placeholders must come after static text. This is also true for all sections of the pattern; meaning if you elect to break the pattern up into sections then those sections will also need text before placeholders.
  • You cannot put sections inside of sections. This means you can't place square brackets inside of a block enclosed by square brackets. This will cause a runtime error.
  • The parsing process is sensitive to white spaces, if the user's intent is to pass in a parameter with white spaces they must use double quotes (") to surround that text. This means a pattern like hello @message can be triggered with the text hello john but can also be triggered with hello "john the baptist"
  • Be careful where you put placeholders in the pattern. A good rule of thumb is to have static text between your variables and any ] character.

A pattern like The [quick @fox_color fox jumped] @jump_direction over the lazy dog will produce three sections.

  • The
  • quick @fox_color fox jumped
  • @jump_direction over the lazy dog

A pattern like the [@quick @fox_color fox jumped] jump_direction over the lazy dog will also produce three sections.

  • The
  • @quick @fox_color fox jumped
  • jump_direction over the lazy dog

As can be seen, during pattern evaluation sections of the Pidgin may get resolve as starting with a variable. To prevent this, never put a placeholder directly after a partitioned block.

Types

CommandLineProcessor

Provides functions that can be used to initiaize the processor and execute commands on it. Also contains properties that can be used to inspect the input string/array that is being executed

CommandLineInputContext

Provides a view into the parameters that were passed in. You can use the type to walk back and forth through the text that was passed into your tool token (word) by token. Note that quoted text is treated as one token

InputToken

Represents a token (word) in the input string/array that is passed into the processor.

DynamicCommandRepository

Collection where dynamic commands are stored. Dynamic commands are the commands that are injected into the CommandLineProcessor as opposed to getting discovered through the initialization process.

Examples

In the following example a command pattern handler is created

[CommandPattern("say [-what @something] [-to @someone]")]
static bool SaySomething(string something, string someone, int i)
{
    Console.WriteLine($"You said {something} to {someone}");
    return true;
}

This method will be invoked whenever the following pattern is passed to the interpreter say -what [...] -to [...] . Any placeholders in the pattern must match the parameter names on the method that it decorates (similar to Web API).
If placeholders are provided but the method does not have paramters (or they dont match) then the parameters will hold their default value when the method is run. This is also true in the reverse scenario; if no placeholders are included the method will be called with the default values of any parameters that exist. The return value is not used by the processor; rather, it is meant to be used in REPL scenarios to indicate whether the loop should continue. THe convension is to return true to continue the program and false to exit it.

In the following example we pass the following into a command-line that has the code above deployed:

say -what hello world -to the man next door

The result of this would be:

You said hello world to the man next door

A single method may be decorated with more than one command pattern (thus allowing it to handle multiple scenarios). In the following example the same SayHello method is used to handle three different patterns.

[CommandPattern("test @arg")]
[CommandPattern("test")]
[CommandPattern("hello there")]
static bool SayHello(string arg)
{
    Console.WriteLine($"Calling hello there command with {arg}");
    return true;
}

This method will be invoked if any of the following patterns is passed into the processor.

  • test
  • test 123
  • hello there

Running the Processor

To initialize the processor use the following code:

CommandLineProcessor.Initialize();

This will traverse the current assembly and any referenced assemblies and identify all the command patterns that have been specified. A command pattern is declared by creating a method with a certain signature (static with a boolean return type) and decorating it with the CommandPatternAttribute. Initialize must always be called first, and must be called again each time a new assembly is added to the AppDomain it was initially called in.

The processor can be used in two ways: For 1-time command line argument processing you can call CommandLineProcessor.Execute(string[]);

This version of the execute method accepts an array of strings. This can be the arguments passed into the console itself (but it really could be anything). No special processing is performed on the array other than turning it into a space-delimited string. So if the array: [hello,world,i,like,to,code] it would be converted to hello world i like to code. This version of the method returns void.

For creating something like a REPL (Read-evaluate-print loop) the other version of the method is more ideal. CommandLineProcessor.Execute(string);

This version of the method takes the entire string as input and processes it with no additional manipulation. This version returns a bool that can be used to determine whether it should be called again.

The following shows how a simple console REPL might look.

static void Main(string[] args)
{
    Console.WriteLine("Pidgin REPL Sample");
    //call initialization
    CommandLineProcessor.Initialize();

    while (true)
    {
        Console.Write("> ");
        var command_line = Console.ReadLine();
        var continue_to_run = CommandLineProcessor.Execute(command_line);

        if (!continue_to_run)
            break;
    }

}

As can be seen from the sample, the Execute method returns a boolean value that is then used to determine whether the loop continues to run. The alternate method does not have a return type.

static void Main(string[] args)
{
    CommandLineProcessor.Initialize();
    CommandLineProcessor.Execute(args);
}

There will be scenarios where a procedural approach is more advanageous to you. For instance, if a quick and easy command is needed, it might not make sense to create an entire method for it. In those situations you can use the DynamicCommands property of CommandLineProcessor. It allows you to inject pattern evaluators directly into the CommandLineProcessor without having to create a class method.

CommandLineProcessor.DynamicCommands["reset @parameter"] = (placeholders) =>
{
    foreach(var placeholder in placeholders.Keys)
        Console.WriteLine($"{placeholder} = {placeholders[placeholder]}");
    return true;
};

One key difference between the two approaches is that with this, the placeholders are not automatically converted and mapped to the method's parameters. With this approach a dictionary(of string) is passed into the lambda. If multiple patterns need to be mapped to one lambda they can be passed comma separated. An example of multiple dynamic commands using the same lamda is shown below.

CommandLineProcessor.DynamicCommands
[
    "run @code_name [override? @role]",
    "stop @application"
] = (placeholders) =>
{
    var is_run = CommandLineProcessor.Context.HasParameter("run");
    if (is_run)
    {
        var allow_override = CommandLineProcessor.Context.HasParameter("override");
        if (allow_override)
        {
            Console.WriteLine($"*** Running {placeholders["code_name"]} ... ***");
        }
        else
            Console.WriteLine($"unning {placeholders["code_name"]} with normal access");
    }
    else
    {
        Console.WriteLine($"Stopping {placeholders["application"]}");
    }

    return true;
};

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.1 324 1/9/2023
1.2.0-beta 110 1/8/2023
1.1.2-beta 122 1/7/2023
1.1.1-beta 106 1/7/2023
1.1.0-alpha 115 1/7/2023
1.0.7 257 1/5/2023
1.0.6 246 1/5/2023
1.0.5 257 1/5/2023
1.0.0 312 11/15/2022