Vytvoření vazby argumentů na obslužné rutiny System.CommandLine
Důležité
System.CommandLine
je aktuálně ve verzi PREVIEW a tato dokumentace je určená pro verzi 2.0 beta 4.
Některé informace se týkají předběžné verze produktu, který může být podstatně změněn před vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Proces analýzy argumentů a jejich poskytnutí kódu obslužné rutiny příkazů se nazývá vazba parametrů. System.CommandLine
má schopnost svázat mnoho typů argumentů předdefinovaných. Například celočíselné hodnoty, výčty a objekty systému souborů, například FileInfo a DirectoryInfo které mohou být svázané. Lze také svázat několik System.CommandLine
typů.
Integrované ověřování argumentů
Argumenty mají očekávané typy a arity. System.CommandLine
odmítne argumenty, které neodpovídají těmto očekáváním.
Například chyba analýzy se zobrazí, pokud argument pro celočíselnou možnost není celé číslo.
myapp --delay not-an-int
Cannot parse argument 'not-an-int' as System.Int32.
Chyba zápisu se zobrazí v případě, že je více argumentů předáno možnosti, která má maximální počet argumentů:
myapp --delay-option 1 --delay-option 2
Option '--delay' expects a single argument but 2 were provided.
Toto chování lze přepsat nastavením Option.AllowMultipleArgumentsPerToken na true
. V takovém případě můžete opakovat možnost, která má maximální počet zápisů, ale přijme se pouze poslední hodnota na řádku. V následujícím příkladu by se hodnota three
předala aplikaci.
myapp --item one --item two --item three
Vazba parametrů až 8 možností a argumentů
Následující příklad ukazuje, jak vytvořit vazbu možností na parametry obslužné rutiny příkazů voláním SetHandler:
var delayOption = new Option<int>
("--delay", "An option whose argument is parsed as an int.");
var messageOption = new Option<string>
("--message", "An option whose argument is parsed as a string.");
var rootCommand = new RootCommand("Parameter binding example");
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);
rootCommand.SetHandler(
(delayOptionValue, messageOptionValue) =>
{
DisplayIntAndString(delayOptionValue, messageOptionValue);
},
delayOption, messageOption);
await rootCommand.InvokeAsync(args);
public static void DisplayIntAndString(int delayOptionValue, string messageOptionValue)
{
Console.WriteLine($"--delay = {delayOptionValue}");
Console.WriteLine($"--message = {messageOptionValue}");
}
Parametry lambda jsou proměnné, které představují hodnoty možností a argumentů:
(delayOptionValue, messageOptionValue) =>
{
DisplayIntAndString(delayOptionValue, messageOptionValue);
},
Proměnné, které následují po výrazu lambda, představují objekty možností a argumentů, které jsou zdroji hodnot možností a argumentů:
delayOption, messageOption);
Možnosti a argumenty musí být deklarovány ve stejném pořadí v lambda a v parametrech, které následují za lambda. Pokud pořadí není konzistentní, výsledkem bude jeden z následujících scénářů:
- Pokud jsou možnosti nebo argumenty mimo pořadí různé typy, vyvolá se výjimka za běhu. Může se například zobrazit místo,
int
kdestring
by měl být v seznamu zdrojů. - Pokud jsou možnosti nebo argumenty mimo pořadí stejného typu, obslužná rutina bezobslužně získá nesprávné hodnoty v parametrech zadaných pro něj. Může se například zobrazit možnost
x
,string
kdestring
by měla být možnosty
v seznamu zdrojů. V takovém případě získá proměnná pro hodnotu možnostiy
hodnotu možnostix
.
Existují přetížení SetHandler , která podporují až 8 parametrů s synchronními i asynchronními podpisy.
Vazba parametru s více než 8 možnostmi a argumenty
Pokud chcete zpracovat více než 8 možností nebo vytvořit vlastní typ z více možností, můžete použít InvocationContext
nebo vlastní pořadač.
Použití InvocationContext
SetHandler Přetížení poskytuje přístup k objektu InvocationContext a můžete použít InvocationContext
k získání libovolného počtu hodnot možností a argumentů. Příklady najdete v tématu Nastavení ukončačních kódů a popisovačů ukončení.
Použití vlastního pořadače
Vlastní pořadač umožňuje zkombinovat více hodnot možností nebo argumentů do komplexního typu a předat je do jednoho parametru obslužné rutiny. Předpokládejme, že máte Person
typ:
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
Vytvořte třídu odvozenou z BinderBase<T>, kde je typ, který T
se má sestavit na základě vstupu příkazového řádku:
public class PersonBinder : BinderBase<Person>
{
private readonly Option<string> _firstNameOption;
private readonly Option<string> _lastNameOption;
public PersonBinder(Option<string> firstNameOption, Option<string> lastNameOption)
{
_firstNameOption = firstNameOption;
_lastNameOption = lastNameOption;
}
protected override Person GetBoundValue(BindingContext bindingContext) =>
new Person
{
FirstName = bindingContext.ParseResult.GetValueForOption(_firstNameOption),
LastName = bindingContext.ParseResult.GetValueForOption(_lastNameOption)
};
}
Pomocí vlastního pořadače můžete vlastní typ předat obslužné rutině stejným způsobem, jakým získáte hodnoty pro možnosti a argumenty:
rootCommand.SetHandler((fileOptionValue, person) =>
{
DoRootCommand(fileOptionValue, person);
},
fileOption, new PersonBinder(firstNameOption, lastNameOption));
Tady je kompletní program, ze kterého jsou převzaty předchozí příklady:
using System.CommandLine;
using System.CommandLine.Binding;
public class Program
{
internal static async Task Main(string[] args)
{
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "An option whose argument is parsed as a FileInfo",
getDefaultValue: () => new FileInfo("scl.runtimeconfig.json"));
var firstNameOption = new Option<string>(
name: "--first-name",
description: "Person.FirstName");
var lastNameOption = new Option<string>(
name: "--last-name",
description: "Person.LastName");
var rootCommand = new RootCommand();
rootCommand.Add(fileOption);
rootCommand.Add(firstNameOption);
rootCommand.Add(lastNameOption);
rootCommand.SetHandler((fileOptionValue, person) =>
{
DoRootCommand(fileOptionValue, person);
},
fileOption, new PersonBinder(firstNameOption, lastNameOption));
await rootCommand.InvokeAsync(args);
}
public static void DoRootCommand(FileInfo? aFile, Person aPerson)
{
Console.WriteLine($"File = {aFile?.FullName}");
Console.WriteLine($"Person = {aPerson?.FirstName} {aPerson?.LastName}");
}
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
public class PersonBinder : BinderBase<Person>
{
private readonly Option<string> _firstNameOption;
private readonly Option<string> _lastNameOption;
public PersonBinder(Option<string> firstNameOption, Option<string> lastNameOption)
{
_firstNameOption = firstNameOption;
_lastNameOption = lastNameOption;
}
protected override Person GetBoundValue(BindingContext bindingContext) =>
new Person
{
FirstName = bindingContext.ParseResult.GetValueForOption(_firstNameOption),
LastName = bindingContext.ParseResult.GetValueForOption(_lastNameOption)
};
}
}
Nastavení ukončovací kódy
TaskExistuje -vrácení Func přetížení SetHandler. Pokud je obslužná rutina volána z asynchronního kódu, můžete vrátit Task<int>
z obslužné rutiny, která používá jednu z těchto rutin, a použít int
hodnotu k nastavení ukončovací kód procesu, jak je znázorněno v následujícím příkladu:
static async Task<int> Main(string[] args)
{
var delayOption = new Option<int>("--delay");
var messageOption = new Option<string>("--message");
var rootCommand = new RootCommand("Parameter binding example");
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);
rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
{
Console.WriteLine($"--delay = {delayOptionValue}");
Console.WriteLine($"--message = {messageOptionValue}");
return Task.FromResult(100);
},
delayOption, messageOption);
return await rootCommand.InvokeAsync(args);
}
Pokud ale samotná lambda musí být asynchronní, nemůžete vrátit Task<int>
hodnotu . V takovém případě použijte InvocationContext.ExitCode. Instanci vloženou InvocationContext
do lambda můžete získat pomocí přetížení SetHandler, které určuje InvocationContext
jako jediný parametr. Toto SetHandler
přetížení neumožňuje zadat IValueDescriptor<T>
objekty, ale můžete získat hodnoty možností a argumentů z ParseResult vlastnost InvocationContext
, jak je znázorněno v následujícím příkladu:
static async Task<int> Main(string[] args)
{
var delayOption = new Option<int>("--delay");
var messageOption = new Option<string>("--message");
var rootCommand = new RootCommand("Parameter binding example");
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);
rootCommand.SetHandler(async (context) =>
{
int delayOptionValue = context.ParseResult.GetValueForOption(delayOption);
string? messageOptionValue = context.ParseResult.GetValueForOption(messageOption);
Console.WriteLine($"--delay = {delayOptionValue}");
await Task.Delay(delayOptionValue);
Console.WriteLine($"--message = {messageOptionValue}");
context.ExitCode = 100;
});
return await rootCommand.InvokeAsync(args);
}
Pokud nemáte asynchronní práci, můžete použít Action přetížení. V takovém případě nastavte ukončovací kód InvocationContext.ExitCode
stejným způsobem jako asynchronní lambda.
Ukončovací kód má výchozí hodnotu 1. Pokud ji explicitně nenastavíte, jeho hodnota se nastaví na 0, když se obslužná rutina obvykle ukončí. Pokud je vyvolán výjimka, zachová výchozí hodnotu.
Podporované typy
Následující příklady ukazují kód, který spojuje některé běžně používané typy.
Výčty
Hodnoty enum
typů jsou vázány názvem a vazba nerozlišuje velká a malá písmena:
var colorOption = new Option<ConsoleColor>("--color");
var rootCommand = new RootCommand("Enum binding example");
rootCommand.Add(colorOption);
rootCommand.SetHandler((colorOptionValue) =>
{ Console.WriteLine(colorOptionValue); },
colorOption);
await rootCommand.InvokeAsync(args);
Tady je ukázkový vstup příkazového řádku a výsledný výstup z předchozího příkladu:
myapp --color red
myapp --color RED
Red
Red
Pole a seznamy
Podporuje se mnoho běžných typů, které se implementují IEnumerable . Příklad:
var itemsOption = new Option<IEnumerable<string>>("--items")
{ AllowMultipleArgumentsPerToken = true };
var command = new RootCommand("IEnumerable binding example");
command.Add(itemsOption);
command.SetHandler((items) =>
{
Console.WriteLine(items.GetType());
foreach (string item in items)
{
Console.WriteLine(item);
}
},
itemsOption);
await command.InvokeAsync(args);
Tady je ukázkový vstup příkazového řádku a výsledný výstup z předchozího příkladu:
--items one --items two --items three
System.Collections.Generic.List`1[System.String]
one
two
three
Vzhledem k tomu AllowMultipleArgumentsPerToken , že je nastavená hodnota true
, následující vstup má za následek stejný výstup:
--items one two three
Typy systému souborů
Aplikace příkazového řádku, které pracují se systémem souborů, mohou používat FileSystemInfo, FileInfoa DirectoryInfo typy. Následující příklad ukazuje použití FileSystemInfo
:
var fileOrDirectoryOption = new Option<FileSystemInfo>("--file-or-directory");
var command = new RootCommand();
command.Add(fileOrDirectoryOption);
command.SetHandler((fileSystemInfo) =>
{
switch (fileSystemInfo)
{
case FileInfo file :
Console.WriteLine($"File name: {file.FullName}");
break;
case DirectoryInfo directory:
Console.WriteLine($"Directory name: {directory.FullName}");
break;
default:
Console.WriteLine("Not a valid file or directory name.");
break;
}
},
fileOrDirectoryOption);
await command.InvokeAsync(args);
Kód pro FileInfo
porovnávání vzorů a DirectoryInfo
shodný se nevyžaduje:
var fileOption = new Option<FileInfo>("--file");
var command = new RootCommand();
command.Add(fileOption);
command.SetHandler((file) =>
{
if (file is not null)
{
Console.WriteLine($"File name: {file?.FullName}");
}
else
{
Console.WriteLine("Not a valid file name.");
}
},
fileOption);
await command.InvokeAsync(args);
Další podporované typy
Mnoho typů, které mají konstruktor, který přebírá jeden řetězcový parametr lze tímto způsobem svázat. Například kód, který by fungoval s FileInfo
prací místo Uri toho.
var endpointOption = new Option<Uri>("--endpoint");
var command = new RootCommand();
command.Add(endpointOption);
command.SetHandler((uri) =>
{
Console.WriteLine($"URL: {uri?.ToString()}");
},
endpointOption);
await command.InvokeAsync(args);
Kromě typů systému souborů jsou Uri
podporovány následující typy:
bool
byte
DateTime
DateTimeOffset
decimal
double
float
Guid
int
long
sbyte
short
uint
ulong
ushort
Použití System.CommandLine objektů
SetHandler
Existuje přetížení, které vám dává přístup k objektuInvocationContext. Tento objekt se pak dá použít pro přístup k jiným System.CommandLine
objektům. Máte například přístup k následujícím objektům:
InvocationContext
Příklady najdete v tématu Nastavení ukončačních kódů a popisovačů ukončení.
CancellationToken
Informace o tom, jak používat CancellationToken, naleznete v tématu Jak zpracovat ukončení.
IConsole
IConsole usnadňuje testování i mnoho scénářů rozšiřitelnosti než použití System.Console
. Je k dispozici ve InvocationContext.Console vlastnosti.
ParseResult
Objekt ParseResult je k dispozici ve InvocationContext.ParseResult vlastnosti. Jedná se o jednoúčelovou strukturu, která představuje výsledky analýzy vstupu příkazového řádku. Můžete ho použít ke kontrole přítomnosti možností nebo argumentů na příkazovém řádku nebo k získání ParseResult.UnmatchedTokens vlastnosti. Tato vlastnost obsahuje seznam tokenů, které byly analyzovány , ale neodpovídají žádnému nakonfigurovanýmu příkazu, možnosti nebo argumentu.
Seznam chybějících tokenů je užitečný v příkazech, které se chovají jako obálky. Příkaz obálky vezme sadu tokenů a předá je jinému příkazu nebo aplikaci. Příkladem sudo
je příkaz v Linuxu. Vezme jméno uživatele, aby se zosobnění následované příkazem ke spuštění. Příklad:
sudo -u admin apt update
Tento příkazový řádek by spustil apt update
jako uživatel admin
.
Chcete-li implementovat příkaz obálky, jako je tento, nastavte vlastnost TreatUnmatchedTokensAsErrors příkazu na false
. ParseResult.UnmatchedTokens
Vlastnost pak bude obsahovat všechny argumenty, které explicitně nepatří do příkazu. V předchozím příkladu ParseResult.UnmatchedTokens
by obsahovaly apt
tokeny a update
tokeny. Obslužná rutina příkazu pak může předat UnmatchedTokens
volání do nového prostředí, například.
Vlastní ověřování a vazba
Pokud chcete zadat vlastní ověřovací kód, zavolejte AddValidator příkaz, možnost nebo argument, jak je znázorněno v následujícím příkladu:
var delayOption = new Option<int>("--delay");
delayOption.AddValidator(result =>
{
if (result.GetValueForOption(delayOption) < 1)
{
result.ErrorMessage = "Must be greater than 0";
}
});
Pokud chcete analyzovat i ověřit vstup, použijte ParseArgument<T> delegáta, jak je znázorněno v následujícím příkladu:
var delayOption = new Option<int>(
name: "--delay",
description: "An option whose argument is parsed as an int.",
isDefault: true,
parseArgument: result =>
{
if (!result.Tokens.Any())
{
return 42;
}
if (int.TryParse(result.Tokens.Single().Value, out var delay))
{
if (delay < 1)
{
result.ErrorMessage = "Must be greater than 0";
}
return delay;
}
else
{
result.ErrorMessage = "Not an int.";
return 0; // Ignored.
}
});
Předchozí kód se nastaví isDefault
tak true
, aby parseArgument
delegát byl volána, i když uživatel nezadal hodnotu pro tuto možnost.
Tady je několik příkladů toho, co můžete dělat s ParseArgument<T>
tím, co nemůžete dělat AddValidator
:
Analýza vlastních typů, například
Person
třídy v následujícím příkladu:public class Person { public string? FirstName { get; set; } public string? LastName { get; set; } }
var personOption = new Option<Person?>( name: "--person", description: "An option whose argument is parsed as a Person", parseArgument: result => { if (result.Tokens.Count != 2) { result.ErrorMessage = "--person requires two arguments"; return null; } return new Person { FirstName = result.Tokens.First().Value, LastName = result.Tokens.Last().Value }; }) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = true };
Analýza jiných typů vstupních řetězců (například parsování "1,2,3" do
int[]
).Dynamická arity. Máte například dva argumenty, které jsou definovány jako pole řetězců a musíte zpracovat posloupnost řetězců ve vstupu příkazového řádku. Metoda ArgumentResult.OnlyTake umožňuje dynamicky dělit vstupní řetězce mezi argumenty.