Browse Source

Added functionality to specify default options for commands (#736)

pull/804/head
qpooqp 6 years ago
committed by GitHub
parent
commit
474dae7f49
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      src/tye/Program.DefaultOptionsMiddleware.cs
  2. 3
      src/tye/Program.cs
  3. 14
      src/tye/StandardOptions.cs
  4. 130
      test/UnitTests/DefaultOptionsMiddlewareTests.cs

78
src/tye/Program.DefaultOptionsMiddleware.cs

@ -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);
}
}
}
}

3
src/tye/Program.cs

@ -23,6 +23,7 @@ namespace Microsoft.Tye
{
Description = "Developer tools and publishing for microservices.",
};
command.AddGlobalOption(StandardOptions.NoDefaultOptions);
command.AddCommand(CreateInitCommand());
command.AddCommand(CreateGenerateCommand());
@ -49,6 +50,8 @@ namespace Microsoft.Tye
builder.CancelOnProcessTermination();
builder.UseExceptionHandler(HandleException);
builder.UseMiddleware(DefaultOptionsMiddleware);
var parser = builder.Build();
return parser.InvokeAsync(args);
}

14
src/Microsoft.Tye.Core/StandardOptions.cs → src/tye/StandardOptions.cs

@ -111,7 +111,6 @@ namespace Microsoft.Tye
{
get
{
var argument = new Argument<FileInfo>(TryParse, isDefault: true)
{
Arity = ArgumentArity.ZeroOrOne,
@ -227,6 +226,19 @@ namespace Microsoft.Tye
}
}
public static Option NoDefaultOptions
{
get
{
return new Option(new[] { "--no-default" })
{
Description = "Disable default options from environment variables",
Required = false,
Argument = new Argument<bool>(),
};
}
}
public static Option CreateForce(string descriptions) =>
new Option(new[] { "--force" })
{

130
test/UnitTests/DefaultOptionsMiddlewareTests.cs

@ -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…
Cancel
Save