Alveus.Contour (0.3.2)

Published 2025-12-21 17:18:31 +01:00 by mike

Installation

dotnet nuget add source --name public --username your_username --password your_token 
dotnet add package --source public --version 0.3.2 Alveus.Contour

About this package

A modern, lightweight framework for building command-line applications in C#. Alveus.Contour provides an intuitive attribute-based API for defining commands, supports middleware pipelines, includes a built-in REPL, and is fully AOT-compatible.

Alveus Contour

Release Build status

A modern, lightweight framework for building command-line applications in C#. Alveus.Contour provides an intuitive attribute-based API for defining commands, supports middleware pipelines, includes a built-in REPL, and is fully AOT-compatible.

Buy me a Coffee

Features

  • Attribute-based command definition - Use simple attributes to declare commands, subcommands, options, flags, and parameters
  • Middleware pipeline - Wrap command execution with cross-cutting concerns (logging, validation, initialization)
  • Global options - Define options available across all commands
  • Built-in REPL - Interactive mode with command history, auto-completion, and customizable prompts
    • Intelligent auto-completion - Tab completion for commands, subcommands, options, and flags
    • Syntax highlighting - Real-time color highlighting of commands, subcommands, and options as you type
    • Command history - Navigate previous commands with arrow keys and recall with !<number> syntax
    • Configurable history - Option to save only successful commands
    • Environment variable expansion - Support for Bash, CMD, and PowerShell variable syntax
  • Dependency injection - Built on Microsoft.Extensions.DependencyInjection
  • Theming system - Customize colors and styles throughout the application with built-in themes or create your own
  • Extensible help system - Customize help output with custom providers
  • Version handling - Display version information with custom providers or inline delegates
  • AOT-ready - Full support for Native AOT compilation
  • Spectre.Console integration - Rich terminal output with colors, tables, and panels

Requirements

  • .NET 10.0 SDK or later

Quick Start

Installation

dotnet add package Alveus.Contour

Basic Usage

using Alveus.Contour;

var builder = new ContourBuilder();

// Register commands
builder.AddCommand<MyCommand>();

// Configure the app
var app = builder.Build();
return app.Run(args);

Defining a Command

using Alveus.Contour.Attributes;

[Command("greet", Description = "Greets a person")]
public class GreetCommand
{
    [DefaultHandler]
    public int Execute(
        [Parameter(0, Description = "Name to greet")]
        string name,
        [Flag("formal", ShortName = "f", Description = "Use formal greeting")]
        bool formal)
    {
        var greeting = formal ? $"Good day, {name}" : $"Hi, {name}!";
        Console.WriteLine(greeting);
        return 0;
    }
}
$ myapp greet Alice
Hi, Alice!

$ myapp greet Alice --formal
Good day, Alice

Core Concepts

Commands

Commands are defined using the [Command] attribute on a class. Each command can have multiple handlers for subcommands.

[Command("git", Description = "Version control operations")]
public class GitCommand
{
    [DefaultHandler]
    public int Default()
    {
        Console.WriteLine("Usage: git <command>");
        return 0;
    }

    [Subcommand("status", Description = "Show working tree status")]
    public int Status()
    {
        // Implementation
        return 0;
    }

    [Subcommand("commit", Description = "Record changes to the repository")]
    public int Commit(
        [Option("message", ShortName = "m", Description = "Commit message", Required = true)]
        string message)
    {
        // Implementation
        return 0;
    }
}

Global Options

Global options are available across all commands:

public class AppGlobalOptions
{
    [Flag("verbose", Description = "Enable verbose output")]
    public bool Verbose { get; set; }

    [Option("config", ShortName = "c", Description = "Configuration file path")]
    public string? ConfigPath { get; set; }
}

// Register in builder
builder.UseGlobalOptions<AppGlobalOptions>();

// Access in commands via dependency injection
[Command("build")]
public class BuildCommand
{
    private readonly AppGlobalOptions _options;

    public BuildCommand(AppGlobalOptions options)
    {
        _options = options;
    }

