diff --git a/NuGet.config b/NuGet.config index 0fd623ffdd..4017b7ad58 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,7 +1,7 @@  - + diff --git a/src/Microsoft.DotNet.Watcher.Tools/Microsoft.DotNet.Watcher.Tools.nuspec b/src/Microsoft.DotNet.Watcher.Tools/Microsoft.DotNet.Watcher.Tools.nuspec index 74300b89ea..0be0f55cbb 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/Microsoft.DotNet.Watcher.Tools.nuspec +++ b/src/Microsoft.DotNet.Watcher.Tools/Microsoft.DotNet.Watcher.Tools.nuspec @@ -15,10 +15,9 @@ - - - - + + + diff --git a/src/Microsoft.DotNet.Watcher.Tools/project.json b/src/Microsoft.DotNet.Watcher.Tools/project.json index 502c402f2e..742e7e484c 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/project.json +++ b/src/Microsoft.DotNet.Watcher.Tools/project.json @@ -21,6 +21,9 @@ "mappings": { "dotnetwatch.targets": "dotnetwatch.targets" } + }, + "compile": { + "include": "../Shared/**/*.cs" } }, "publishOptions": { @@ -29,13 +32,12 @@ ] }, "dependencies": { - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", - "Microsoft.Extensions.Logging": "1.1.0-*", - "Microsoft.Extensions.Logging.Console": "1.1.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-preview2-003121", + "Microsoft.Extensions.Logging": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Process.Sources": { "type": "build", - "version": "1.1.0-*" + "version": "1.0.0" }, "Microsoft.NETCore.App": { "type": "platform", diff --git a/src/Microsoft.Extensions.Caching.SqlConfig.Tools/project.json b/src/Microsoft.Extensions.Caching.SqlConfig.Tools/project.json index 5b16064ac7..3ff33ed3bd 100644 --- a/src/Microsoft.Extensions.Caching.SqlConfig.Tools/project.json +++ b/src/Microsoft.Extensions.Caching.SqlConfig.Tools/project.json @@ -19,9 +19,9 @@ ] }, "dependencies": { - "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", - "Microsoft.Extensions.Logging": "1.1.0-*", - "Microsoft.Extensions.Logging.Console": "1.1.0-*", + "Microsoft.Extensions.CommandLineUtils": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.NETCore.App": { "version": "1.0.1", "type": "platform" diff --git a/src/Microsoft.Extensions.SecretManager.Tools/Microsoft.Extensions.SecretManager.Tools.nuspec b/src/Microsoft.Extensions.SecretManager.Tools/Microsoft.Extensions.SecretManager.Tools.nuspec index ac4bfbfe7b..f8c596e5e9 100644 --- a/src/Microsoft.Extensions.SecretManager.Tools/Microsoft.Extensions.SecretManager.Tools.nuspec +++ b/src/Microsoft.Extensions.SecretManager.Tools/Microsoft.Extensions.SecretManager.Tools.nuspec @@ -17,10 +17,9 @@ - - - - + + + @@ -28,6 +27,6 @@ - + \ No newline at end of file diff --git a/src/Microsoft.Extensions.SecretManager.Tools/project.json b/src/Microsoft.Extensions.SecretManager.Tools/project.json index 79b230a15f..d7a2e799bf 100644 --- a/src/Microsoft.Extensions.SecretManager.Tools/project.json +++ b/src/Microsoft.Extensions.SecretManager.Tools/project.json @@ -10,6 +10,9 @@ "mappings": { "FindUserSecretsProperty.targets": "./FindUserSecretsProperty.targets" } + }, + "compile": { + "include": "../Shared/**/*.cs" } }, "description": "Command line tool to manage user secrets for Microsoft.Extensions.Configuration.", @@ -35,10 +38,9 @@ ] }, "dependencies": { - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.Extensions.CommandLineUtils": "1.1.0-*", - "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0-*", - "Microsoft.Extensions.Logging": "1.1.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-preview2-003121", + "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.NETCore.App": { "version": "1.0.1", "type": "platform" diff --git a/src/Shared/CommandLine/AnsiConsole.cs b/src/Shared/CommandLine/AnsiConsole.cs new file mode 100644 index 0000000000..79d705f25e --- /dev/null +++ b/src/Shared/CommandLine/AnsiConsole.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class AnsiConsole + { + private AnsiConsole(TextWriter writer, bool useConsoleColor) + { + Writer = writer; + + _useConsoleColor = useConsoleColor; + if (_useConsoleColor) + { + OriginalForegroundColor = Console.ForegroundColor; + } + } + + private int _boldRecursion; + private bool _useConsoleColor; + + public static AnsiConsole GetOutput(bool useConsoleColor) + { + return new AnsiConsole(Console.Out, useConsoleColor); + } + + public static AnsiConsole GetError(bool useConsoleColor) + { + return new AnsiConsole(Console.Error, useConsoleColor); + } + + public TextWriter Writer { get; } + + public ConsoleColor OriginalForegroundColor { get; } + + private void SetColor(ConsoleColor color) + { + Console.ForegroundColor = (ConsoleColor)(((int)Console.ForegroundColor & 0x08) | ((int)color & 0x07)); + } + + private void SetBold(bool bold) + { + _boldRecursion += bold ? 1 : -1; + if (_boldRecursion > 1 || (_boldRecursion == 1 && !bold)) + { + return; + } + + Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor ^ 0x08); + } + + public void WriteLine(string message) + { + if (!_useConsoleColor) + { + Writer.WriteLine(message); + return; + } + + var escapeScan = 0; + for (; ;) + { + var escapeIndex = message.IndexOf("\x1b[", escapeScan); + if (escapeIndex == -1) + { + var text = message.Substring(escapeScan); + Writer.Write(text); + break; + } + else + { + var startIndex = escapeIndex + 2; + var endIndex = startIndex; + while (endIndex != message.Length && + message[endIndex] >= 0x20 && + message[endIndex] <= 0x3f) + { + endIndex += 1; + } + + var text = message.Substring(escapeScan, escapeIndex - escapeScan); + Writer.Write(text); + if (endIndex == message.Length) + { + break; + } + + switch (message[endIndex]) + { + case 'm': + int value; + if (int.TryParse(message.Substring(startIndex, endIndex - startIndex), out value)) + { + switch (value) + { + case 1: + SetBold(true); + break; + case 22: + SetBold(false); + break; + case 30: + SetColor(ConsoleColor.Black); + break; + case 31: + SetColor(ConsoleColor.Red); + break; + case 32: + SetColor(ConsoleColor.Green); + break; + case 33: + SetColor(ConsoleColor.Yellow); + break; + case 34: + SetColor(ConsoleColor.Blue); + break; + case 35: + SetColor(ConsoleColor.Magenta); + break; + case 36: + SetColor(ConsoleColor.Cyan); + break; + case 37: + SetColor(ConsoleColor.Gray); + break; + case 39: + SetColor(OriginalForegroundColor); + break; + } + } + break; + } + + escapeScan = endIndex + 1; + } + } + Writer.WriteLine(); + } + } +} diff --git a/src/Shared/CommandLine/CommandArgument.cs b/src/Shared/CommandLine/CommandArgument.cs new file mode 100644 index 0000000000..c687485e33 --- /dev/null +++ b/src/Shared/CommandLine/CommandArgument.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class CommandArgument + { + public CommandArgument() + { + Values = new List(); + } + + public string Name { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string Description { get; set; } + public List Values { get; private set; } + public bool MultipleValues { get; set; } + public string Value + { + get + { + return Values.FirstOrDefault(); + } + } + } +} diff --git a/src/Shared/CommandLine/CommandLineApplication.cs b/src/Shared/CommandLine/CommandLineApplication.cs new file mode 100644 index 0000000000..0623e5104e --- /dev/null +++ b/src/Shared/CommandLine/CommandLineApplication.cs @@ -0,0 +1,555 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class CommandLineApplication + { + // Indicates whether the parser should throw an exception when it runs into an unexpected argument. + // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all + // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property. + private readonly bool _throwOnUnexpectedArg; + + public CommandLineApplication(bool throwOnUnexpectedArg = true) + { + _throwOnUnexpectedArg = throwOnUnexpectedArg; + Options = new List(); + Arguments = new List(); + Commands = new List(); + RemainingArguments = new List(); + Invoke = () => 0; + } + + public CommandLineApplication Parent { get; set; } + public string Name { get; set; } + public string FullName { get; set; } + public string Syntax { get; set; } + public string Description { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string ExtendedHelpText { get; set; } + public readonly List Options; + public CommandOption OptionHelp { get; private set; } + public CommandOption OptionVersion { get; private set; } + public readonly List Arguments; + public readonly List RemainingArguments; + public bool IsShowingInformation { get; protected set; } // Is showing help or version? + public Func Invoke { get; set; } + public Func LongVersionGetter { get; set; } + public Func ShortVersionGetter { get; set; } + public readonly List Commands; + public bool AllowArgumentSeparator { get; set; } + public TextWriter Out { get; set; } = Console.Out; + public TextWriter Error { get; set; } = Console.Error; + + public IEnumerable GetOptions() + { + var expr = Options.AsEnumerable(); + var rootNode = this; + while (rootNode.Parent != null) + { + rootNode = rootNode.Parent; + expr = expr.Concat(rootNode.Options.Where(o => o.Inherited)); + } + + return expr; + } + + public CommandLineApplication Command(string name, Action configuration, + bool throwOnUnexpectedArg = true) + { + var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this }; + Commands.Add(command); + configuration(command); + return command; + } + + public CommandOption Option(string template, string description, CommandOptionType optionType) + => Option(template, description, optionType, _ => { }, inherited: false); + + public CommandOption Option(string template, string description, CommandOptionType optionType, bool inherited) + => Option(template, description, optionType, _ => { }, inherited); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration) + => Option(template, description, optionType, configuration, inherited: false); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration, bool inherited) + { + var option = new CommandOption(template, optionType) + { + Description = description, + Inherited = inherited + }; + Options.Add(option); + configuration(option); + return option; + } + + public CommandArgument Argument(string name, string description, bool multipleValues = false) + { + return Argument(name, description, _ => { }, multipleValues); + } + + public CommandArgument Argument(string name, string description, Action configuration, bool multipleValues = false) + { + var lastArg = Arguments.LastOrDefault(); + if (lastArg != null && lastArg.MultipleValues) + { + var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.", + lastArg.Name); + throw new InvalidOperationException(message); + } + + var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues }; + Arguments.Add(argument); + configuration(argument); + return argument; + } + + public void OnExecute(Func invoke) + { + Invoke = invoke; + } + + public void OnExecute(Func> invoke) + { + Invoke = () => invoke().Result; + } + public int Execute(params string[] args) + { + CommandLineApplication command = this; + CommandOption option = null; + IEnumerator arguments = null; + + for (var index = 0; index < args.Length; index++) + { + var arg = args[index]; + var processed = false; + if (!processed && option == null) + { + string[] longOption = null; + string[] shortOption = null; + + if (arg.StartsWith("--")) + { + longOption = arg.Substring(2).Split(new[] { ':', '=' }, 2); + } + else if (arg.StartsWith("-")) + { + shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2); + } + if (longOption != null) + { + processed = true; + var longOptionName = longOption[0]; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.LongName, longOptionName, StringComparison.Ordinal)); + + if (option == null) + { + if (string.IsNullOrEmpty(longOptionName) && !command._throwOnUnexpectedArg && AllowArgumentSeparator) + { + // skip over the '--' argument separator + index++; + } + + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (longOption.Length == 2) + { + if (!option.TryParse(longOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{longOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + if (shortOption != null) + { + processed = true; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.ShortName, shortOption[0], StringComparison.Ordinal)); + + // If not a short option, try symbol option + if (option == null) + { + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, shortOption[0], StringComparison.Ordinal)); + } + + if (option == null) + { + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (shortOption.Length == 2) + { + if (!option.TryParse(shortOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{shortOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + } + + if (!processed && option != null) + { + processed = true; + if (!option.TryParse(arg)) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{option.LongName}'"); + } + option = null; + } + + if (!processed && arguments == null) + { + var currentCommand = command; + foreach (var subcommand in command.Commands) + { + if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase)) + { + processed = true; + command = subcommand; + break; + } + } + + // If we detect a subcommand + if (command != currentCommand) + { + processed = true; + } + } + if (!processed) + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + if (arguments.MoveNext()) + { + processed = true; + arguments.Current.Values.Add(arg); + } + } + if (!processed) + { + HandleUnexpectedArg(command, args, index, argTypeName: "command or argument"); + break; + } + } + + if (option != null) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Missing value for option '{option.LongName}'"); + } + + return command.Invoke(); + } + + // Helper method that adds a help option + public CommandOption HelpOption(string template) + { + // Help option is special because we stop parsing once we see it + // So we store it separately for further use + OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue); + + return OptionHelp; + } + + public CommandOption VersionOption(string template, + string shortFormVersion, + string longFormVersion = null) + { + if (longFormVersion == null) + { + return VersionOption(template, () => shortFormVersion); + } + else + { + return VersionOption(template, () => shortFormVersion, () => longFormVersion); + } + } + + // Helper method that adds a version option + public CommandOption VersionOption(string template, + Func shortFormVersionGetter, + Func longFormVersionGetter = null) + { + // Version option is special because we stop parsing once we see it + // So we store it separately for further use + OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue); + ShortVersionGetter = shortFormVersionGetter; + LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter; + + return OptionVersion; + } + + // Show short hint that reminds users to use help option + public void ShowHint() + { + if (OptionHelp != null) + { + Out.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName)); + } + } + + // Show full help + public void ShowHelp(string commandName = null) + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(GetHelpText(commandName)); + } + + public virtual string GetHelpText(string commandName = null) + { + var headerBuilder = new StringBuilder("Usage:"); + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + headerBuilder.Insert(6, string.Format(" {0}", cmd.Name)); + } + + CommandLineApplication target; + + if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase)) + { + target = this; + } + else + { + target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase)); + + if (target != null) + { + headerBuilder.AppendFormat(" {0}", commandName); + } + else + { + // The command name is invalid so don't try to show help for something that doesn't exist + target = this; + } + + } + + var optionsBuilder = new StringBuilder(); + var commandsBuilder = new StringBuilder(); + var argumentsBuilder = new StringBuilder(); + + var arguments = target.Arguments.Where(a => a.ShowInHelpText).ToList(); + if (arguments.Any()) + { + headerBuilder.Append(" [arguments]"); + + argumentsBuilder.AppendLine(); + argumentsBuilder.AppendLine("Arguments:"); + var maxArgLen = arguments.Max(a => a.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxArgLen + 2); + foreach (var arg in arguments) + { + argumentsBuilder.AppendFormat(outputFormat, arg.Name, arg.Description); + argumentsBuilder.AppendLine(); + } + } + + var options = target.GetOptions().Where(o => o.ShowInHelpText).ToList(); + if (options.Any()) + { + headerBuilder.Append(" [options]"); + + optionsBuilder.AppendLine(); + optionsBuilder.AppendLine("Options:"); + var maxOptLen = options.Max(o => o.Template.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2); + foreach (var opt in options) + { + optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description); + optionsBuilder.AppendLine(); + } + } + + var commands = target.Commands.Where(c => c.ShowInHelpText).ToList(); + if (commands.Any()) + { + headerBuilder.Append(" [command]"); + + commandsBuilder.AppendLine(); + commandsBuilder.AppendLine("Commands:"); + var maxCmdLen = commands.Max(c => c.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2); + foreach (var cmd in commands.OrderBy(c => c.Name)) + { + commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description); + commandsBuilder.AppendLine(); + } + + if (OptionHelp != null) + { + commandsBuilder.AppendLine(); + commandsBuilder.AppendFormat($"Use \"{target.Name} [command] --{OptionHelp.LongName}\" for more information about a command."); + commandsBuilder.AppendLine(); + } + } + + if (target.AllowArgumentSeparator) + { + headerBuilder.Append(" [[--] ...]"); + } + + headerBuilder.AppendLine(); + + var nameAndVersion = new StringBuilder(); + nameAndVersion.AppendLine(GetFullNameAndVersion()); + nameAndVersion.AppendLine(); + + return nameAndVersion.ToString() + + headerBuilder.ToString() + + argumentsBuilder.ToString() + + optionsBuilder.ToString() + + commandsBuilder.ToString() + + target.ExtendedHelpText; + } + + public void ShowVersion() + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(FullName); + Out.WriteLine(LongVersionGetter()); + } + + public string GetFullNameAndVersion() + { + return ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter()); + } + + public void ShowRootCommandFullNameAndVersion() + { + var rootCmd = this; + while (rootCmd.Parent != null) + { + rootCmd = rootCmd.Parent; + } + + Out.WriteLine(rootCmd.GetFullNameAndVersion()); + Out.WriteLine(); + } + + private void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName) + { + if (command._throwOnUnexpectedArg) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'"); + } + else + { + // All remaining arguments are stored for further use + command.RemainingArguments.AddRange(new ArraySegment(args, index, args.Length - index)); + } + } + + private class CommandArgumentEnumerator : IEnumerator + { + private readonly IEnumerator _enumerator; + + public CommandArgumentEnumerator(IEnumerator enumerator) + { + _enumerator = enumerator; + } + + public CommandArgument Current + { + get + { + return _enumerator.Current; + } + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public void Dispose() + { + _enumerator.Dispose(); + } + + public bool MoveNext() + { + if (Current == null || !Current.MultipleValues) + { + return _enumerator.MoveNext(); + } + + // If current argument allows multiple values, we don't move forward and + // all later values will be added to current CommandArgument.Values + return true; + } + + public void Reset() + { + _enumerator.Reset(); + } + } + } +} diff --git a/src/Shared/CommandLine/CommandOption.cs b/src/Shared/CommandLine/CommandOption.cs new file mode 100644 index 0000000000..8f6d6af4ad --- /dev/null +++ b/src/Shared/CommandLine/CommandOption.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class CommandOption + { + public CommandOption(string template, CommandOptionType optionType) + { + Template = template; + OptionType = optionType; + Values = new List(); + + foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (part.StartsWith("--")) + { + LongName = part.Substring(2); + } + else if (part.StartsWith("-")) + { + var optName = part.Substring(1); + + // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?") + if (optName.Length == 1 && !IsEnglishLetter(optName[0])) + { + SymbolName = optName; + } + else + { + ShortName = optName; + } + } + else if (part.StartsWith("<") && part.EndsWith(">")) + { + ValueName = part.Substring(1, part.Length - 2); + } + else + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName)) + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + public string Template { get; set; } + public string ShortName { get; set; } + public string LongName { get; set; } + public string SymbolName { get; set; } + public string ValueName { get; set; } + public string Description { get; set; } + public List Values { get; private set; } + public CommandOptionType OptionType { get; private set; } + public bool ShowInHelpText { get; set; } = true; + public bool Inherited { get; set; } + + public bool TryParse(string value) + { + switch (OptionType) + { + case CommandOptionType.MultipleValue: + Values.Add(value); + break; + case CommandOptionType.SingleValue: + if (Values.Any()) + { + return false; + } + Values.Add(value); + break; + case CommandOptionType.NoValue: + if (value != null) + { + return false; + } + // Add a value to indicate that this option was specified + Values.Add("on"); + break; + default: + break; + } + return true; + } + + public bool HasValue() + { + return Values.Any(); + } + + public string Value() + { + return HasValue() ? Values[0] : null; + } + + private bool IsEnglishLetter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + } +} \ No newline at end of file diff --git a/src/Shared/CommandLine/CommandOptionType.cs b/src/Shared/CommandLine/CommandOptionType.cs new file mode 100644 index 0000000000..759182ca91 --- /dev/null +++ b/src/Shared/CommandLine/CommandOptionType.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +namespace Microsoft.Extensions.CommandLineUtils +{ + public enum CommandOptionType + { + MultipleValue, + SingleValue, + NoValue + } +} diff --git a/src/Shared/CommandLine/CommandParsingException.cs b/src/Shared/CommandLine/CommandParsingException.cs new file mode 100644 index 0000000000..b5151c2301 --- /dev/null +++ b/src/Shared/CommandLine/CommandParsingException.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class CommandParsingException : Exception + { + public CommandParsingException(CommandLineApplication command, string message) + : base(message) + { + Command = command; + } + + public CommandLineApplication Command { get; } + } +} diff --git a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json index a1a2015d4a..39e6e6ce55 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json +++ b/test/Microsoft.DotNet.Watcher.Tools.FunctionalTests/project.json @@ -11,13 +11,13 @@ }, "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Microsoft.AspNetCore.Testing": "1.1.0-*", + "Microsoft.AspNetCore.Testing": "1.0.0", "Microsoft.DotNet.Watcher.Tools": "1.0.0-*", "Microsoft.Extensions.Process.Sources": { "type": "build", - "version": "1.1.0-*" + "version": "1.0.0" }, - "xunit": "2.2.0-*" + "xunit": "2.2.0-beta3-build3402" }, "frameworks": { "netcoreapp1.0": { diff --git a/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json b/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json index 49c0a70124..fd2d9874aa 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json +++ b/test/Microsoft.DotNet.Watcher.Tools.Tests/project.json @@ -7,7 +7,7 @@ "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.DotNet.Watcher.Tools": "1.0.0-*", - "xunit": "2.2.0-*" + "xunit": "2.2.0-beta3-build3402" }, "frameworks": { "netcoreapp1.0": { diff --git a/test/Microsoft.Extensions.SecretManager.Tools.Tests/SetCommandTest.cs b/test/Microsoft.Extensions.SecretManager.Tools.Tests/SetCommandTest.cs index b704a87c36..5420aa0ff4 100644 --- a/test/Microsoft.Extensions.SecretManager.Tools.Tests/SetCommandTest.cs +++ b/test/Microsoft.Extensions.SecretManager.Tools.Tests/SetCommandTest.cs @@ -3,9 +3,10 @@ using System.IO; using System.Collections.Generic; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.SecretManager.Tools.Internal; +using Microsoft.Extensions.Logging; using Xunit; +using System; namespace Microsoft.Extensions.SecretManager.Tools.Tests { @@ -75,7 +76,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests var secretStore = new TestSecretsStore(); var command = new SetCommand("key", null); - var ex = Assert.Throws< Microsoft.DotNet.Cli.Utils.GracefulException>( + var ex = Assert.Throws( () => command.Execute(new CommandContext(secretStore, NullLogger.Instance, testConsole))); Assert.Equal(Resources.FormatError_MissingArgument("value"), ex.Message); } @@ -98,4 +99,25 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests } } } + + public class NullLogger : ILogger + { + public static NullLogger Instance = new NullLogger(); + + private class NullScope : IDisposable + { + public void Dispose() + { + } + } + public IDisposable BeginScope(TState state) + => new NullScope(); + + public bool IsEnabled(LogLevel logLevel) + => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } } diff --git a/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json b/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json index 3512358403..515a64c422 100644 --- a/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json +++ b/test/Microsoft.Extensions.SecretManager.Tools.Tests/project.json @@ -6,7 +6,7 @@ "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.Extensions.SecretManager.Tools": "1.0.0-*", - "xunit": "2.2.0-*" + "xunit": "2.2.0-beta3-build3402" }, "testRunner": "xunit", "frameworks": { diff --git a/tools/NuGetPackager/project.json b/tools/NuGetPackager/project.json index f42b3d92d0..23a32ed3f8 100644 --- a/tools/NuGetPackager/project.json +++ b/tools/NuGetPackager/project.json @@ -6,14 +6,14 @@ "dependencies": { "Microsoft.NETCore.App": { "type": "platform", - "version": "1.1.0-*" + "version": "1.0.1" }, - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.Extensions.CommandLineUtils": "1.1.0-*" + "Microsoft.DotNet.ProjectModel": "1.0.0-rc3-003121", + "Microsoft.DotNet.Cli.Utils": "1.0.0-preview2-003121", + "Microsoft.Extensions.CommandLineUtils": "1.0.0" }, "frameworks": { - "netcoreapp1.1": { } + "netcoreapp1.0": { } } } \ No newline at end of file