farlee2121.System.CommandLine.PropertyMapBinder
0.1.0-alpha1
See the version list below for details.
dotnet add package farlee2121.System.CommandLine.PropertyMapBinder --version 0.1.0-alpha1
NuGet\Install-Package farlee2121.System.CommandLine.PropertyMapBinder -Version 0.1.0-alpha1
<PackageReference Include="farlee2121.System.CommandLine.PropertyMapBinder" Version="0.1.0-alpha1" />
paket add farlee2121.System.CommandLine.PropertyMapBinder --version 0.1.0-alpha1
#r "nuget: farlee2121.System.CommandLine.PropertyMapBinder, 0.1.0-alpha1"
// Install farlee2121.System.CommandLine.PropertyMapBinder as a Cake Addin #addin nuget:?package=farlee2121.System.CommandLine.PropertyMapBinder&version=0.1.0-alpha1&prerelease // Install farlee2121.System.CommandLine.PropertyMapBinder as a Cake Tool #tool nuget:?package=farlee2121.System.CommandLine.PropertyMapBinder&version=0.1.0-alpha1&prerelease
System.CommandLine.PropertyMapBinding
Motivation / what is this
This library is an experiment. The goal is to create an intuitive handler binding experience for System.CommandLine. A few goals
- intuitive binding of complex types
- support handler declaraction as a self-contained expression (no reference to symbol instances)
- blending multiple binding rules for a customizable and consistent binding experience
- easy extension of the binding pipeline
Examples
All examples assume this following definitions are available
Option<int> frequencyOpt = new Option<int>(new string[] { "--frequency", "-f" }, "such description");
RootCommand rootCommand = new RootCommand("Test Test")
{
new Argument<string>("print-me", "gets printed"),
frequencyOpt,
new Option<IEnumerable<int>>(new string[] { "--list", "-l" }, "make sure lists work")
{
Arity = ArgumentArity.ZeroOrMore
};
};
public static async Task SuchHandler(SuchInput input)
{
Console.WriteLine($"printme: {input.PrintMe}; \nfrequency: {input.Frequency}; \nlist:{string.Join(",",input.SuchList)}");
}
public class SuchInput {
public int Frequency { get; set; }
public string? PrintMe { get; set; }
public IEnumerable<int> SuchList { get; set; } = Enumerable.Empty<int>();
}
Pipeline
The backbone construct is BinderPipeline
.
rootCommand.Handler = CommandHandler.FromPropertyMap(SuchHandler,
new BinderPipeline<SuchInput>{
PropertyMap.FromName<SuchInput, string>("print-me", model => model.PrintMe ),
PropertyMap.FromReference<SuchInput, int>(frequencyOpt, model => model.Frequency),
PropertyMap.FromName<SuchInput, IEnumerable<int>>("-l", model => model.SuchList)
});
BinderPipeline
is really a collection of IPropertyBinder
. Each IPropertyBinder
defines a strategy for assigning input to the target object.
The pipeline executes each binder in the order they are given. This means later binders will override earlier ones. This means we can
- use multiple rules to bind properties
- define a priority/fallback chain for any given property
Builder
We can also build the pipeline through a set of extension methods. The primary benefit is improved type inference (thus less explicit typing). Binders will still be called in the order registered.
rootCommand.Handler = CommandHandler.FromPropertyMap(SuchHandler,
new BinderPipeline<SuchInput>()
.MapFromName("print-me", model => model.PrintMe)
.MapFromReference(frequencyOpt, model => model.Frequency)
.MapFromName("-l", model => model.SuchList)
);
Blended Conventions
The pipeline can handle many approaches binding input. Here's an example of a simple naming convention with an explicit mapping fallback
rootCommand.Handler = CommandHandler.FromPropertyMap(SuchHandler,
new BinderPipeline<SuchInput>()
.MapFromNameConvention(TextCase.Pascal)
.MapFromName("-l", model => model.SuchList)
);
Possible extensions to the pipeline
Here are some cases I haven't implemented, but would be fairly easy to add
- map default values from configuration
- Ask a user for any missing inputs
- can be done with the existing setter overload, but prompts could be automated with a signature like
.PromptIfMissing(name, selector)
- can be done with the existing setter overload, but prompts could be automated with a signature like
- match properties based on type
- Set a value directly
- can be done with the existing setter overload, but could be simpler
.MapFromValue(c => c.Frequency, 5)
- can be done with the existing setter overload, but could be simpler
How to extend
Extending the pipeline is fairly easy.
The core contract is
public interface IPropertyBinder<InputModel>
{
InputModel Bind(InputModel InputModel, InvocationContext context);
}
IPropertyBinder
takes an instance of the target input class and the invocation context provided by the parser.
Input definitions (i.e. options and arguments) can be found in context.ParserResult.CommandResult.Symbol.Children
and values can be fetched by functions like context.ParseResult.GetValueForOption
.
Examples exist for symbol name and property path and simple name conventions.
The other key step is to register extension methods on BinderPipeline
. The main behaviors to consider
- the extension should add it's binder to the end of the pipeline (e.g.
pipeline.Add(yourBinder)
) - The extension should return the modified copy of the pipeline (i.e. always has return type
BinderPipeline<T>
)
Status of project
A successful experiment. Usable, but not production-tested. No guarantees of support
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.CommandLine (>= 2.0.0-beta2.21617.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on farlee2121.System.CommandLine.PropertyMapBinder:
Package | Downloads |
---|---|
farlee2121.System.CommandLine.PropertyMapBinder.NameConventionBinder
A System.CommandLine.PropertyMapBinder extension for mapping console input to properties by simple naming conventions |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.0 | 2,179 | 3/23/2022 |
1.0.0-preview1 | 1,093 | 3/5/2022 |
0.1.0-beta1 | 985 | 1/13/2022 |
0.1.0-alpha1 | 147 | 1/9/2022 |