    [DefaultHandler]
    public int Execute()
    {
        if (_options.Verbose)
            Console.WriteLine("Verbose mode enabled");
        return 0;
    }
}

Middleware

Middleware wraps command execution with cross-cutting concerns like logging, validation, or initialization:

// Class-based middleware
public class LoggingMiddleware : ICommandMiddleware
{
    public int Execute(CommandContext context, Func<int> next)
    {
        Console.WriteLine($"Executing: {context.Arguments.Command}");
        var result = next();
        Console.WriteLine($"Completed with exit code: {result}");
        return result;
    }
}

builder.UseMiddleware<LoggingMiddleware>();

// Or use inline middleware
builder.UseMiddleware((context, next) =>
{
    var startTime = DateTime.Now;
    var result = next();
    var elapsed = DateTime.Now - startTime;
    Console.WriteLine($"Execution time: {elapsed.TotalMilliseconds}ms");
    return result;
});

Skipping Middleware

You can skip middleware for specific commands or subcommands using the [SkipMiddleware] attribute. This is useful for informational commands (like help or version) or system commands where middleware would be inappropriate:

// Skip middleware for entire command
[Command("info")]
[SkipMiddleware]
public class InfoCommand
{
    [DefaultHandler]
    public int Execute()
    {
        Console.WriteLine("Application information...");
        return 0;
    }
}

// Skip middleware for specific subcommands only
[Command("database")]
public class DatabaseCommand
{
    [Subcommand("migrate")]
    public int Migrate()
    {
        // Middleware runs for this subcommand
        Console.WriteLine("Running migrations...");
        return 0;
    }

    [Subcommand("status")]
    [SkipMiddleware]  // Skip middleware for this subcommand
    public int Status()
    {
        Console.WriteLine("Database status: OK");
        return 0;
    }
}

Note: Middleware is automatically skipped for:

  • Help requests (--help, -h, or the help command)
  • Version requests (--version, -v)
  • Invalid commands or validation errors

REPL Mode

Enable an interactive Read-Eval-Print Loop with advanced features:

builder.ConfigureRepl(config =>
{
    config.EnableRepl = true;
    config.EnableHistory = true;
    config.SaveHistoryOnSuccessOnly = false;  // Save all commands, or only successful ones
    config.DefaultToRepl = true;  // Start in REPL when no args provided
    config.EnableSyntaxHighlighting = true;   // Enable real-time syntax highlighting (default: true)
    config.PersistGlobalOptions = true;       // Persist global options across REPL commands (default: false)

    // Prompt receives ContourApp to access current theme
    config.Prompt = (app) =>
    {
        var theme = app.Theme;
        AnsiConsole.Markup($"[{theme.InfoStyle.ToMarkup()}]> [/]");
    };

    // Enable environment variable expansion with multiple syntaxes
    config.EnableVariableExpansion = true;
    config.VariableExpansionSyntax = VariableExpansionSyntax.OsDefault;  // Platform defaults
    config.ErrorOnUnknownVariable = false;  // Return empty string for unknown vars (default: false)

    // Or combine multiple syntaxes using flags:
    // config.VariableExpansionSyntax = VariableExpansionSyntax.Bash | VariableExpansionSyntax.PowerShell;
});

