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

fix for subcommand help formatting #27

Merged
merged 1 commit into from
Jun 13, 2024
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
21 changes: 19 additions & 2 deletions src/Xcaciv.Command.Core/AbstractCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,24 @@ public virtual void Help(IIoContext outputContext)
/// <param name="outputContext"></param>
public virtual void OneLineHelp(IIoContext outputContext)
{
var baseCommand = Attribute.GetCustomAttribute(GetType(), typeof(CommandRegisterAttribute)) as CommandRegisterAttribute;
if (baseCommand != null)
var thisType = GetType();
var baseCommand = Attribute.GetCustomAttribute(thisType, typeof(CommandRegisterAttribute)) as CommandRegisterAttribute;
if (baseCommand == null)
{
outputContext.AddTraceMessage($"no base command for {outputContext.Name}");
return;
}

if (Attribute.GetCustomAttribute(thisType, typeof(CommandRootAttribute)) is CommandRootAttribute)
{
outputContext.OutputChunk($"\t{baseCommand.Command,-12} {baseCommand.Description}");
}
else
{
outputContext.OutputChunk($"{baseCommand.Command,-12} {baseCommand.Description}");
}


}
/// <summary>
/// create a nicely formated
Expand All @@ -55,6 +70,8 @@ protected virtual string BuildHelpString()

// TODO: extract a help formatter so it can be customized
var builder = new StringBuilder();
if (Attribute.GetCustomAttribute(thisType, typeof(CommandRootAttribute)) is CommandRootAttribute rootCommand)
builder.Append($"{rootCommand.Command} {Environment.NewLine}\t");
builder.AppendLine($"{baseCommand?.Command}:");
builder.AppendLine($" {baseCommand?.Description}");
builder.AppendLine("Usage:");
Expand Down
2 changes: 1 addition & 1 deletion src/Xcaciv.Command.Core/Xcaciv.Command.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.4</Version>
<Version>1.0.6</Version>
<AssemblyName>Xcaciv.Command.Core</AssemblyName>
<RootNamespace>Xcaciv.Command.Core</RootNamespace>
<IsPublishable>True</IsPublishable>
Expand Down
10 changes: 7 additions & 3 deletions src/Xcaciv.Command.FileLoader/Crawler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,16 @@ public IDictionary<string, PackageDescription> LoadPackageDescriptions(string ba
try
{
var newDescription = CommandParameters.CreatePackageDescription(commandType, packagDesc);
if (newDescription.SubCommands.Count > 0 && commands.TryGetValue(commandType.Name, out ICommandDescription? description))

// when it is a sub command, we need to add it to a parent if it already exists
if (newDescription.SubCommands.Count > 0 && commands.TryGetValue(newDescription.BaseCommand, out ICommandDescription? description))
{
var subCommand = description.SubCommands.First().Value;
description.SubCommands.Add(subCommand.BaseCommand, subCommand);
var newSubCommand = newDescription.SubCommands.First().Value;
description.SubCommands[newSubCommand.BaseCommand] = newSubCommand;
}
else
{
// when the parent command does not exist, add it to the list
commands[newDescription.BaseCommand] = newDescription;
}
}
Expand All @@ -87,6 +90,7 @@ public IDictionary<string, PackageDescription> LoadPackageDescriptions(string ba
packagDesc.Commands = commands;
}

// dont add packages without valid commands
if (packagDesc.Commands.Count > 0) packages.TryAdd(key, packagDesc);
});

