Oakton

repository·master·Indexed 43 days ago

https://github.com/jasperfx/oakton

A .NET library for parsing and providing utilities for command line tools. It allows developers to define command-line patterns using input and command classes, supporting both synchronous (OaktonCommand<T>) and asynchronous (OaktonAsyncCommand<T>) execution. Oakton features automatic command assembly discovery via the OaktonCommandAssembly attribute and supports IoC integration through the ICommandCreator interface. Note: Oakton has been integrated into the unified JasperFx NuGet package.

Tokens
16.4K
Snippets
55
Records
65
Agent score
54%

What's inside Oakton

  1. What are Flags in Oakton?

    master

    Flags are any public settable property or public field on the input class that is suffixed with Flag.

    Unlike arguments, flags are identified by a prefix (usually -- for long form or - for short form). Flags can be:

    • Boolean Flags: Presence on the command line sets the value to true.
    • Value Flags: Accept a following value (e.g., --create-branch working).
    • Enumerable Flags: Accept multiple values (e.g., --files file1 file2).
  2. How Oakton command parsing works

    master
    Oakton maps tokenized command-line input values to the properties of an input class. It then passes that populated object into the Execute() method of the corresponding command. Oakton also performs validation to ensure that the provided arguments and flags match the known command syntax and provides helpful error messages or help text if they do not.
  3. Use IWriteToConsole for enhanced describe output

    master

    If you want your IDescribedSystemPart to have enhanced, visually rich formatting in the console (e.g., using Spectre.Console tables), implement the IWriteToConsole interface in addition to IDescribedSystemPart.

    • Write(TextWriter writer) will still be used when exporting to a file (Markdown).
    • WriteToConsole() will be used when the output is directed to the terminal.
    public class ReferencedAssemblies : IDescribedSystemPart, IWriteToConsole
    {
        public string Title { get; } = "Referenced Assemblies";
    
        // Used for file export (Markdown)
        public Task Write(TextWriter writer)
        {
            var referenced = Assembly.GetEntryAssembly().GetReferencedAssemblies();
            foreach (var assemblyName in referenced) writer.WriteLine("* " + assemblyName);
            return Task.CompletedTask;
        }
    
        // Used for console output (Enhanced formatting)
        public Task WriteToConsole()
        {
            var table = new Table();
            table.AddColumn("Assembly Name");
            table.AddColumn("Version");
    
            var referenced = Assembly.GetEntryAssembly().GetReferencedAssemblies();
            foreach (var assemblyName in referenced) table.AddRow(assemblyName.Name, assemblyName.Version.ToString());
    
            AnsiConsole.Write(table);
            return Task.CompletedTask;
        }
    }
  4. How environment checks work internally

    master

    Environment checks are powered by the IEnvironmentCheck interface. When CheckEnvironment() is called, it registers a service that implements this interface.

    During execution, Oakton.AspNetCore iterates through all registered IEnvironmentCheck services and runs their Assert method within a try/catch block.

    • Success: If Assert completes without an exception, the check is marked as successful (green).
    • Failure: If Assert throws an exception, the check is marked as a failure and the exception details are displayed.

    If you need to run checks programmatically outside of the CLI, use EnvironmentChecker.ExecuteAllEnvironmentChecks(IServiceProvider).

  5. How Oakton commands are structured

    master

    An Oakton command consists of two distinct parts:

    1. An Input Class: A concrete class that holds all the argument and flag data inputs.
    2. A Command Class: A concrete class that inherits from either OaktonCommand<T> or OaktonAsyncCommand<T>, where T is the input class defined in step 1.

    Key characteristics of a command class:

    • The entry point is the Execute() method.
    • The Execute() method returns a bool indicating success or failure. This return value is used by the OS to signal command completion status, which is critical for automated build pipelines.
    • The [Description] attribute provides help text and is optional.
    • It is legal to reuse the same input class across multiple different commands.
    [Description("Print somebody's name")]
    public class NameCommand : OaktonCommand<NameInput>
    {
        public NameCommand()
        {
            Usage("Default Color").Arguments(x => x.Name);
        }
    
        public override bool Execute(NameInput input)
        {
            // Command logic here
            return true;
        }
    }
  6. What are Arguments in Oakton?

    master

    An Argument is any public field or settable property on your input class that is not suffixed with Flag and is not marked with the [IgnoreOnCommandLine] attribute.

    Arguments are positional values that appear in the command line before any flags.

    Key rules:

    • Settable properties and public fields are valid.
    • Read-only properties are ignored.
    • Use [IgnoreOnCommandLine] to prevent a field/property from being captured from the command line.
    • Properties suffixed with Flag are treated as Flags, not arguments.
    public class NameInput
    {
        // Argument (settable property)
        public string First { get; set; }
        
        // Argument (public field)
        public string Last;
    
        // Ignored (read-only)
        public string Fullname => $"{First} {Last}";
        
        // Ignored (explicitly)
        [IgnoreOnCommandLine]
        public string Nickname;
        
        // Flag (suffixed with Flag)
        public bool VerboseFlag { get; set; }
    }
  7. Using Array or Enumerable Arguments

    master

    You can use IEnumerable<T> or T[] for arguments.

    CRITICAL CAVEAT: An enumerable or array argument must be the very last argument in your command line usage. This is because Oakton needs to know when the list of values ends, and it uses the start of the next flag to determine this.

    Correct Usage Pattern: executable command value1 value2 value3 --flag --flag2

    public class EnumerableArgumentInput
    {
        public IEnumerable<string> Names { get; set; }
        public IEnumerable<TargetEnum> Enums { get; set; }
        public IEnumerable<int> Ages { get; set; }
    }
  8. Run environment checks before starting the application

    master

    To make deployments self-diagnosing, you can instruct Oakton to run all configured environment checks before starting the Kestrel web server. If any check fails, the application startup will fail immediately.

    To trigger this behavior, include the --check flag (or simply --environment as per the documentation's specific usage example) with the run command.

    Note: The documentation provides two ways to trigger checks: --check and --environment (without a value).

    dotnet run -- --check
  9. Write an extension command using NetCoreInput

    master

    Extension commands can be implemented by inheriting from OaktonCommand<T> or OaktonAsyncCommand<T>.

    If your command needs to access or modify the application's IHostBuilder configuration, you should use NetCoreInput (or a class inheriting from it) as the input type.

    NetCoreInput provides access to the IHostBuilder, but it does not automatically build or start the host. This lazy behavior allows you to perform configuration or extensions before calling input.BuildHost() or input.StartHost().

    [Description("Simply try to build a web host as a smoke test", Name = "smoke")]
    public class SmokeCommand : OaktonCommand<NetCoreInput>
    {
        public override bool Execute(NetCoreInput input)
        {
            // input.BuildHost() builds the IWebHost from the configured IHostBuilder
            using (var host = input.BuildHost())
            {
                Console.WriteLine("It's all good");
            }
    
            return true;
        }
    }
  10. Provide help text using the [Description] attribute

    master

    You can provide descriptive help text for your command-line interface by applying the [Description] attribute to properties or fields within your input class, or to the command class itself. This text is used by Oakton to explain the purpose of specific arguments or the command as a whole when displaying help information.

    public class NameInput
    {
        [Description("The name to be printed to the console output")]
        public string Name { get; set; }
    
        [Description("The color of the text. Default is black")]
        public ConsoleColor Color { get; set; } = ConsoleColor.Black;
    
        [Description("Optional title preceeding the name")]
        public string TitleFlag { get; set; }
    }
    
    [Description("Print somebody's name")]
    public class NameCommand : OaktonCommand<NameInput>
    {
        // ...
    }
  11. Bootstrap Oakton with a single command

    master

    If your application only contains one specific command, you can bootstrap Oakton using the simplest method by calling CommandExecutor.ExecuteCommand<T>(args). This bypasses complex configuration and directly executes the specified command type.

    class Program
    {
        static int Main(string[] args)
        {
            // As long as this doesn't blow up, we're good to go
            return CommandExecutor.ExecuteCommand<NameCommand>(args);
        }
    }
  12. Use Boolean, Enumerable, and Key/Value Flags

    master

    Boolean Flags

    If a boolean flag appears, the property is set to true. You can combine multiple short flags into a single expression (e.g., -xfd).

    Enumerable Flags

    Use string[] or IEnumerable<string> to collect multiple values. Oakton knows when the list ends by looking for the next flag.

    Key/Value Flags

    If a flag is not a boolean, Oakton treats the next string in the command line as the value for that flag.

    Example Usage:

    # Boolean flags combined
    executable command -x -f --force
    
    # Enumerable flags
    executable command --files one.txt two.txt --directories dir1 dir2
    
    # Key/Value flags
    executable command --create-branch working
    public class FileInput
    {
        public string[] FilesFlag;
        public string[] DirectoriesFlag;
    }
    
    public class CleanInput
    {
        public bool ForceFlag { get; set; }
        
        [FlagAlias('d')]
        public bool RemoveUntrackedDirectoriesFlag { get; set; }
        
        [FlagAlias('x')]
        public bool DoNoUseStandardIgnoreRulesFlag { get; set; }
    }