REPL Features:

  • Syntax Highlighting - Commands, subcommands, and options are highlighted as you type

    • Commands appear in the theme's command color
    • Valid options and flags are highlighted in the option color
    • Invalid or unknown tokens remain unhighlighted
    • Colors update dynamically when you change themes
  • Auto-completion - Press Tab to complete commands, subcommands, options, and flags

    > gre<Tab>       # Completes to "greet"
    > greet --<Tab>  # Shows all available options and flags
    > greet -<Tab>   # Shows short-form options
    
  • Command History - Navigate with arrow keys or recall by number

    > greet Alice
    > greet Bob
    > history        # Shows numbered command history
    > !1             # Recalls "greet Alice" for editing
    > <Up Arrow>     # Navigate to previous commands
    
  • Environment Variable Expansion - Use environment variables in commands with support for multiple syntaxes

    # Bash/Linux style
    > greet $USER              # Expands to current user
    > process ${HOME}/file.txt # Braced syntax
    
    # Windows CMD style
    > greet %USERNAME%         # Windows user variable
    > process %TEMP%\file.txt  # Temp directory
    
    # Enhanced Batch/DOS style with substring support
    > echo %PATH:~0,20%        # First 20 characters of PATH
    > echo %USERNAME:~-5%      # Last 5 characters of username
    > echo %TEMP:~3%           # From character 3 to end
    
    # PowerShell style
    > greet $env:USERNAME      # PowerShell syntax
    

    Available syntaxes (can be combined with |):

    • VariableExpansionSyntax.None - Disable expansion
    • VariableExpansionSyntax.Bash - $VAR or ${VAR}
    • VariableExpansionSyntax.Cmd - %VAR%
    • VariableExpansionSyntax.PowerShell - $env:VAR
    • VariableExpansionSyntax.BatchEnhanced - %VAR:~start,length% with substring support
    • VariableExpansionSyntax.OsDefault - Automatically use platform defaults
      • Windows: Cmd | BatchEnhanced
      • Linux/macOS: Bash

    Examples of combining syntaxes:

    // Support both Bash and PowerShell in same session
    config.VariableExpansionSyntax = VariableExpansionSyntax.Bash | VariableExpansionSyntax.PowerShell;
    
    // Support all Windows styles
    config.VariableExpansionSyntax = VariableExpansionSyntax.Cmd | VariableExpansionSyntax.BatchEnhanced | VariableExpansionSyntax.PowerShell;
    
    // Cross-platform support
    config.VariableExpansionSyntax = VariableExpansionSyntax.Bash | VariableExpansionSyntax.Cmd | VariableExpansionSyntax.PowerShell;
    

    Handling Unknown Variables:

    By default, unknown environment variables expand to an empty string:

    > echo $UNKNOWN_VAR something    # Expands to "echo  something"
    

    To receive an error when unknown variables are encountered:

    config.ErrorOnUnknownVariable = true;
    

    With this enabled:

    > echo $UNKNOWN_VAR
    Error: Unknown environment variable: UNKNOWN_VAR
    
  • Persistent Global Options - Global options can persist across REPL commands

    When PersistGlobalOptions is enabled, global options set when starting the app will remain active for all REPL commands:

    # Start app with global options
    $ myapp --verbose --log-level 3
    
    # In REPL, these options remain active
    > info                   # Shows: verbose=true, log-level=3
    > build                  # Runs with verbose=true, log-level=3
    > test --log-level 1     # Override log-level to 1, verbose stays true
    > deploy --nogopt        # Reset to defaults for this command only
    

    Key Features:

    • Global options persist across all REPL commands
    • Options can be overridden on a per-command basis
    • Use --nogopt flag to temporarily reset to defaults
    • When disabled (default), options reset to defaults before each command

    Configuration:

    builder.ConfigureRepl(config =>
    {
        config.PersistGlobalOptions = true;  // Enable persistence (default: false)
    });
    
  • Boolean Flag Values - Flags support explicit boolean values

    Flags can now accept explicit true/false values in addition to the traditional presence/absence:

    # Traditional syntax (presence = true)
    $ myapp --verbose
    
    # Explicit true values
    $ myapp --verbose true
    $ myapp --verbose yes
    $ myapp --verbose on
    $ myapp --verbose 1
    
    # Explicit false values
    $ myapp --verbose false
    $ myapp --verbose no
    $ myapp --verbose off
    $ myapp --verbose 0
    

    This is particularly useful in REPL mode when you want to disable a flag that's already set:

    > info --verbose         # Enable verbose
    > info --verbose false   # Disable verbose
    

    Supported values:

    • True: true, yes, on, 1 (case-insensitive)
    • False: false, no, off, 0 (case-insensitive)
    • No value: Defaults to true (traditional behavior)
  • Built-in Commands

    • exit or quit - Exit the REPL
    • help - Display available commands
    • history - Display command history
    • history clear - Clear command history
    • clear - Clear the screen

