diff --git a/src/Xcaciv.Command.Core/AbstractCommand.cs b/src/Xcaciv.Command.Core/AbstractCommand.cs
index 6dcd508..b309d15 100644
--- a/src/Xcaciv.Command.Core/AbstractCommand.cs
+++ b/src/Xcaciv.Command.Core/AbstractCommand.cs
@@ -35,9 +35,24 @@ public virtual void Help(IIoContext outputContext)
///
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}");
+ }
+
+
}
///
/// create a nicely formated
@@ -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:");
diff --git a/src/Xcaciv.Command.Core/Xcaciv.Command.Core.csproj b/src/Xcaciv.Command.Core/Xcaciv.Command.Core.csproj
index 3286782..30c3729 100644
--- a/src/Xcaciv.Command.Core/Xcaciv.Command.Core.csproj
+++ b/src/Xcaciv.Command.Core/Xcaciv.Command.Core.csproj
@@ -3,7 +3,7 @@
enable
enable
- 1.0.4
+ 1.0.6
Xcaciv.Command.Core
Xcaciv.Command.Core
True
diff --git a/src/Xcaciv.Command.FileLoader/Crawler.cs b/src/Xcaciv.Command.FileLoader/Crawler.cs
index 6839f12..b24c17b 100644
--- a/src/Xcaciv.Command.FileLoader/Crawler.cs
+++ b/src/Xcaciv.Command.FileLoader/Crawler.cs
@@ -68,13 +68,16 @@ public IDictionary 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;
}
}
@@ -87,6 +90,7 @@ public IDictionary LoadPackageDescriptions(string ba
packagDesc.Commands = commands;
}
+ // dont add packages without valid commands
if (packagDesc.Commands.Count > 0) packages.TryAdd(key, packagDesc);
});
diff --git a/src/Xcaciv.Command.Interface/ICommandController.cs b/src/Xcaciv.Command.Interface/ICommandController.cs
index b1eac02..53de5e4 100644
--- a/src/Xcaciv.Command.Interface/ICommandController.cs
+++ b/src/Xcaciv.Command.Interface/ICommandController.cs
@@ -30,5 +30,26 @@ public interface ICommandController
///
///
void GetHelp(string command, IIoContext output);
+ ///
+ /// install a single command into the index
+ ///
+ ///
+ void AddCommand(ICommandDescription command);
+ ///
+ /// add a command from a loaded type
+ /// good for commands from internal or linked dlls
+ ///
+ ///
+ ///
+ ///
+ void AddCommand(string packageKey, Type commandType, bool modifiesEnvironment = false);
+ ///
+ /// add a command from an instance of the command
+ /// good for commands from internal or linked dlls
+ ///
+ ///
+ ///
+ ///
+ void AddCommand(string packageKey, ICommandDelegate command, bool modifiesEnvironment = false);
}
}
\ No newline at end of file
diff --git a/src/Xcaciv.Command.Interface/Xcaciv.Command.Interface.csproj b/src/Xcaciv.Command.Interface/Xcaciv.Command.Interface.csproj
index 5df5ceb..0d0e9dc 100644
--- a/src/Xcaciv.Command.Interface/Xcaciv.Command.Interface.csproj
+++ b/src/Xcaciv.Command.Interface/Xcaciv.Command.Interface.csproj
@@ -3,7 +3,7 @@
net8.0
enable
enable
- 1.1.19
+ 1.1.21
True
Xcaciv.Command.Interface
Xcaciv.Command.Interface
diff --git a/src/Xcaciv.Command.Tests/CommandControllerTests.cs b/src/Xcaciv.Command.Tests/CommandControllerTests.cs
index ddf7dcb..fbbfff3 100644
--- a/src/Xcaciv.Command.Tests/CommandControllerTests.cs
+++ b/src/Xcaciv.Command.Tests/CommandControllerTests.cs
@@ -10,6 +10,7 @@
using Xcaciv.Command.FileLoader;
using System.IO.Abstractions.TestingHelpers;
using Moq;
+using Xcaciv.Command.Tests.TestImpementations;
namespace Xcaciv.Command.Tests
{
@@ -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()
@@ -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.
}
}
\ No newline at end of file
diff --git a/src/Xcaciv.Command.Tests/Commands/RegifCommandTests.cs b/src/Xcaciv.Command.Tests/Commands/RegifCommandTests.cs
index 830d42d..651a57a 100644
--- a/src/Xcaciv.Command.Tests/Commands/RegifCommandTests.cs
+++ b/src/Xcaciv.Command.Tests/Commands/RegifCommandTests.cs
@@ -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());
}
}
}
\ No newline at end of file
diff --git a/src/Xcaciv.Command.Tests/TestImpementations/CommandControllerTestHarness.cs b/src/Xcaciv.Command.Tests/TestImpementations/CommandControllerTestHarness.cs
new file mode 100644
index 0000000..5459acb
--- /dev/null
+++ b/src/Xcaciv.Command.Tests/TestImpementations/CommandControllerTestHarness.cs
@@ -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
+ {
+ ///
+ /// Command Manger
+ ///
+ public CommandControllerTestHarness() : base(new Crawler()) { }
+ ///
+ /// Command Manger
+ ///
+ public CommandControllerTestHarness(ICrawler crawler) : base(crawler) { }
+
+ ///
+ /// Command Manager constructor to specify restricted directory
+ ///
+ ///
+ public CommandControllerTestHarness(ICrawler crawler, string restrictedDirectory) : base(crawler, restrictedDirectory) { }
+ ///
+ /// Command Manager test constructor
+ ///
+ ///
+ public CommandControllerTestHarness(IVerfiedSourceDirectories packageBinearyDirectories) : base(packageBinearyDirectories) { }
+
+ internal Dictionary GetCommands()
+ {
+ return this.Commands;
+ }
+ }
+}
diff --git a/src/Xcaciv.Command.Tests/TestImpementations/TestTextIo.cs b/src/Xcaciv.Command.Tests/TestImpementations/TestTextIo.cs
index 2d0e3b1..2da2201 100644
--- a/src/Xcaciv.Command.Tests/TestImpementations/TestTextIo.cs
+++ b/src/Xcaciv.Command.Tests/TestImpementations/TestTextIo.cs
@@ -90,7 +90,7 @@ public override string ToString()
}
}
- output += string.Join('-', Output);
+ output += string.Join(Environment.NewLine, Output);
return output;
}
diff --git a/src/Xcaciv.Command/CommandController.cs b/src/Xcaciv.Command/CommandController.cs
index 18fd48e..08b7319 100644
--- a/src/Xcaciv.Command/CommandController.cs
+++ b/src/Xcaciv.Command/CommandController.cs
@@ -127,7 +127,7 @@ public void AddCommand(string packageKey, ICommandDelegate command, bool modifie
///
///
///
- 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)
{
@@ -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
});
@@ -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(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(fullTypeName);
}
else
{
@@ -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
@@ -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);
+ }
}
diff --git a/src/Xcaciv.Command/Xcaciv.Command.csproj b/src/Xcaciv.Command/Xcaciv.Command.csproj
index 2bf0f1a..a8abadc 100644
--- a/src/Xcaciv.Command/Xcaciv.Command.csproj
+++ b/src/Xcaciv.Command/Xcaciv.Command.csproj
@@ -2,7 +2,7 @@
enable
enable
- 1.4.19
+ 1.4.23
Xcaciv.Command
Xcaciv.Command
True
diff --git a/src/zTestCommandPackage/EchoDoCommand.cs b/src/zTestCommandPackage/DoEchoCommand.cs
similarity index 83%
rename from src/zTestCommandPackage/EchoDoCommand.cs
rename to src/zTestCommandPackage/DoEchoCommand.cs
index 0b8153b..f00f9e1 100644
--- a/src/zTestCommandPackage/EchoDoCommand.cs
+++ b/src/zTestCommandPackage/DoEchoCommand.cs
@@ -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)
{
diff --git a/src/zTestCommandPackage/DoSayCommand.cs b/src/zTestCommandPackage/DoSayCommand.cs
new file mode 100644
index 0000000..a263713
--- /dev/null
+++ b/src/zTestCommandPackage/DoSayCommand.cs
@@ -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);
+ }
+ }
+}