Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLI] Open help in web browser #1179

Merged
merged 2 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Remove empty region directive ([RCS1091](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1091))
- Remove empty destructor ([RCS1106](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1106))

### Changed

- [CLI] Open help in web browser when running command `roslynator help <COMMAND>` ([#1179](https://github.com/josefpihrt/roslynator/pull/1179))

### Fixed

- Fix [RCS1187](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1187) ([#1150](https://github.com/JosefPihrt/Roslynator/pull/1150)).
Expand Down
163 changes: 70 additions & 93 deletions src/CommandLine/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,45 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Roslynator.CommandLine.Help;
using static Roslynator.Logger;

namespace Roslynator.CommandLine;

internal class HelpCommand
{
public HelpCommand(HelpCommandLineOptions options, Filter filter)
public HelpCommand(HelpCommandLineOptions options)
{
Options = options;
Filter = filter;
}

public HelpCommandLineOptions Options { get; }

public Filter Filter { get; }

public CommandStatus Execute()
{
try
{
WriteHelp(
commandName: Options.Command,
online: false,
manual: Options.Manual,
includeValues: ConsoleOut.Verbosity > Verbosity.Normal,
filter: Filter);
if (Options.Manual)
{
WriteManual();
}
else if (Options.Command is not null)
{
Command command = CommandLoader.LoadCommand(typeof(HelpCommand).Assembly, Options.Command);

if (command is null)
throw new InvalidOperationException($"Command '{Options.Command}' does not exist.");

OpenHelpInBrowser(Options.Command);
}
else
{
WriteCommandsHelp();
}

return CommandStatus.Success;
}
Expand All @@ -42,136 +52,103 @@ public CommandStatus Execute()
}
}

private static void WriteHelp(
string commandName,
bool online,
bool manual,
bool includeValues,
Filter filter = null)
private static void OpenHelpInBrowser(string commandName)
{
if (online)
{
OpenHelpInBrowser(commandName);
}
else if (commandName is not null)
{
Command command = CommandLoader.LoadCommand(typeof(HelpCommand).Assembly, commandName);
var url = "https://josefpihrt.github.io/docs/roslynator/cli";

if (command is null)
throw new InvalidOperationException($"Command '{commandName}' does not exist.");
if (commandName is not null)
url += $"/commands/{commandName}";

WriteCommandHelp(command, includeValues: includeValues, filter: filter);
}
else if (manual)
try
{
WriteManual(includeValues: includeValues, filter: filter);
Process.Start(url);
}
else
catch
{
WriteCommandsHelp(includeValues: includeValues, filter: filter);
}
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var psi = new ProcessStartInfo()
{
FileName = url,
UseShellExecute = true
};

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter")]
private static void OpenHelpInBrowser(string commandName)
{
throw new NotSupportedException();
Process.Start(psi);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}

public static void WriteCommandHelp(Command command, bool includeValues = false, Filter filter = null)
public static void WriteCommandHelp(Command command)
{
var writer = new ConsoleHelpWriter(new HelpWriterOptions(filter: filter));
var writer = new ConsoleHelpWriter();

command = command.WithOptions(command.Options.Sort(CommandOptionComparer.Name));

CommandHelp commandHelp = CommandHelp.Create(command, providers: null, filter: filter);
CommandHelp commandHelp = CommandHelp.Create(command);

writer.WriteCommand(commandHelp);

if (includeValues)
writer.WriteValues(commandHelp.Values);
}

public static void WriteCommandsHelp(bool includeValues = false, Filter filter = null)
public static void WriteCommandsHelp()
{
IEnumerable<Command> commands = LoadCommands().Where(f => f.Name != "help");

CommandsHelp commandsHelp = CommandsHelp.Create(commands, providers: null, filter: filter);
CommandsHelp commandsHelp = CommandsHelp.Create(commands);

var writer = new ConsoleHelpWriter(new HelpWriterOptions(filter: filter));
var writer = new ConsoleHelpWriter(new HelpWriterOptions());

writer.WriteCommands(commandsHelp);

if (includeValues)
writer.WriteValues(commandsHelp.Values);

WriteLine();
WriteLine(GetFooterText());
}

private static void WriteManual(bool includeValues = false, Filter filter = null)
private static void WriteManual()
{
IEnumerable<Command> commands = LoadCommands();

var writer = new ConsoleHelpWriter(new HelpWriterOptions(filter: filter));
var writer = new ConsoleHelpWriter();

IEnumerable<CommandHelp> commandHelps = commands.Select(f => CommandHelp.Create(f, filter: filter))
IEnumerable<CommandHelp> commandHelps = commands.Select(f => CommandHelp.Create(f))
.Where(f => f.Arguments.Any() || f.Options.Any())
.ToImmutableArray();

ImmutableArray<CommandItem> commandItems = HelpProvider.GetCommandItems(commandHelps.Select(f => f.Command));

ImmutableArray<OptionValueList> values = ImmutableArray<OptionValueList>.Empty;
var commandsHelp = new CommandsHelp(commandItems, ImmutableArray<OptionValueList>.Empty);

if (commandItems.Any())
{
values = HelpProvider.GetOptionValues(
commandHelps.SelectMany(f => f.Command.Options),
providers: ImmutableArray<OptionValueProvider>.Empty,
filter);
writer.WriteCommands(commandsHelp);

var commandsHelp = new CommandsHelp(commandItems, values);
foreach (CommandHelp commandHelp in commandHelps)
{
WriteSeparator();
WriteLine();
WriteLine($"Command: {commandHelp.Name}");
WriteLine();

writer.WriteCommands(commandsHelp);
string description = commandHelp.Description;

foreach (CommandHelp commandHelp in commandHelps)
if (!string.IsNullOrEmpty(description))
{
WriteSeparator();
WriteLine(description);
WriteLine();
WriteLine($"Command: {commandHelp.Name}");
WriteLine();

string description = commandHelp.Description;

if (!string.IsNullOrEmpty(description))
{
WriteLine(description);
WriteLine();
}

writer.WriteCommand(commandHelp);
}

if (includeValues)
WriteSeparator();
}
else
{
WriteLine();
WriteLine("No command found");

if (includeValues)
{
values = HelpProvider.GetOptionValues(
commands.Select(f => CommandHelp.Create(f)).SelectMany(f => f.Command.Options),
providers: ImmutableArray<OptionValueProvider>.Empty,
filter);
}
writer.WriteCommand(commandHelp);
}

if (includeValues)
writer.WriteValues(values);

static void WriteSeparator()
{
WriteLine();
Expand Down
1 change: 0 additions & 1 deletion src/CommandLine/OptionShortNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ internal static class OptionShortNames
{
public const char AnalyzerAssemblies = 'a';
public const char DryRun = 'd';
public const char Filter = 'f';
public const char IncludeGeneratedCode = 'g';
public const char Help = 'h';
public const char Manual = 'm';
Expand Down
8 changes: 1 addition & 7 deletions src/CommandLine/Options/HelpCommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ internal sealed class HelpCommandLineOptions : AbstractCommandLineOptions
HelpText = "Command name.",
MetaName = "<COMMAND>")]
public string Command { get; set; } = null!;
#if DEBUG
[Option(
shortName: OptionShortNames.Filter,
longName: "filter",
HelpText = "Search phrase to filter the results.")]
public string Filter { get; set; }
#endif

[Option(
shortName: OptionShortNames.Manual,
longName: "manual",
Expand Down
17 changes: 1 addition & 16 deletions src/CommandLine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -492,22 +492,7 @@ private static async Task<int> RenameSymbolAsync(RenameSymbolCommandLineOptions

private static int Help(HelpCommandLineOptions options)
{
Filter filter = null;
#if DEBUG
if (!string.IsNullOrEmpty(options.Filter))
{
try
{
filter = new Filter(new Regex(options.Filter, RegexOptions.IgnoreCase));
}
catch (ArgumentException ex)
{
WriteLine($"Filter is invalid: {ex.Message}", Verbosity.Quiet);
return ExitCodes.Error;
}
}
#endif
var command = new HelpCommand(options: options, filter);
var command = new HelpCommand(options: options);

CommandStatus status = command.Execute();

Expand Down