Example REPL Session:

$ myapp
Interactive REPL - Type 'exit' to quit

> greet Alice
Hi, Alice!

> greet Bob --formal
Good day, Bob

> history
  # | Command
 ---+-------------------
  1 | greet Alice
  2 | greet Bob --formal

> !1
> greet Alice --formal  # Edit recalled command before running
Good day, Alice

> exit

Theming

Customize the appearance of your CLI application with themes:

// Apply a built-in theme
var app = builder.Build();
app.ApplyTheme(ContourTheme.Dark());        // Dark theme
app.ApplyTheme(ContourTheme.Light());       // Light theme
app.ApplyTheme(ContourTheme.Ultraviolet()); // Vibrant purple/neon
app.ApplyTheme(ContourTheme.Rainbow());     // Colorful rainbow

// Create a custom theme
var customTheme = new ContourTheme
{
    CommandStyle = new Style(Color.Magenta, decoration: Decoration.Bold),
    SubcommandStyle = new Style(Color.Cyan),
    OptionStyle = new Style(Color.Green),
    ErrorStyle = new Style(Color.Red, decoration: Decoration.Bold),
    WarningStyle = new Style(Color.Yellow),
    SuccessStyle = new Style(Color.Green1),
    InfoStyle = new Style(Color.Blue),
    DimStyle = new Style(Color.Grey),
    HighlightStyle = new Style(Color.White, decoration: Decoration.Bold)
};
app.ApplyTheme(customTheme);

// Themes affect:
// - Syntax highlighting in REPL
// - Help output (commands, options, descriptions)
// - Error and success messages
// - Tables, panels, and borders
// - Version display
// - Custom command output (when using theme)

Accessing Theme in Commands:

[Command("mycommand")]
public class MyCommand
{
    private readonly ContourApp _app;

    public MyCommand(ContourApp app)
    {
        _app = app;
    }

    [DefaultHandler]
    public int Execute()
    {
        // Option 1: Use theme-aware markup methods (recommended)
        _app.MarkupLine("[success]Operation completed successfully![/]");
        _app.MarkupLine("[error]Something went wrong![/]");
        _app.MarkupLine("[warning]Warning message[/]");
        _app.MarkupLine("[info]Informational message[/]");
        _app.MarkupLine("[dim]Dimmed text[/] [highlight]Highlighted text[/]");
        _app.MarkupLine("[command]mycommand[/] [subcommand]subcommand[/] [option]--option[/]");

        // Option 2: Access theme directly for advanced scenarios
        var theme = _app.Theme;
        AnsiConsole.MarkupLine($"[{theme.SuccessStyle.ToMarkup()}]Success![/]");

        return 0;
    }
}

Theme-Aware Markup Tags:

ContourApp provides convenient wrapper methods that automatically apply theme colors to your output. These methods support the following tags:

Tag Description Use Case
[error]...[/error] Error messages Errors, failures, exceptions
[warning]...[/warning] Warning messages Warnings, cautions
[success]...[/success] Success messages Confirmations, completions
[info]...[/info] Informational messages General information, prompts
[dim]...[/dim] Dimmed/muted text Secondary information, hints
[highlight]...[/highlight] Highlighted/emphasized text Important values, keywords
[command]...[/command] Command names Commands in help text
[subcommand]...[/subcommand] Subcommand names Subcommands in help text
[option]...[/option] Option/flag names Options and flags in help text

Available Methods:

// Write markup (no newline)
_app.Markup("[error]Error:[/] Something went wrong");

// Write markup with newline
_app.MarkupLine("[success]Success:[/] Task completed");

// Write interpolated markup (no newline)
_app.MarkupInterpolated($"[info]Processing {filename}...[/]");

// Write interpolated markup with newline
_app.MarkupLineInterpolated($"[success]Processed {count} files[/]");

// Parse theme markup without writing to console
string spectreMarkup = _app.ParseThemeMarkup("[error]Error:[/] [warning]Warning[/]");
// Returns: "[red]Error:[/] [yellow]Warning[/]" (actual colors depend on theme)