Expand Down
21 changes: 21 additions & 0 deletions src/Xcaciv.Command.Interface/ICommandController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,26 @@ public interface ICommandController
/// <param name="command"></param>
/// <param name="output"></param>
void GetHelp(string command, IIoContext output);
/// <summary>
/// install a single command into the index
/// </summary>
/// <param name="command"></param>
void AddCommand(ICommandDescription command);
/// <summary>
/// add a command from a loaded type
/// good for commands from internal or linked dlls
/// </summary>
/// <param name="packageKey"></param>
/// <param name="commandType"></param>
/// <param name="modifiesEnvironment"></param>
void AddCommand(string packageKey, Type commandType, bool modifiesEnvironment = false);
/// <summary>
/// add a command from an instance of the command
/// good for commands from internal or linked dlls
/// </summary>
/// <param name="packageKey"></param>
/// <param name="command"></param>
/// <param name="modifiesEnvironment"></param>
void AddCommand(string packageKey, ICommandDelegate command, bool modifiesEnvironment = false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.1.19</Version>
<Version>1.1.21</Version>
<IsPublishable>True</IsPublishable>
<AssemblyName>Xcaciv.Command.Interface</AssemblyName>
<RootNamespace>Xcaciv.Command.Interface</RootNamespace>
Expand Down
35 changes: 34 additions & 1 deletion src/Xcaciv.Command.Tests/CommandControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Xcaciv.Command.FileLoader;
using System.IO.Abstractions.TestingHelpers;
using Moq;
using Xcaciv.Command.Tests.TestImpementations;

namespace Xcaciv.Command.Tests
{
Expand Down Expand Up @@ -74,8 +75,21 @@ public async Task PipeCommandsTestAsync()

// verify the output of the first run
// by looking at the output of the second output line
Assert.Equal(":d2hhdC13aGF0:-:aXMtaXM=:-:dXAtdXA=:", textio.ToString());
Assert.Equal(":d2hhdC13aGF0:\r\n:aXMtaXM=:\r\n:dXAtdXA=:", textio.ToString());
}
[Fact()]
public void LoadCommandsTest()
{
var controller = new CommandControllerTestHarness(new Crawler(), @"..\..\..\..\..\");
controller.AddPackageDirectory(commandPackageDir);
controller.EnableDefaultCommands();
controller.LoadCommands(string.Empty);

var commands = controller.GetCommands();

Assert.Equal(2, commands["DO"]?.SubCommands.Count);
}

#pragma warning disable CS8602 // Dereference of a possibly null reference.
[Fact()]
public void LoadDefaultCommandsTest()
Expand All @@ -89,6 +103,25 @@ public void LoadDefaultCommandsTest()
// Note: currently Loader is not unloading assemblies for performance reasons
Assert.Contains("REGIF", textio.ToString());
}
[Fact()]
public void HelpCommandsTestAsync()
{
var controller = new CommandControllerTestHarness(new Crawler(), @"..\..\..\..\..\") as Interface.ICommandController;
controller.EnableDefaultCommands();

controller.AddPackageDirectory(commandPackageDir);
controller.LoadCommands(string.Empty);

var textio = new TestImpementations.TestTextIo();
//var env = new EnvironmentContext();
controller.GetHelp(string.Empty, textio);
var output = textio.ToString();

//BROKEN

// Note: currently Loader is not unloading assemblies for performance reasons
Assert.Contains("SUB DO say", output);
}
#pragma warning restore CS8602 // Dereference of a possibly null reference.
}
}
2 changes: 1 addition & 1 deletion src/Xcaciv.Command.Tests/Commands/RegifCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async Task HandleExecutionTestAsync()

// verify the output of the first run
// by looking at the output of the second output line
Assert.Equal("-is-", textio.ToString());
Assert.Equal("is", textio.ToString().Trim());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xcaciv.Command.FileLoader;
using Xcaciv.Command.Interface;

namespace Xcaciv.Command.Tests.TestImpementations
{
internal class CommandControllerTestHarness : CommandController
{
/// <summary>
/// Command Manger
/// </summary>
public CommandControllerTestHarness() : base(new Crawler()) { }
/// <summary>
/// Command Manger
/// </summary>
public CommandControllerTestHarness(ICrawler crawler) : base(crawler) { }

/// <summary>
/// Command Manager constructor to specify restricted directory
/// </summary>
/// <param name="restrictedDirectory"></param>
public CommandControllerTestHarness(ICrawler crawler, string restrictedDirectory) : base(crawler, restrictedDirectory) { }
/// <summary>
/// Command Manager test constructor
/// </summary>
/// <param name="packageBinearyDirectories"></param>
public CommandControllerTestHarness(IVerfiedSourceDirectories packageBinearyDirectories) : base(packageBinearyDirectories) { }

internal Dictionary<string, ICommandDescription> GetCommands()
{
return this.Commands;
}
}
}
2 changes: 1 addition & 1 deletion src/Xcaciv.Command.Tests/TestImpementations/TestTextIo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public override string ToString()
}
}

output += string.Join('-', Output);
output += string.Join(Environment.NewLine, Output);

