Page 30 - MSDN Magazine, March 2019
P. 30

convert an argument. In addition, the model might leverage attri- butes to define aliases, for example. Alternatively, you could write a model that parses a docopt file (see docopt.org) for the configuration. Each of these app models would invoke the System.CommandLine configuration API. Of course, developers might prefer to call System.CommandLine directly from their application rather than via an app model, and this approach is also supported.
Figure 4 Working with System.CommandLine Directly
Passing Parameters to the .NET Core Executable
When specifying command-line arguments in combination with the dotnet run command, the full command line would be:
dotnet run --project imageconv.csproj -- --input sunrise.CR2 --output sunrise.JPG
If you’re running dotnet from the same directory in which the csproj file was located, however, the command line would read:
dotnet run -- --input sunrise.CR2 --output sunrise.JPG
The dotnet run command uses the “--” as the identifier, indicating that all other arguments should be passed to the executable for it to parse.
Starting with .NET Core 2.2, there’s also support for self-contained applications (even on Linux). With a self-contained application, you can launch it without using dotnet run and instead just rely on the resulting executable, like so:
imageconv.exe --input sunrise.CR2 --output sunrise.JPG
Obviously, this is expected behavior to Windows users.
using System;
using System.CommandLine;
using System.CommandLine.Invocation; using System.IO;
...
public static async Task<int> Main(params string[] args) {
RootCommand rootCommand = new RootCommand(
description: "Converts an image file from one format to another." , treatUnmatchedTokensAsErrors: true);
Option inputOption = new Option(
aliases: new string[] { "--input", "-i" }
, description: "The path to the image file that is to be converted." , argument: new Argument<FileInfo>());
rootCommand.AddOption(inputOption);
Option outputOption = new Option(
aliases: new string[] { "--output", "-o" }
, description: "The target name of the output file after conversion." , argument: new Argument<FileInfo>());
rootCommand.AddOption(outputOption);
Option xCropSizeOption = new Option(
aliases: new string[] { "--x-crop-size", "-x" }
, description: "The x dimension size to crop the picture.
The default is 0 indicating no cropping is required."
, argument: new Argument<FileInfo>());
rootCommand.AddOption(xCropSizeOption);
Option yCropSizeOption = new Option(
aliases: new string[] { "--y-crop-size", "-y" }
, description: "The Y dimension size to crop the picture.
The default is 0 indicating no cropping is required."
, argument: new Argument<FileInfo>());
rootCommand.AddOption(yCropSizeOption);
rootCommand.Handler =
CommandHandler.Create<FileInfo, FileInfo, int, int>(Convert);
return await rootCommand.InvokeAsync(args); }
static public void Convert(
FileInfo input, FileInfo output, int xCropSize = 0, int yCropSize = 0)
{
// Convert...
}
Figure 5 Using Method-First Approach to Configure System.CommandLine
Making the Complex Possible
Earlier, I mentioned that the functionality for keeping simple things simple was basic. This is because enabling command-line parsing via the Main method still lacks some features that some might consider important. For example, you can’t configure a (sub) command or an option alias. If you encounter these lim- itations, you can build your own app model or call into the Core (System.CommandLine assembly) directly.
System.CommandLine includes classes that represent the constructs of a command line. This includes Command (and RootCommand), Option and Argument. Figure 4 provides some sample code for invoking System.CommandLine directly and configuring it to accomplish the basic functionality defined in the help text of Figure 1.
System.CommandLine includes classes that represent the constructs of a command line.
In this example, rather than rely on a Main app model to define the command-line configuration, each construct is instantiated explic- itly. The only functional difference is the addition of aliases for each option. Leveraging the Core API directly, however, provides more control than what’s possible with the Main like approach.
For example, you could define subcommands, like an image- enhance command that includes its own set of options and arguments related to the enhance action. Complex command-line programs have multiple subcommands and even sub-subcommands. The dotnet command, for example, has the dotnet sln add command, where dotnet is the root command, sln is one of the many subcom- mands, and add (or list and remove) is a child command of sln.
The final call to InvokeAsync implicitly sets up many of the features automatically including:
public static async Task<int> Main(params string[] args) {
RootCommand rootCommand = new RootCommand(
description: "Converts an image file from one format to another." , treatUnmatchedTokensAsErrors: true);
MethodInfo method = typeof(Program).GetMethod(nameof(Convert));
rootCommand.ConfigureFromMethod(method); rootCommand.Children["--input"].AddAlias("-i"); rootCommand.Children["--output"].AddAlias("-o");
return await rootCommand.InvokeAsync(args); }
24 msdn magazine
Essential .NET


































































































   28   29   30   31   32