FluentCommandLine is a typed, compositional command-line parsing and execution framework for .NET.
Unlike traditional command-line parsers that rely on flat flags, switches, or option bags (e.g., -f file.txt --window 100), FluentCommandLine models command-line invocations as functional expressions and pipeline graphs. Commands and arguments are resolved as strongly-typed strategies that construct and compose objects, evaluate pipelines, and execute target operations.
- Compositional Grammar: Methods return typed objects that seamlessly serve as inputs to downstream strategies.
- Type-Safe Strategy Registries: Dedicated registries per target CLR type (
FluentMethodRegistry), allowing modular registration of commands and sub-strategies. - Automatic Sub-Strategy Resolution: When a strategy parameter expects a registered type, the parser automatically delegates argument parsing to that type's registry.
- Ambient Context Management: Scoped execution contexts (
FluentEnvironmentScope,FluentEnvironment.Current) and strongly-typed state bags (FluentContextData). - Rich Parameter Binding: Supports primitives, numbers, enums,
TimeSpan,DateTimeOffset, custom converters, default values, and variadic (params) arrays. - Self-Documenting CLI: Built-in interactive help, strategy listing (
list), type exploration (info) support, and clear diagnostic error reporting.
The framework consists of several cooperating components:
+-------------------------------------------------------------+
| FluentEnvironment |
| |
| +---------------------+ +------------------------+ |
| | FluentContextData | | ServeTypes | |
| | (Typed State Bag) | | (Top-Level Commands) | |
| +---------------------+ +------------------------+ |
| |
| +-------------------------------------------------------+ |
| | Registries: Dictionary<Type, Registry> | |
| | | |
| | Type A -> FluentMethodRegistry (Strategies for A) | |
| | Type B -> FluentMethodRegistry (Strategies for B) | |
| +-------------------------------------------------------+ |
+-------------------------------------------------------------+
The root orchestrator of the CLI engine. It holds:
Registries: Mapping of CLRTypetoFluentMethodRegistry.ServeTypes: Array of types that can be invoked directly as top-level commands (e.g.,FarmCommand,IAction).Context: An instance ofFluentContextDatastoring ambient runtime services, configuration, and dependencies.- Parsing API: Methods like
ParseOne(...), andListCapture(...).
Provides thread-safe access to the active FluentEnvironment during strategy invocation via FluentEnvironment.Current. It uses a thread-static stack so nested or concurrent evaluations remain isolated.
using (env.EnterScope())
{
// FluentEnvironment.Current is accessible here
}A type-indexed dependency container for storing ambient contextual data:
var context = env.Context;
// Storing state
context.Set(new DatabaseConnection("Server=..."));
// Retrieving state
if (context.TryGet<DatabaseConnection>(out var db))
{
// Use db
}
// Or throwing if absent
var requiredDb = context.Get<DatabaseConnection>();Manages strategy definitions for creating instances of a specific RegistryType.
- Each entry in
Strategiesis aFluentMethod. - Implements recursive parsing (
TryParse) to resolve command tokens and argument sub-trees. - Supports default fallback strategies when no explicit strategy name is provided.
Represents a callable strategy:
- Wraps a static
MethodInfoorDelegate. - Contains metadata:
Name,Help,IsDefault, and a list ofParameterdescriptors (with names, types, default values, and help text).
Commands and strategies are defined using standard C# static methods decorated with attributes.
| Attribute | Target | Purpose |
|---|---|---|
[FluentMethod("name", def: bool)] |
Method | Registers a static method as a named CLI strategy. def: true marks it as the default strategy for its return type. |
[KV_FA(FluentAttribute.Help, "description")] or [Description("...")] |
Method / Parameter | Provides human-readable help documentation. |
[KV_FA(FluentAttribute.Def, "value")] |
Parameter | Defines a string fallback default value if the parameter is omitted. |
A module is a class containing one or more static strategy methods:
using System.ComponentModel;
using FluentCommandLine;
public static class MathModule
{
// Module initialization hook (called automatically by AddModule)
public static void FluentModuleInitialize(FluentEnvironment env)
{
env.Context.Set(new CalculatorConfig { Precision = 4 });
}
[FluentMethod("add")]
[Description("Adds two floating-point numbers.")]
public static double Add(
[Description("First operand")] double a,
[Description("Second operand")] double b)
{
return a + b;
}
[FluentMethod("scale", def: true)]
[Description("Multiplies a value by a factor.")]
public static double Scale(
[Description("Input value")] double value,
[KV_FA(FluentAttribute.Def, "1.0")] double factor)
{
return value * factor;
}
}var env = new FluentEnvironment();
// Register the module
env.AddModule<MathModule>();
// Designate double as a top-level executable type
env.ServeTypes = new[] { typeof(double) };FluentCommandLine treats command arguments as expressions in a typed grammar.
Suppose you have the following types and strategies:
public record DataSource(string Path);
public record Filter(string Expression);
public record ProcessPipeline(DataSource Source, Filter Filter);
public static class PipelineModule
{
[FluentMethod("file")]
public static DataSource FromFile(string path) => new(path);
[FluentMethod("where")]
public static Filter Where(string expression) => new(expression);
[FluentMethod("pipeline")]
public static ProcessPipeline Create(DataSource source, Filter filter)
=> new(source, filter);
[FluentMethod("run")]
public static void Run(ProcessPipeline pipeline)
{
Console.WriteLine($"Running pipeline for {pipeline.Source.Path} with filter {pipeline.Filter.Expression}");
}
}When invoking the CLI with:
run pipeline file "data.csv" where "Age > 30"
The parser constructs the execution tree:
Run(
pipeline: Create(
source: FromFile("data.csv"),
filter: Where("Age > 30")
)
)
Each token routes to the strategy matching the expected parameter type at that position.
- Primitives and Built-in Types:
string,bool,byte,sbyte,short,ushort,int,uint,long,ulong,float,double,decimal.TimeSpan(e.g.,00:01:30,100ms,5s).DateTimeOffset/DateTime.Enumvalues (matched case-insensitively).
- Sub-Strategies (
RegistryType):- If a parameter type is registered in
env.Registries, the parser recursively consumes tokens to construct that object.
- If a parameter type is registered in
- Variadic / Params Arguments:
- Methods accepting
params string[]orparams T[]consume subsequent arguments until end-of-stream or until encountering the special delimiter.END..
- Methods accepting
FluentCommandLine automatically generates syntax guides and interactive inspection commands.
list: Displays all top-level commands and their parameter signatures.Available Commands add <a> <b> — Adds two floating-point numbers. scale <value> [factor] — Multiplies a value by a factor.-helporhelp: Displays general help and command usage.
If a command contains syntax or type conversion errors, the parser prints descriptive diagnostics highlighting missing arguments or invalid conversions:
Error: Expected type 'Double' for parameter 'factor' in strategy 'scale', but received 'abc'.
using System;
using FluentCommandLine;
public class Program
{
public record FileInput(string Path);
public record Operation(string Name);
public static class CliModule
{
[FluentMethod("file")]
public static FileInput LoadFile(string path) => new(path);
[FluentMethod("op")]
public static Operation SetOp(string name) => new(name);
[FluentMethod("process")]
public static void Process(FileInput input, Operation op)
{
Console.WriteLine($"Processing file '{input.Path}' with operation '{op.Name}'");
}
}
public static int Main(string[] args)
{
var env = new FluentEnvironment();
env.AddModule<CliModule>();
env.ServeTypes = new[] { typeof(void) };
int cursor = 0;
var parsed = env.ParseOne(args, ref cursor);
//Normaly parsed would represent delayed execution and would be invoked here.
return 0;
}
}Invocations:
# Display available commands
dotnet run -- list
# Run the process strategy
dotnet run -- process file "data.json" op "Transform"