mirror of https://github.com/dotnet/tye.git
committed by
GitHub
4 changed files with 224 additions and 1 deletions
@ -0,0 +1,78 @@ |
|||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
// See the LICENSE file in the project root for more information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.CommandLine; |
||||
|
using System.CommandLine.Invocation; |
||||
|
using System.CommandLine.Parsing; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Microsoft.Tye |
||||
|
{ |
||||
|
public static partial class Program |
||||
|
{ |
||||
|
private const string DefaultOptionsEnvVarPrefix = "TYE_"; |
||||
|
private const string DefaultOptionsEnvVarPostfix = "_ARGS"; |
||||
|
|
||||
|
public static void DefaultOptionsMiddleware(InvocationContext context) |
||||
|
{ |
||||
|
// Check if current command is child of root command - default options for deeper nested commands is not supported at the moment
|
||||
|
if (context.ParseResult.CommandResult.Parent != context.ParseResult.RootCommandResult) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var commandName = context.ParseResult.CommandResult.Command.Name; |
||||
|
|
||||
|
// Get default options from environment variable for current command
|
||||
|
var rawDefaultOptions = Environment.GetEnvironmentVariable(DefaultOptionsEnvVarPrefix + commandName.ToUpper() + DefaultOptionsEnvVarPostfix); |
||||
|
if (string.IsNullOrWhiteSpace(rawDefaultOptions)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var originalParseResult = context.ParseResult; |
||||
|
// Get currently applied options
|
||||
|
var originalOptionResults = GetCommandOptions(originalParseResult.CommandResult); |
||||
|
// Recreate orignial input
|
||||
|
var originalTokens = StringifyTokens(originalParseResult.Tokens); |
||||
|
|
||||
|
// Exit early if --no-default option is applied
|
||||
|
if (originalOptionResults.Any(option => option.Option.Name == StandardOptions.NoDefaultOptions.Name)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Parse default options to validate them
|
||||
|
var defaultParseResult = context.Parser.Parse($"{commandName} {rawDefaultOptions}"); |
||||
|
// Get valid default options
|
||||
|
var defaultOptionResults = GetCommandOptions(defaultParseResult.CommandResult); |
||||
|
// Get only options that are not already applied
|
||||
|
var additionalTokens = GetAdditionalOptionsTokens(originalOptionResults, defaultOptionResults); |
||||
|
|
||||
|
// Set parse result as combination of original input plus default options
|
||||
|
context.ParseResult = context.Parser.Parse($"{originalTokens} {additionalTokens}"); |
||||
|
|
||||
|
static string StringifyTokens(IEnumerable<Token> tokens) |
||||
|
{ |
||||
|
return string.Join(" ", tokens.Select(t => t.Value)); |
||||
|
} |
||||
|
|
||||
|
static IEnumerable<OptionResult> GetCommandOptions(CommandResult commandResult) |
||||
|
{ |
||||
|
return commandResult.Children.OfType<OptionResult>(); |
||||
|
} |
||||
|
|
||||
|
// Filter only options which are not already applied in original command or which are implicit
|
||||
|
static string GetAdditionalOptionsTokens(IEnumerable<OptionResult> originalOptions, IEnumerable<OptionResult> defaultOptions) |
||||
|
{ |
||||
|
var additionalOptions = defaultOptions |
||||
|
.Where(@default => !originalOptions.Any(original => !original.IsImplicit && original.Option.Name == @default.Option.Name)) |
||||
|
.Select(additional => $"{additional.Token.Value} {StringifyTokens(additional.Tokens)}"); |
||||
|
return string.Join(" ", additionalOptions); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.CommandLine; |
||||
|
using System.CommandLine.Builder; |
||||
|
using System.CommandLine.Invocation; |
||||
|
using System.CommandLine.IO; |
||||
|
using System.CommandLine.Parsing; |
||||
|
using System.Linq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Microsoft.Tye.UnitTests |
||||
|
{ |
||||
|
public class DefaultOptionsMiddlewareTests |
||||
|
{ |
||||
|
private readonly TestConsole _console = new TestConsole(); |
||||
|
private readonly Parser _parser; |
||||
|
|
||||
|
public DefaultOptionsMiddlewareTests() |
||||
|
{ |
||||
|
var command = new Command("xxx") |
||||
|
{ |
||||
|
Handler = CommandHandler.Create((IConsole console, ParseResult result) => |
||||
|
{ |
||||
|
foreach (var option in result.CommandResult.Children.OfType<OptionResult>()) |
||||
|
{ |
||||
|
console.Out.Write(option.Token.Value); |
||||
|
var argument = option.Children.OfType<ArgumentResult>().FirstOrDefault(); |
||||
|
if (argument?.Tokens.Count > 0) |
||||
|
{ |
||||
|
console.Out.Write($":{argument.Tokens[0].Value}"); |
||||
|
} |
||||
|
console.Out.Write(" "); |
||||
|
} |
||||
|
}) |
||||
|
}; |
||||
|
|
||||
|
var subcommand = new Command("yyy"); |
||||
|
command.AddCommand(subcommand); |
||||
|
|
||||
|
var originalOption = new Option("--original"); |
||||
|
var defaultOption = new Option("--default"); |
||||
|
var implicitOption = new Option("--implicit") |
||||
|
{ |
||||
|
Argument = new Argument<bool>(() => false) |
||||
|
{ |
||||
|
Arity = ArgumentArity.ExactlyOne, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
var rootCommand = new RootCommand(); |
||||
|
rootCommand.AddGlobalOption(originalOption); |
||||
|
rootCommand.AddGlobalOption(defaultOption); |
||||
|
rootCommand.AddGlobalOption(implicitOption); |
||||
|
rootCommand.AddGlobalOption(StandardOptions.NoDefaultOptions); |
||||
|
rootCommand.AddCommand(command); |
||||
|
|
||||
|
_parser = new CommandLineBuilder(rootCommand) |
||||
|
.UseMiddleware(Program.DefaultOptionsMiddleware) |
||||
|
.Build(); |
||||
|
} |
||||
|
|
||||
|
private string[] OptionsFromConsole => _console.Out.ToString()?.Split(" ", StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_apply_default_option() |
||||
|
{ |
||||
|
Environment.SetEnvironmentVariable("TYE_XXX_ARGS", "--default", EnvironmentVariableTarget.Process); |
||||
|
|
||||
|
_parser.Invoke("xxx --original", _console); |
||||
|
|
||||
|
var appliedOptions = OptionsFromConsole; |
||||
|
Assert.Contains("--default", appliedOptions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_apply_default_option_if_command_is_root_command() |
||||
|
{ |
||||
|
Environment.SetEnvironmentVariable("TYE_XXX_ARGS", "--default", EnvironmentVariableTarget.Process); |
||||
|
|
||||
|
_parser.Invoke("--original", _console); |
||||
|
|
||||
|
var appliedOptions = OptionsFromConsole; |
||||
|
Assert.DoesNotContain("--default", appliedOptions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_apply_default_option_if_command_is_not_child_of_root_command() |
||||
|
{ |
||||
|
Environment.SetEnvironmentVariable("TYE_XXX_ARGS", "--default", EnvironmentVariableTarget.Process); |
||||
|
|
||||
|
_parser.Invoke("xxx yyy --original", _console); |
||||
|
|
||||
|
var appliedOptions = OptionsFromConsole; |
||||
|
Assert.DoesNotContain("--default", appliedOptions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_apply_default_option_if_env_var_is_empty() |
||||
|
{ |
||||
|
Environment.SetEnvironmentVariable("TYE_XXX_ARGS", "", EnvironmentVariableTarget.Process); |
||||
|
|
||||
|
_parser.Invoke("xxx --original", _console); |
||||
|
|
||||
|
var appliedOptions = OptionsFromConsole; |
||||
|
Assert.DoesNotContain("--default", appliedOptions); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_apply_default_option_if_it_is_already_applied() |
||||
|
{ |
||||
|
Environment.SetEnvironmentVariable("TYE_XXX_ARGS", "--default", EnvironmentVariableTarget.Process); |
||||
|
|
||||
|
_parser.Invoke("xxx --original --default", _console); |
||||
|
|
||||
|
var appliedOptions = OptionsFromConsole; |
||||
|
Assert.Equal(1, appliedOptions.Count(o => o == "--default")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_apply_default_option_if_it_is_already_implicitly_applied() |
||||
|
{ |
||||
|
Environment.SetEnvironmentVariable("TYE_XXX_ARGS", "--default --implicit true", EnvironmentVariableTarget.Process); |
||||
|
|
||||
|
_parser.Invoke("xxx --original", _console); |
||||
|
|
||||
|
var appliedOptions = OptionsFromConsole; |
||||
|
Assert.Contains("--implicit:true", appliedOptions); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue