Skip to content
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
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/CommandAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class CommandAttribute : Attribute, ICommandMetadata
public string Name { get; }
public string? SubCommand { get; }
public string HelpText { get; }

public string? Example { get; set; }
public bool IsPreview { get; set; }

public CommandAttribute(string name, string helpText)
{
Expand Down
18 changes: 12 additions & 6 deletions src/SeqCli/Cli/CommandLineHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,25 @@ public async Task<int> Run(string[] args, LoggingLevelSwitch levelSwitch)

if (args.Length > 0)
{
const string prereleaseArg = "--pre", verboseArg = "--verbose";

var norm = args[0].ToLowerInvariant();
var subCommandNorm = args.Length > 1 && !args[1].Contains('-') ? args[1].ToLowerInvariant() : null;


var pre = args.Any(a => a == prereleaseArg);

var cmd = _availableCommands.SingleOrDefault(c =>
c.Metadata.Name == norm && (c.Metadata.SubCommand == subCommandNorm || c.Metadata.SubCommand == null));
(!c.Metadata.IsPreview || pre) &&
c.Metadata.Name == norm &&
(c.Metadata.SubCommand == subCommandNorm || c.Metadata.SubCommand == null));

if (cmd != null)
{
var amountToSkip = cmd.Metadata.SubCommand == null ? 1 : 2;
var commandSpecificArgs = args.Skip(amountToSkip).ToArray();
var commandSpecificArgs = args.Skip(amountToSkip).Where(arg => arg != prereleaseArg).ToArray();

var verboseArg = commandSpecificArgs.FirstOrDefault(arg => arg == "--verbose");
if (verboseArg != null)
var verbose = commandSpecificArgs.Any(arg => arg == verboseArg);
if (verbose)
{
levelSwitch.MinimumLevel = LogEventLevel.Information;
commandSpecificArgs = commandSpecificArgs.Where(arg => arg != verboseArg).ToArray();
Expand All @@ -63,6 +69,6 @@ public async Task<int> Run(string[] args, LoggingLevelSwitch levelSwitch)

Console.WriteLine($"Usage: {name} <command> [<args>]");
Console.WriteLine($"Type `{name} help` for available commands");
return -1;
return 1;
}
}
3 changes: 2 additions & 1 deletion src/SeqCli/Cli/CommandMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public class CommandMetadata : ICommandMetadata
public string? SubCommand { get; set; }
public string HelpText { get; set; } = null!;
public string? Example { get; set; }
}
public bool IsPreview { get; set; }
}
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/InstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

namespace SeqCli.Forwarder.Cli.Commands
{
[Command("forwarder", "install", "Install the forwarder as a Windows service")]
[Command("forwarder", "install", "Install the forwarder as a Windows service", IsPreview = true)]
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
class InstallCommand : Command
{
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/RestartCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

namespace SeqCli.Forwarder.Cli.Commands
{
[Command("forwarder", "restart", "Restart the forwarder Windows service")]
[Command("forwarder", "restart", "Restart the forwarder Windows service", IsPreview = true)]
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
class RestartCommand : Command
{
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

namespace SeqCli.Cli.Commands.Forwarder;

[Command("forwarder", "run", "Listen on an HTTP endpoint and forward ingested logs to Seq")]
[Command("forwarder", "run", "Listen on an HTTP endpoint and forward ingested logs to Seq", IsPreview = true)]
class RunCommand : Command
{
readonly StoragePathFeature _storagePath;
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/StartCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace SeqCli.Forwarder.Cli.Commands
{
[Command("forwarder", "start", "Start the forwarder Windows service")]
[Command("forwarder", "start", "Start the forwarder Windows service", IsPreview = true)]
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
class StartCommand : Command
{
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/StatusCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace SeqCli.Forwarder.Cli.Commands
{
[Command("forwarder", "status", "Show the status of the forwarder Windows service")]
[Command("forwarder", "status", "Show the status of the forwarder Windows service", IsPreview = true)]
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
class StatusCommand : Command
{
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/StopCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace SeqCli.Forwarder.Cli.Commands
{
[Command("forwarder", "stop", "Stop the forwarder Windows service")]
[Command("forwarder", "stop", "Stop the forwarder Windows service", IsPreview = true)]
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
class StopCommand : Command
{
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/TruncateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

namespace SeqCli.Cli.Commands.Forwarder;

[Command("forwarder", "truncate", "Empty the forwarder's persistent log buffer")]
[Command("forwarder", "truncate", "Empty the forwarder's persistent log buffer", IsPreview = true)]
class TruncateCommand : Command
{
readonly StoragePathFeature _storagePath;
Expand Down
2 changes: 1 addition & 1 deletion src/SeqCli/Cli/Commands/Forwarder/UninstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace SeqCli.Forwarder.Cli.Commands
{
[Command("forwarder", "uninstall", "Uninstall the forwarder Windows service")]
[Command("forwarder", "uninstall", "Uninstall the forwarder Windows service", IsPreview = true)]
class UninstallCommand : Command
{
protected override Task<int> Run()
Expand Down
38 changes: 23 additions & 15 deletions src/SeqCli/Cli/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,31 @@
using System.Reflection;
using System.Threading.Tasks;
using Autofac.Features.Metadata;
using CommandList = System.Collections.Generic.List<Autofac.Features.Metadata.Meta<System.Lazy<SeqCli.Cli.Command>, SeqCli.Cli.CommandMetadata>>;

namespace SeqCli.Cli.Commands;

[Command("help", "Show information about available commands", Example = "seqcli help search")]
class HelpCommand : Command
{
readonly List<Meta<Lazy<Command>, CommandMetadata>> _orderedCommands;
bool _markdown;
readonly IEnumerable<Meta<Lazy<Command>, CommandMetadata>> _availableCommands;
bool _markdown, _pre;

public HelpCommand(IEnumerable<Meta<Lazy<Command>, CommandMetadata>> availableCommands)
{
_availableCommands = availableCommands;
Options.Add("pre", "Show preview commands", _ => _pre = true);
Options.Add("m|markdown", "Generate markdown for use in documentation", _ => _markdown = true);
_orderedCommands = availableCommands.OrderBy(c => c.Metadata.Name).ThenBy(c => c.Metadata.SubCommand).ToList();
}

protected override Task<int> Run(string[] unrecognized)
{
var orderedCommands = _availableCommands
.Where(c => !c.Metadata.IsPreview || _pre)
.OrderBy(c => c.Metadata.Name)
.ThenBy(c => c.Metadata.SubCommand)
.ToList();

var ea = Assembly.GetEntryAssembly();
// ReSharper disable once PossibleNullReferenceException
var name = ea!.GetName().Name!;
Expand All @@ -44,7 +52,7 @@ protected override Task<int> Run(string[] unrecognized)
if (unrecognized.Length != 0)
return base.Run(unrecognized);

PrintMarkdownHelp(name);
PrintMarkdownHelp(name, orderedCommands);
return Task.FromResult(0);
}

Expand All @@ -53,7 +61,7 @@ protected override Task<int> Run(string[] unrecognized)
{
topLevelCommand = unrecognized[0].ToLowerInvariant();
var subCommand = unrecognized.Length > 1 && !unrecognized[1].Contains("-") ? unrecognized[1] : null;
var cmds = _orderedCommands.Where(c => c.Metadata.Name == topLevelCommand &&
var cmds = orderedCommands.Where(c => c.Metadata.Name == topLevelCommand &&
(subCommand == null || subCommand == c.Metadata.SubCommand)).ToArray();

if (cmds.Length == 1 && cmds[0].Metadata.SubCommand == subCommand)
Expand All @@ -79,15 +87,15 @@ protected override Task<int> Run(string[] unrecognized)
}
}

if (topLevelCommand != null && _orderedCommands.Any(a => a.Metadata.Name == topLevelCommand))
PrintHelp(name, topLevelCommand);
if (topLevelCommand != null && orderedCommands.Any(a => a.Metadata.Name == topLevelCommand))
PrintHelp(name, topLevelCommand, orderedCommands);
else
PrintHelp(name);
PrintHelp(name, orderedCommands);

return Task.FromResult(0);
}

void PrintMarkdownHelp(string executableName)
static void PrintMarkdownHelp(string executableName, CommandList orderedCommands)
{
Console.WriteLine("## Commands");
Console.WriteLine();
Expand All @@ -101,7 +109,7 @@ void PrintMarkdownHelp(string executableName)
Console.WriteLine("Available commands:");
Console.WriteLine();

foreach (var cmd in _orderedCommands.GroupBy(cmd => cmd.Metadata.Name).OrderBy(c => c.Key))
foreach (var cmd in orderedCommands.GroupBy(cmd => cmd.Metadata.Name).OrderBy(c => c.Key))
{
if (cmd.Count() == 1)
{
Expand All @@ -122,7 +130,7 @@ void PrintMarkdownHelp(string executableName)
}
Console.WriteLine();

foreach (var cmd in _orderedCommands)
foreach (var cmd in orderedCommands)
{
if (cmd.Metadata.SubCommand != null)
Console.WriteLine($"### `{cmd.Metadata.Name} {cmd.Metadata.SubCommand}`");
Expand Down Expand Up @@ -166,14 +174,14 @@ void PrintMarkdownHelp(string executableName)
}
}

void PrintHelp(string executableName)
static void PrintHelp(string executableName, CommandList orderedCommands)
{
Console.WriteLine($"Usage: {executableName} <command> [<args>]");
Console.WriteLine();
Console.WriteLine("Available commands are:");

var printedGroups = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var avail in _orderedCommands)
foreach (var avail in orderedCommands)
{
if (avail.Metadata.SubCommand != null)
{
Expand All @@ -193,13 +201,13 @@ void PrintHelp(string executableName)
Console.WriteLine($"Type `{executableName} help <command>` for detailed help");
}

void PrintHelp(string executableName, string topLevelCommand)
static void PrintHelp(string executableName, string topLevelCommand, CommandList orderedCommands)
{
Console.WriteLine($"Usage: {executableName} {topLevelCommand} <sub-command> [<args>]");
Console.WriteLine();
Console.WriteLine("Available sub-commands are:");

foreach (var avail in _orderedCommands.Where(c => c.Metadata.Name == topLevelCommand))
foreach (var avail in orderedCommands.Where(c => c.Metadata.Name == topLevelCommand))
{
Printing.Define($" {avail.Metadata.SubCommand}", avail.Metadata.HelpText, Console.Out);
}
Expand Down
2 changes: 2 additions & 0 deletions test/SeqCli.EndToEnd/Help/MarkdownHelpTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public Task ExecuteAsync(SeqConnection connection, ILogger logger, CliCommandRun
var indexOfTemplateImport = markdown.IndexOf("### `template import`", StringComparison.Ordinal);
Assert.NotEqual(indexOfTemplateExport, indexOfTemplateImport);
Assert.True(indexOfTemplateExport < indexOfTemplateImport);

Assert.DoesNotContain("### `forwarder run`", markdown);

return Task.CompletedTask;
}
Expand Down
41 changes: 27 additions & 14 deletions test/SeqCli.Tests/Cli/CommandLineHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Autofac.Features.Metadata;
using SeqCli.Cli;
using SeqCli.Tests.Support;
using Serilog.Core;
using Serilog.Events;
using Xunit;
Expand All @@ -13,7 +14,7 @@ namespace SeqCli.Tests.Cli;
public class CommandLineHostTests
{
[Fact]
public async Task CheckCommandLineHostPicksCorrectCommand()
public async Task CommandLineHostPicksCorrectCommand()
{
var executed = new List<string>();
var availableCommands = new List<Meta<Lazy<Command>, CommandMetadata>>
Expand All @@ -28,27 +29,47 @@ public async Task CheckCommandLineHostPicksCorrectCommand()
var commandLineHost = new CommandLineHost(availableCommands);
await commandLineHost.Run(["test"],new LoggingLevelSwitch());

Assert.Equal("test", executed.First());
Assert.Equal("test", executed.Single());
}

[Fact]
public async Task PrereleaseCommandsAreIgnoredWithoutFlag()
{
var executed = new List<string>();
var availableCommands = new List<Meta<Lazy<Command>, CommandMetadata>>
{
new(
new Lazy<Command>(() => new ActionCommand(() => executed.Add("test"))),
new CommandMetadata {Name = "test", IsPreview = true}),
};
var commandLineHost = new CommandLineHost(availableCommands);
var exit = await commandLineHost.Run(["test"],new LoggingLevelSwitch());
Assert.Equal(1, exit);
Assert.Empty(executed);

exit = await commandLineHost.Run(["test", "--pre"],new LoggingLevelSwitch());
Assert.Equal(0, exit);
Assert.Equal("test", executed.Single());
}

[Fact]
public async Task WhenMoreThanOneSubcommandAndTheUserRunsWithSubcommandEnsurePickedCorrect()
{
var commandsRan = new List<string>();
var executed = new List<string>();
var availableCommands =
new List<Meta<Lazy<Command>, CommandMetadata>>
{
new(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test-subcommand1"))),
new Lazy<Command>(() => new ActionCommand(() => executed.Add("test-subcommand1"))),
new CommandMetadata {Name = "test", SubCommand = "subcommand1"}),
new(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test-subcommand2"))),
new Lazy<Command>(() => new ActionCommand(() => executed.Add("test-subcommand2"))),
new CommandMetadata {Name = "test", SubCommand = "subcommand2"})
};
var commandLineHost = new CommandLineHost(availableCommands);
await commandLineHost.Run(["test", "subcommand2"], new LoggingLevelSwitch());

Assert.Equal("test-subcommand2", commandsRan.First());
Assert.Equal("test-subcommand2", executed.First());
}

[Fact]
Expand All @@ -70,12 +91,4 @@ public async Task VerboseOptionSetsLoggingLevelToInformation()

Assert.Equal(LogEventLevel.Information, levelSwitch.MinimumLevel);
}

class ActionCommand : Command
{
public ActionCommand(Action action)
{
action.Invoke();
}
}
}
12 changes: 12 additions & 0 deletions test/SeqCli.Tests/Support/ActionCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using SeqCli.Cli;

namespace SeqCli.Tests.Support;

class ActionCommand : Command
{
public ActionCommand(Action action)
{
action.Invoke();
}
}