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