Add support for dotnet help <verb>

This commit adds supports for getting more detailed help by using the
`dotnet help <verb>` syntax (e.g. `dotnet help build`). This change
opens up the URL that is specified for each verb in the default browser
on the user's machine, so internet access is required.
This commit is contained in:
blackdwarf 2017-02-17 09:00:25 -08:00 committed by Zlatko Knezevic
parent b42697ff09
commit 4aacb22993
9 changed files with 292 additions and 50 deletions

View file

@ -0,0 +1,10 @@
using System;
namespace Microsoft.DotNet.Cli
{
public class BuiltInCommandMetadata
{
public Func<string[], int> Command { get; set; }
public Uri DocLink { get; set; }
}
}

View file

@ -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<string, BuiltInCommandMetadata> Commands = new Dictionary<string, BuiltInCommandMetadata>
{
["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")
}
};
}
}

View file

@ -24,13 +24,13 @@ namespace Microsoft.DotNet.Cli
NuGetFramework framework = null, NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration) string configuration = Constants.DefaultConfiguration)
{ {
Func<string[], int> builtInCommand; BuiltInCommandMetadata builtInCommand;
if (!_alwaysRunOutOfProc && Program.TryGetBuiltInCommand(commandName, out builtInCommand)) if (!_alwaysRunOutOfProc && Program.TryGetBuiltInCommand(commandName, out builtInCommand))
{ {
Debug.Assert(framework == null, "BuiltInCommand doesn't support the 'framework' argument."); Debug.Assert(framework == null, "BuiltInCommand doesn't support the 'framework' argument.");
Debug.Assert(configuration == Constants.DefaultConfiguration, "BuiltInCommand doesn't support the 'configuration' 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); return Command.CreateDotNet(commandName, args, framework, configuration);

View file

@ -2,60 +2,18 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Configurer; using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.PlatformAbstractions; 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.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; using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli namespace Microsoft.DotNet.Cli
{ {
public class Program public class Program
{ {
private static Dictionary<string, Func<string[], int>> s_builtIns = new Dictionary<string, Func<string[], int>>
{
["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) public static int Main(string[] args)
{ {
DebugHelper.HandleDebugSwitch(ref args); DebugHelper.HandleDebugSwitch(ref args);
@ -169,10 +127,12 @@ namespace Microsoft.DotNet.Cli
telemetryClient.TrackEvent(command, null, null); telemetryClient.TrackEvent(command, null, null);
int exitCode; int exitCode;
Func<string[], int> builtIn; // Func<string[], int> builtIn;
if (s_builtIns.TryGetValue(command, out 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 else
{ {
@ -215,9 +175,9 @@ namespace Microsoft.DotNet.Cli
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
} }
internal static bool TryGetBuiltInCommand(string commandName, out Func<string[], int> 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() private static void PrintVersion()

View file

@ -1,7 +1,11 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. // 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. // 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.Reflection;
using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Help namespace Microsoft.DotNet.Tools.Help
@ -49,6 +53,32 @@ Project modification commands:
public static int Run(string[] args) 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) if (args.Length == 0)
{ {
PrintHelp(); PrintHelp();
@ -56,7 +86,7 @@ Project modification commands:
} }
else else
{ {
return Cli.Program.Main(new[] { args[0], "--help" }); return app.Execute(args);
} }
} }
@ -73,5 +103,39 @@ Project modification commands:
$" ({Product.Version})"; $" ({Product.Version})";
Reporter.Output.WriteLine(Product.LongName + versionString); 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
};
}
} }
} }

View file

@ -66,5 +66,18 @@ namespace Microsoft.DotNet.Tools.Help
public const string CleanDefinition = "Clean build output(s)."; public const string CleanDefinition = "Clean build output(s).";
public const string SlnDefinition = "Modify solution (SLN) files."; 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.";
} }
} }

View file

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

View file

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

View file

@ -8,6 +8,7 @@ using Microsoft.Build.Construction;
using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit; using Xunit;
using FluentAssertions; using FluentAssertions;
using HelpActual = Microsoft.DotNet.Tools.Help;
namespace Microsoft.DotNet.Help.Tests namespace Microsoft.DotNet.Help.Tests
{ {
@ -65,5 +66,39 @@ Advanced Commands:
cmd.Should().Pass(); cmd.Should().Pass();
cmd.StdOut.Should().ContainVisuallySameFragment(HelpText); 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);
}
} }
} }