return output;
}
Expand Down
50 changes: 41 additions & 9 deletions src/Xcaciv.Command/CommandController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public void AddCommand(string packageKey, ICommandDelegate command, bool modifie
/// <param name="packageKey"></param>
/// <param name="commandType"></param>
/// <param name="modifiesEnvironment"></param>
public void AddCommand(string packageKey, Type commandType, bool modifiesEnvironment)
public void AddCommand(string packageKey, Type commandType, bool modifiesEnvironment = false)
{
if (Attribute.GetCustomAttribute(commandType, typeof(CommandRegisterAttribute)) is CommandRegisterAttribute attributes)
{
Expand All @@ -138,7 +138,7 @@ public void AddCommand(string packageKey, Type commandType, bool modifiesEnviron
PackageDescription = new PackageDescription()
{
Name = packageKey,
FullPath = ""
FullPath = commandType.Assembly.Location
},
ModifiesEnvironment = modifiesEnvironment
});
Expand Down Expand Up @@ -299,14 +299,15 @@ protected static ICommandDelegate GetCommandInstance(ICommandDescription command

protected static ICommandDelegate GetCommandInstance(string fullTypeName, string packagePath)
{
if (String.IsNullOrEmpty(fullTypeName)) throw new InvalidOperationException("Command type name is empty.");
Type? executeDeligateType = Type.GetType(fullTypeName);
ICommandDelegate commandInstance;
if (executeDeligateType == null)
{
using (var context = new AssemblyContext(packagePath, basePathRestriction:"*")) // TODO: restrict the path
{
commandInstance = context.CreateInstance<ICommandDelegate>(fullTypeName);
}
if (String.IsNullOrEmpty(packagePath)) throw new InvalidOperationException($"Command [{fullTypeName}] is not loaded and no assembly was defined.");

using var context = new AssemblyContext(packagePath, basePathRestriction: "*"); // TODO: restrict the path
commandInstance = context.CreateInstance<ICommandDelegate>(fullTypeName);
}
else
{
Expand All @@ -332,11 +333,37 @@ protected static async Task ExecuteCommand(IIoContext ioContext, ICommandDelegat
public void GetHelp(string command, IIoContext context)
{
if (String.IsNullOrEmpty(command))
foreach(var description in Commands)
{
foreach (var description in Commands)
{
var cmdInsance = GetCommandInstance(description.Value);
cmdInsance.OneLineHelp(context);
if (String.IsNullOrEmpty(description.Value.FullTypeName))
{
if (description.Value.SubCommands.Count > 0)
{
// get the first sub command to get the type to get the root command
var subCmd = GetCommandInstance(description.Value.SubCommands.First().Value);

if (subCmd != null && Attribute.GetCustomAttribute(subCmd.GetType(), typeof(CommandRootAttribute)) is CommandRootAttribute rootAttribute)
{
context.OutputChunk($"{rootAttribute.Command,-12} {rootAttribute.Description}");
}

foreach (var subCommand in description.Value.SubCommands)
{
outputOneLineHelp(context, subCommand.Value);
}
}
else
{
context.AddTraceMessage($"No type name registered for command: {description.Key}");
}
}
else
{
outputOneLineHelp(context, description.Value);
}
}
}
else
{
try
Expand All @@ -361,4 +388,9 @@ public void GetHelp(string command, IIoContext context)
}
}

protected static void outputOneLineHelp(IIoContext context, ICommandDescription description)
{
var cmdInsance = GetCommandInstance(description);
cmdInsance.OneLineHelp(context);
}
}
2 changes: 1 addition & 1 deletion src/Xcaciv.Command/Xcaciv.Command.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.4.19</Version>
<Version>1.4.23</Version>
<AssemblyName>Xcaciv.Command</AssemblyName>
<RootNamespace>Xcaciv.Command</RootNamespace>
<IsPublishable>True</IsPublishable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
namespace zTestCommandPackage
{
[CommandRoot("do", "does stuff")]
[CommandRegister("ECHO", "echoes stuff to test subcommands")]
public class EchoDoCommand : AbstractCommand, ICommandDelegate
[CommandRegister("ECHO", "SUB DO echo")]
public class DoEchoCommand : AbstractCommand, ICommandDelegate
{
public override string HandleExecution(string[] parameters, IEnvironmentContext env)
{
Expand Down
26 changes: 26 additions & 0 deletions src/zTestCommandPackage/DoSayCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xcaciv.Command.Core;
using Xcaciv.Command.Interface;
using Xcaciv.Command.Interface.Attributes;

namespace zTestCommandPackage
{
[CommandRoot("do", "does stuff")]
[CommandRegister("SAY", "SUB DO say")]
public class DoSayCommand : AbstractCommand
{
public override string HandleExecution(string[] parameters, IEnvironmentContext env)
{
return String.Join(' ', parameters);
}

public override string HandlePipedChunk(string pipedChunk, string[] parameters, IEnvironmentContext env)
{
return pipedChunk + String.Join(' ', parameters);
}
}
}