McMaster.Extensions.CommandLineUtils

repository·main·Indexed 25 days ago

https://github.com/natemcmaster/commandlineutils

A .NET library for simplifying the creation of command line applications. It provides an Attribute API and a Builder API for argument parsing, input validation, and automatic help text generation. Includes utilities for interactive console feedback (Prompt), locating the dotnet executable (DotNetExe), and escaping shell arguments (ArgumentEscaper). Supports dependency injection via ConstructorInjectionConvention and integration with the .NET Generic Host through the McMaster.Extensions.Hosting.CommandLine package.

Tokens
7.1K
Snippets
22
Records
41
Agent score
81%

What's inside CommandLineUtils

  1. Using response files with sub-commands

    main

    Response files can include sub-command names. CommandLineUtils will correctly identify and execute the specified sub-command.

    Example: If your response file contains:

    list
    --tag
    major
    --tag
    fitness

    Running the application with this file will execute the list command with the provided tags.

    Important: When using sub-commands, you must explicitly set the ResponseFileHandling property for the sub-commands as well to ensure they can parse the response file contents.

  2. Understand the difference between Options and Arguments

    main

    In CommandLineUtils, command-line inputs are categorized into two types:

    1. Options: Named entities that must be specified using a name (e.g., --verbose or -v). They are typically optional by default and their order does not matter. Options can be flags (no value) or carry values (e.g., --path:logs/).
    2. Arguments: Positional values specified based on their order in the command string (e.g., abc in mycommand.exe abc).

    Example breakdown of mycommand.exe abc --verbose --path:logs/ --message=Hello xyz:

    • abc: Argument (position 0)
    • --path:logs/: Option (Name: path, Value: logs/)
    • --verbose: Option (Name: verbose, Value: null)
    • --message=Hello: Option (Name: message, Value: Hello)
    • xyz: Argument (position 1)
  3. Build command line apps using the Attribute API

    main

    If you prefer a declarative approach, you can define your command line structure using attributes on classes and properties. The following attributes are the most common:

    • CommandAttribute: Marks a class as a command.
    • OptionAttribute: Defines command-line options (e.g., --option-name).
    • ArgumentAttribute: Defines positional arguments.
    • SubcommandAttribute: Defines subcommands within a command.
    • HelpOptionAttribute: Adds a --help option to the command.
    • VersionOptionAttribute: Adds a --version option to the command.
  4. How CommandLineApplication works

    main

    CommandLineApplication is the central class used to parse command line arguments and execute commands. It supports two distinct configuration styles:

    1. Attribute API: Declarative style where you decorate properties with attributes like [Option] and define logic in an OnExecute method.
    2. Builder Pattern: Imperative style where you use method calls on an instance of CommandLineApplication to define options, help, and execution callbacks.
  5. Handle variable numbers of arguments

    main

    To collect a variable number of arguments (e.g., a list of files) into a collection, use the following methods depending on your approach:

    Using Attributes

    Define a property of type string[] or IEnumerable<string> and decorate it with [Argument(index)]. The library will automatically bind multiple values to these types.

    Using Builder API

    When calling app.Argument(...), set the multipleValues parameter to true. This argument must be the last one specified in the command definition. Access the values via the Values property of the CommandArgument object.

  6. Enable @-files (Response File Parsing)

    main

    CommandLineUtils supports parsing response files, where arguments starting with @ are treated as file paths. The contents of these files are injected into the command line as additional arguments.

    By default, response file parsing is disabled. You can enable it using either the Attributes API or the Builder API by setting the ResponseFileHandling property.

    Response File Rules:

    • Comments can be included using the # symbol.
    • Line concatenation using the backslash (\) is not supported.
    • You can use ResponseFileHandling.ParseArgsAsLineSeparated (each argument/option on its own line) or ResponseFileHandling.ParseArgsAsSpaceSeparated (arguments separated by spaces).
    myapp.exe @args.txt
  7. Create a console application using the Builder Pattern

    main

    The Builder Pattern provides a more programmatic way to configure your application. You instantiate a CommandLineApplication, define options using .Option(), and register the execution logic using .OnExecute().

    using System;
    using McMaster.Extensions.CommandLineUtils;
    
    public class Program
    {
        public static int Main(string[] args)
        {
            var app = new CommandLineApplication();
    
            app.HelpOption();
            var subject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
            subject.DefaultValue = "world";
    
            app.OnExecute(() =>
            {
                Console.WriteLine($"Hello {subject.Value()}!");
                return 0;
            });
    
            return app.Execute(args);
        }
    }
  8. Define options using the OptionAttribute

    main

    You can define options by applying the [Option] attribute to properties in a class passed to CommandLineApplication.Execute<T>.

    Inference Rules:

    • Short names: The first letter of the property name in lowercase, prefixed with - (e.g., Verbose becomes -v).
    • Long names: The property name in kebab-case, prefixed with -- (e.g., LogLevel becomes --log-level).
    • Types: The CommandOptionType is inferred from the property type:
      • bool: NoValue (Flag/Switch)
      • string: SingleValue
      • (bool hasValue, string value): SingleOrNoValue
      • T[]: MultipleValue (or SingleValue if explicitly configured)

    Note: Option names are case-sensitive. If you use a different case than the inferred name, it will result in an error.

  9. Integrate command line parsing with .NET Generic Host

    main

    You can integrate command line parsing with .NET's generic host using the McMaster.Extensions.Hosting.CommandLine package. This allows you to leverage generic host features like IHostingEnvironment and standard Dependency Injection (DI) alongside command line arguments.

    To use this integration, call RunCommandLineApplicationAsync<TApp>(args), where TApp is a class decorated with attributes that define your command line structure (similar to how CommandLineApplication.Execute<T> works).

  10. Count flag occurrences

    main

    To support scenarios where a flag (like -v) is specified multiple times to increment a level (e.g., -vvv), you can use one of two methods:

    1. Attributes: Use a bool[] property. The length of the array will equal the number of times the flag was provided.
    2. Builder API: Access the Values.Count property on the CommandOption object returned by app.Option().

    Note: Flag counting via attributes requires version 2.3 or newer.

    // Using Attributes
    public class Program
    {
        [Option]
        public bool[] Verbose { get; set; }
        
        public void OnExecute()
        {
           Console.WriteLine("Verbose count = " + Verbose.Length);
        }
      
        public static int Main(string[] args) 
            => CommandLineApplication.Execute<Program>("-v", "-v", "-v"); // Result: Verbose count = 3
    }
    
    // Using Builder API
    var app = new CommandLineApplication();
    var verbose = app.Option("-v|--verbose", "Show verbose output", CommandOptionType.NoValue);
    
    app.OnExecute(()n => Console.WriteLine("Verbose count = " + verbose.Values.Count));
    
    return app.Execute("-v", "-v", "-v"); // Result: Verbose count = 3