// Use with Spectre.Console directly
var panel = new Panel(_app.ParseThemeMarkup("[info]Panel content[/]"));
AnsiConsole.Write(panel);

Benefits:

  • Theme-independent: Your code doesn't need to know about specific colors
  • Automatic updates: Changes to the theme automatically affect all output
  • Cleaner code: Simpler syntax compared to accessing theme styles directly
  • Consistent styling: Ensures consistent use of theme colors across commands

Version Handling

Customize version display with access to the current theme:

// Using a custom provider class
public class CustomVersionProvider : IVersionProvider
{
    public int ShowVersion()
    {
        Console.WriteLine("MyApp v2.1.0");
        return 0;
    }
}

builder.UseVersionProvider<CustomVersionProvider>();

// Or use inline version provider with theme access
builder.UseVersionProvider((app) =>
{
    var theme = app?.Theme ?? ContourTheme.Default();
    AnsiConsole.MarkupLine($"[{theme.InfoStyle.ToMarkup()}]MyApp v2.1.0 - Build 12345[/]");
    return 0;
});

Help System

Customize help output:

// Using a custom provider class
public class CustomHelpProvider : IHelpProvider
{
    public int ShowHelp(Type commandType, CommandAvailability context)
    {
        // Custom help implementation
        return 0;
    }

    public int ShowAllHelp(CommandAvailability context)
    {
        // Custom help implementation
        return 0;
    }
}

builder.UseHelpProvider<CustomHelpProvider>();

Attributes Reference

Command Attributes

  • [Command] - Marks a class as a command

    • Name - Command name (required)
    • Description - Command description
    • Aliases - Alternative names
    • Availability - Where command is available (Cli, Repl, or Both)
  • [DefaultHandler] - Marks the default command handler method

  • [Subcommand] - Marks a method as a subcommand handler

    • Name - Subcommand name (required)
    • Description - Subcommand description
    • Availability - Where subcommand is available

Parameter Attributes

  • [Parameter] - Positional parameter

    • Position - Zero-based position
    • Description - Parameter description
    • MinCount - Minimum required count (for collections)
  • [Option] - Named option that accepts a value

    • Name - Option name (required)
    • ShortName - Short form (e.g., -o)
    • Description - Option description
    • DefaultValue - Default value if not provided
    • Required - Whether option is required
  • [Flag] - Boolean flag

    • Name - Flag name (required)
    • ShortName - Short form (e.g., -v)
    • Description - Flag description
    • DefaultValue - Default value (defaults to false)

Building and Running

Build the Solution

dotnet restore
dotnet build

Project Structure

Alveus.Contour/
├── Alveus.Contour/           # Core library
│   ├── Attributes/           # Command and parameter attributes
│   ├── Commands/             # Built-in commands (help, history, etc.)
│   ├── Help/                 # Help system
│   ├── Middleware/           # Middleware infrastructure
│   ├── Parsing/              # Argument parsing
│   ├── REPL/                 # REPL implementation
│   ├── Routing/              # Command routing
│   ├── Version/              # Version handling
│   ├── ContourApp.cs         # Main application class
│   └── ContourBuilder.cs     # Fluent builder API
└── Alveus.Contour.Demo/      # Demo application
    ├── Commands/             # Example commands
    ├── Middleware/           # Example middleware
    └── Program.cs            # Demo entry point

Advanced Examples

Command with Collection Parameters

[Command("process")]
public class ProcessCommand
{
    [DefaultHandler]
    public int Execute(
        [Option("tag", ShortName = "t", Description = "Tags (can be specified multiple times)")]
        IEnumerable<string> tags,
        [Parameter(0, MinCount = 1, Description = "Files to process")]
        IEnumerable<string> files)
    {
        foreach (var file in files)
        {
            Console.WriteLine($"Processing: {file}");
            foreach (var tag in tags ?? Enumerable.Empty<string>())
            {
                Console.WriteLine($"  Tag: {tag}");
            }
        }
        return 0;
    }
}
$ myapp process file1.txt file2.txt -t alpha -t beta
Processing: file1.txt
  Tag: alpha
  Tag: beta
