Sdílet prostřednictvím


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 kde string 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žnostx, string kde string by měla být možnost y v seznamu zdrojů. V takovém případě získá proměnná pro hodnotu možnosti y hodnotu možnosti x .

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 Uripodporová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.

Viz také

System.CommandLine Přehled