diff --git a/src/dotnet/BuiltInCommandMetadata.cs b/src/dotnet/BuiltInCommandMetadata.cs new file mode 100644 index 000000000..7f7bf009b --- /dev/null +++ b/src/dotnet/BuiltInCommandMetadata.cs @@ -0,0 +1,10 @@ +using System; + +namespace Microsoft.DotNet.Cli +{ + public class BuiltInCommandMetadata + { + public Func Command { get; set; } + public Uri DocLink { get; set; } + } +} \ No newline at end of file diff --git a/src/dotnet/BuiltInCommandsCatalog.cs b/src/dotnet/BuiltInCommandsCatalog.cs new file mode 100644 index 000000000..05bea1cef --- /dev/null +++ b/src/dotnet/BuiltInCommandsCatalog.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using Microsoft.DotNet.Tools.Add; +using Microsoft.DotNet.Tools.Build; +using Microsoft.DotNet.Tools.Clean; +using Microsoft.DotNet.Tools.Help; +using Microsoft.DotNet.Tools.List; +using Microsoft.DotNet.Tools.Migrate; +using Microsoft.DotNet.Tools.MSBuild; +using Microsoft.DotNet.Tools.New; +using Microsoft.DotNet.Tools.NuGet; +using Microsoft.DotNet.Tools.Pack; +using Microsoft.DotNet.Tools.Publish; +using Microsoft.DotNet.Tools.Remove; +using Microsoft.DotNet.Tools.Restore; +using Microsoft.DotNet.Tools.Run; +using Microsoft.DotNet.Tools.Sln; +using Microsoft.DotNet.Tools.Test; +using Microsoft.DotNet.Tools.VSTest; +using Microsoft.DotNet.Tools.Cache; + +namespace Microsoft.DotNet.Cli +{ + public static class BuiltInCommandsCatalog + { + public static Dictionary Commands = new Dictionary + { + ["add"] = new BuiltInCommandMetadata + { + Command = AddCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-add") + }, + ["build"] = new BuiltInCommandMetadata + { + Command = BuildCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-build") + }, + ["cache"] = new BuiltInCommandMetadata + { + Command = CacheCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-cache") + }, + ["clean"] = new BuiltInCommandMetadata + { + Command = CleanCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-clean") + }, + ["help"] = new BuiltInCommandMetadata + { + Command = HelpCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-help") + }, + ["list"] = new BuiltInCommandMetadata + { + Command = ListCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-list") + }, + ["migrate"] = new BuiltInCommandMetadata + { + Command = MigrateCommand.Run, + DocLink = new Uri("http://aka.ms/dotnet-migrate") + + }, + ["msbuild"] = new BuiltInCommandMetadata + { + Command = MSBuildCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-msbuild") + }, + ["new"] = new BuiltInCommandMetadata + { + Command = NewCommandShim.Run, + DocLink = new Uri("https://aka.ms/dotnet-new") + }, + ["nuget"] = new BuiltInCommandMetadata + { + Command = NuGetCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-nuget") + }, + ["pack"] = new BuiltInCommandMetadata + { + Command = PackCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-pack") + }, + ["publish"] = new BuiltInCommandMetadata + { + Command = PublishCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-publish") + }, + ["remove"] = new BuiltInCommandMetadata + { + Command = RemoveCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-remove") + }, + ["restore"] = new BuiltInCommandMetadata + { + Command = RestoreCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-restore") + }, + ["run"] = new BuiltInCommandMetadata + { + Command = RunCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-run") + }, + ["sln"] = new BuiltInCommandMetadata + { + Command = SlnCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-sln") + }, + ["test"] = new BuiltInCommandMetadata + { + Command = TestCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-test") + }, + ["vstest"] = new BuiltInCommandMetadata + { + Command = VSTestCommand.Run, + DocLink = new Uri("https://aka.ms/dotnet-vstest") + } + }; + + } +} \ No newline at end of file diff --git a/src/dotnet/DotNetCommandFactory.cs b/src/dotnet/DotNetCommandFactory.cs index 8ead957d6..d9c17b639 100644 --- a/src/dotnet/DotNetCommandFactory.cs +++ b/src/dotnet/DotNetCommandFactory.cs @@ -24,13 +24,13 @@ namespace Microsoft.DotNet.Cli NuGetFramework framework = null, string configuration = Constants.DefaultConfiguration) { - Func builtInCommand; + BuiltInCommandMetadata builtInCommand; if (!_alwaysRunOutOfProc && Program.TryGetBuiltInCommand(commandName, out builtInCommand)) { Debug.Assert(framework == null, "BuiltInCommand doesn't support the 'framework' argument."); Debug.Assert(configuration == Constants.DefaultConfiguration, "BuiltInCommand doesn't support the 'configuration' argument."); - return new BuiltInCommand(commandName, args, builtInCommand); + return new BuiltInCommand(commandName, args, builtInCommand.Command); } return Command.CreateDotNet(commandName, args, framework, configuration); diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 12aaf631f..17ebe91ae 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -2,60 +2,18 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Configurer; using Microsoft.DotNet.PlatformAbstractions; -using Microsoft.DotNet.Tools.Add; -using Microsoft.DotNet.Tools.Build; -using Microsoft.DotNet.Tools.Clean; using Microsoft.DotNet.Tools.Help; -using Microsoft.DotNet.Tools.List; -using Microsoft.DotNet.Tools.Migrate; -using Microsoft.DotNet.Tools.MSBuild; -using Microsoft.DotNet.Tools.New; -using Microsoft.DotNet.Tools.NuGet; -using Microsoft.DotNet.Tools.Pack; -using Microsoft.DotNet.Tools.Publish; -using Microsoft.DotNet.Tools.Remove; -using Microsoft.DotNet.Tools.Restore; -using Microsoft.DotNet.Tools.RestoreProjectJson; -using Microsoft.DotNet.Tools.Run; -using Microsoft.DotNet.Tools.Sln; -using Microsoft.DotNet.Tools.Test; -using Microsoft.DotNet.Tools.VSTest; -using Microsoft.DotNet.Tools.Cache; using NuGet.Frameworks; namespace Microsoft.DotNet.Cli { public class Program { - private static Dictionary> s_builtIns = new Dictionary> - { - ["add"] = AddCommand.Run, - ["build"] = BuildCommand.Run, - ["cache"] = CacheCommand.Run, - ["clean"] = CleanCommand.Run, - ["help"] = HelpCommand.Run, - ["list"] = ListCommand.Run, - ["migrate"] = MigrateCommand.Run, - ["msbuild"] = MSBuildCommand.Run, - ["new"] = NewCommandShim.Run, - ["nuget"] = NuGetCommand.Run, - ["pack"] = PackCommand.Run, - ["publish"] = PublishCommand.Run, - ["remove"] = RemoveCommand.Run, - ["restore"] = RestoreCommand.Run, - ["run"] = RunCommand.Run, - ["sln"] = SlnCommand.Run, - ["test"] = TestCommand.Run, - ["vstest"] = VSTestCommand.Run, - }; - public static int Main(string[] args) { DebugHelper.HandleDebugSwitch(ref args); @@ -169,10 +127,12 @@ namespace Microsoft.DotNet.Cli telemetryClient.TrackEvent(command, null, null); int exitCode; - Func builtIn; - if (s_builtIns.TryGetValue(command, out builtIn)) + // Func builtIn; + // if (s_builtIns.TryGetValue(command, out builtIn)) + BuiltInCommandMetadata builtIn; + if (BuiltInCommandsCatalog.Commands.TryGetValue(command, out builtIn)) { - exitCode = builtIn(appArgs.ToArray()); + exitCode = builtIn.Command(appArgs.ToArray()); } else { @@ -215,9 +175,9 @@ namespace Microsoft.DotNet.Cli Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); } - internal static bool TryGetBuiltInCommand(string commandName, out Func builtInCommand) + internal static bool TryGetBuiltInCommand(string commandName, out BuiltInCommandMetadata builtInCommand) { - return s_builtIns.TryGetValue(commandName, out builtInCommand); + return BuiltInCommandsCatalog.Commands.TryGetValue(commandName, out builtInCommand); } private static void PrintVersion() diff --git a/src/dotnet/commands/dotnet-help/HelpCommand.cs b/src/dotnet/commands/dotnet-help/HelpCommand.cs index 1a14efe6b..cc0afe797 100644 --- a/src/dotnet/commands/dotnet-help/HelpCommand.cs +++ b/src/dotnet/commands/dotnet-help/HelpCommand.cs @@ -1,7 +1,11 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; +using System.Diagnostics; using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Tools.Help @@ -49,6 +53,32 @@ Project modification commands: public static int Run(string[] args) { + + CommandLineApplication app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Name = "dotnet help"; + app.FullName = LocalizableStrings.AppFullName; + app.Description = LocalizableStrings.AppDescription; + + CommandArgument commandNameArgument = app.Argument($"<{LocalizableStrings.CommandArgumentName}>", LocalizableStrings.CommandArgumentDescription); + + app.OnExecute(() => + { + Cli.BuiltInCommandMetadata builtIn; + if (Cli.BuiltInCommandsCatalog.Commands.TryGetValue(commandNameArgument.Value, out builtIn)) + { + // var p = Process.Start(GetProcessStartInfo(builtIn)); + var process = ConfigureProcess(builtIn.DocLink.ToString()); + process.Start(); + process.WaitForExit(); + } + else + { + Reporter.Error.WriteLine(String.Format(LocalizableStrings.CommandDoesNotExist, commandNameArgument.Value)); + return 1; + } + return 0; + }); + if (args.Length == 0) { PrintHelp(); @@ -56,7 +86,7 @@ Project modification commands: } else { - return Cli.Program.Main(new[] { args[0], "--help" }); + return app.Execute(args); } } @@ -73,5 +103,39 @@ Project modification commands: $" ({Product.Version})"; Reporter.Output.WriteLine(Product.LongName + versionString); } + + public static Process ConfigureProcess(string docUrl) + { + ProcessStartInfo psInfo; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + psInfo = new ProcessStartInfo + { + FileName = "cmd", + Arguments = $"/c start {docUrl}" + }; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + psInfo = new ProcessStartInfo + { + FileName = "open", + Arguments = docUrl + }; + } + else + { + psInfo = new ProcessStartInfo + { + FileName = "xdg-open", + Arguments = docUrl + }; + } + + return new Process + { + StartInfo = psInfo + }; + } } } diff --git a/src/dotnet/commands/dotnet-help/LocalizableStrings.cs b/src/dotnet/commands/dotnet-help/LocalizableStrings.cs index 04a938ac8..07371de63 100644 --- a/src/dotnet/commands/dotnet-help/LocalizableStrings.cs +++ b/src/dotnet/commands/dotnet-help/LocalizableStrings.cs @@ -66,5 +66,18 @@ namespace Microsoft.DotNet.Tools.Help public const string CleanDefinition = "Clean build output(s)."; public const string SlnDefinition = "Modify solution (SLN) files."; + + public const string CommandDoesNotExist = "Specified command {0} is not a valid CLI command. Please specify a valid CLI commands. For more information, run dotnet help."; + + public const string AppFullName = ".NET CLI help utility"; + + public const string AppDescription = "Utility to get more detailed help about each of the CLI commands."; + + public const string CommandArgumentName = "COMMAND_NAME"; + + public const string CommandArgumentDescription = "CLI command for which to view more detailed help."; + + + } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/LinuxOnlyFactAttribute.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/LinuxOnlyFactAttribute.cs new file mode 100644 index 000000000..cc8814c5b --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/LinuxOnlyFactAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.PlatformAbstractions; +using Xunit; + +namespace Microsoft.DotNet.Tools.Test.Utilities +{ + public class LinuxOnlyFactAttribute : FactAttribute + { + public LinuxOnlyFactAttribute() + { + if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Linux) + { + this.Skip = "This test requires linux to run"; + } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/MacOsOnlyFactAttribute.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/MacOsOnlyFactAttribute.cs new file mode 100644 index 000000000..0b4442621 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/MacOsOnlyFactAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.PlatformAbstractions; +using Xunit; + +namespace Microsoft.DotNet.Tools.Test.Utilities +{ + public class MacOsOnlyFactAttribute : FactAttribute + { + public MacOsOnlyFactAttribute() + { + if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Darwin) + { + this.Skip = "This test requires macos to run"; + } + } + } +} diff --git a/test/dotnet-help.Tests/GivenThatIWantToShowHelpForDotnetCommand.cs b/test/dotnet-help.Tests/GivenThatIWantToShowHelpForDotnetCommand.cs index e98fb85e5..ee049a5e7 100644 --- a/test/dotnet-help.Tests/GivenThatIWantToShowHelpForDotnetCommand.cs +++ b/test/dotnet-help.Tests/GivenThatIWantToShowHelpForDotnetCommand.cs @@ -8,6 +8,7 @@ using Microsoft.Build.Construction; using Microsoft.DotNet.Tools.Test.Utilities; using Xunit; using FluentAssertions; +using HelpActual = Microsoft.DotNet.Tools.Help; namespace Microsoft.DotNet.Help.Tests { @@ -65,5 +66,39 @@ Advanced Commands: cmd.Should().Pass(); cmd.StdOut.Should().ContainVisuallySameFragment(HelpText); } + + [Fact] + public void WhenInvalidCommandIsPassedToDOtnetHelpItPrintsError() + { + var cmd = new DotnetCommand() + .ExecuteWithCapturedOutput("help invalid"); + + cmd.Should().Fail(); + cmd.StdErr.Should().ContainVisuallySameFragment($"Specified command invalid is not a valid CLI command. Please specify a valid CLI commands. For more information, run dotnet help."); + } + + [WindowsOnlyFact] + public void WhenRunOnWindowsDotnetHelpCommandShouldContainProperProcessInformation() + { + var proc = HelpActual.HelpCommand.ConfigureProcess("https://aka.ms/dotnet-build"); + Assert.Equal("cmd", proc.StartInfo.FileName); + Assert.Equal("/c start https://aka.ms/dotnet-build", proc.StartInfo.Arguments); + } + + [LinuxOnlyFact] + public void WhenRunOnLinuxDotnetHelpCommandShouldContainProperProcessInformation() + { + var proc = HelpActual.HelpCommand.ConfigureProcess("https://aka.ms/dotnet-build"); + Assert.Equal("xdg-open", proc.StartInfo.FileName); + Assert.Equal("https://aka.ms/dotnet-build", proc.StartInfo.Arguments); + + } + [MacOsOnlyFact] + public void WhenRunOnMacOsDotnetHelpCommandShouldContainProperProcessInformation() + { + var proc = HelpActual.HelpCommand.ConfigureProcess("https://aka.ms/dotnet-build"); + Assert.Equal("open", proc.StartInfo.FileName); + Assert.Equal("https://aka.ms/dotnet-build", proc.StartInfo.Arguments); + } } }