Processing: file2.txt
  Tag: alpha
  Tag: beta

Async Command Handlers

public class AsyncMiddleware : ICommandMiddlewareAsync
{
    public async Task<int> ExecuteAsync(CommandContext context, Func<Task<int>> next)
    {
        await Task.Delay(100);  // Async work
        return await next();
    }
}

builder.UseMiddleware<AsyncMiddleware>();

Multiple Global Options Types

public class AppGlobalOptions
{
    [Flag("verbose", ShortName = "v")]
    public bool Verbose { get; set; }
}

public class DatabaseOptions
{
    [Option("connection", ShortName = "db")]
    public string? ConnectionString { get; set; }
}

builder.UseGlobalOptions<AppGlobalOptions>();
builder.UseGlobalOptions<DatabaseOptions>();

Dependencies

  • Microsoft.Extensions.DependencyInjection (10.0.0) - Dependency injection
  • Spectre.Console (0.54.0) - Rich terminal UI

What's New

Persistent Global Options in REPL

Global options can now persist across REPL commands, providing a more seamless interactive experience:

  • Persistent State: Global options set when launching the app remain active for all REPL commands
  • Per-command Override: Override global options on individual commands without changing the persisted values
  • Reset to Defaults: Use the --nogopt flag to temporarily reset global options to defaults for a single command
  • Configurable Behavior: Enable or disable persistence with config.PersistGlobalOptions

This feature is particularly useful when you want to maintain a consistent set of options (like --verbose or --log-level) throughout a REPL session without having to specify them on every command.

Boolean Flag Values

Flags now support explicit boolean values, giving you more control over their state:

  • Explicit True/False: Set flags to true or false explicitly using --verbose true or --verbose false
  • Multiple Formats: Supports multiple boolean formats: true/false, yes/no, on/off, 1/0
  • Case Insensitive: All boolean values are parsed case-insensitively
  • REPL Friendly: Especially useful in REPL mode for toggling flags on and off

This feature makes it easier to disable flags in REPL mode and provides a more intuitive syntax for flag manipulation.

Theming System

Alveus.Contour includes a comprehensive theming system that allows you to customize the appearance of your CLI application:

  • Built-in themes: Default, Dark, Light, Monochrome, Ultraviolet, and Rainbow
  • Custom themes: Create your own themes by defining styles for commands, options, errors, and more
  • Dynamic theme switching: Change themes at runtime (even in REPL mode)
  • Automatic propagation: Themes automatically apply to syntax highlighting, help output, error messages, and all UI elements
  • Theme-aware commands: Commands can access the current theme via dependency injection

Syntax Highlighting in REPL

The REPL features real-time syntax highlighting:

  • Commands, subcommands, and options are color-coded as you type
  • Invalid or unknown elements remain unhighlighted for immediate visual feedback
  • Highlighting uses the current theme and updates dynamically when themes change
  • Provides a more intuitive and polished interactive experience

Custom Line Editor

Alveus.Contour includes a custom-built line editor, removing the dependency on the ReadLine library. This provides:

  • Full control over keyboard input handling (arrow keys, Home, End, backspace, delete)
  • Intelligent auto-completion for commands, subcommands, options, and flags (including global options)
  • Command history with configurable success-only saving
  • History recall with !<number> syntax allowing pre-execution editing
  • Environment variable expansion with support for multiple shell syntaxes (Bash, CMD, PowerShell)
  • Better integration with the REPL environment and syntax highlighting

License

Refer to the LICENSE file in the repository.

Release v0.2.0

Dependencies

ID Version Target Framework
Microsoft.Extensions.DependencyInjection 10.0.0 net10.0
Spectre.Console 0.54.0 net10.0
Details
NuGet
2025-12-21 17:18:31 +01:00
13
Alveus Dev (https://alveus.dev)
117 KiB
Assets (4)
Versions (8) View all
0.3.2 2025-12-21
0.3.1 2025-12-19
0.3.0 2025-12-18
0.2.2 2025-12-08
0.2.1 2025-12-08