Skip to content

Commit f2d9092

Browse files
authoredMay 9, 2024··
Merge pull request #5 from Xcaciv/rc1
Rc1
2 parents 9feab36 + 44ddfc2 commit f2d9092

27 files changed

+639
-339
lines changed
 

‎README.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ Commands are .NET class libraries that contain implementations of the `Xc.Comman
1414

1515
## Roadmap
1616

17-
[X] Threaded piplineing
18-
[ ] Internal commands `SAY` and `REGIF`
19-
[ ] External commands `HEAD` and `SITECERT` (start of web util package)
20-
[ ] Binary package installer
21-
[ ] Source package installer
17+
- [X] Threaded piplineing
18+
- [ ] Internal commands `SAY` and `REGIF`
19+
- [ ] External commands `HEAD` and `SITECERT` (start of web util package)
20+
- [ ] Binary package installer
21+
- [ ] Cupcake shell platform
22+
- [ ] Source package installer
23+
- [ ] dotnet tool installer

‎Xcaciv.Command.sln

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xcaciv.Command.FileLoader",
1010
EndProject
1111
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xcaciv.Command.FileLoaderTests", "src\Xcaciv.Command.FileLoaderTests\Xcaciv.Command.FileLoaderTests.csproj", "{9B087B2C-5852-4B56-B213-727CB6048784}"
1212
EndProject
13-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xcaciv.CommandTests", "src\Xcaciv.CommandTests\Xcaciv.CommandTests.csproj", "{B517F2E2-DFEF-4C9E-9C5E-DBCF7873AAA9}"
14-
EndProject
1513
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6FEF8306-B2C7-489D-9ECC-23BCC8B53C41}"
1614
EndProject
1715
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B6212A8E-CCFE-492A-95E3-2AAD25970292}"
@@ -22,7 +20,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
2220
EndProject
2321
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zTestCommandPackage", "src\zTestCommandPackage\zTestCommandPackage.csproj", "{B062C312-8C65-45E6-AF49-0692502DA605}"
2422
EndProject
25-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zTestCommandPackage.Tests", "src\zTestCommandPackage.Tests\zTestCommandPackage.Tests.csproj", "{59AB8C54-097B-48E9-B42F-37E4252B81D9}"
23+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zTestCommandPackage.Tests", "src\zTestCommandPackage.Tests\zTestCommandPackage.Tests.csproj", "{59AB8C54-097B-48E9-B42F-37E4252B81D9}"
24+
EndProject
25+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xcaciv.Command.Tests", "src\Xcaciv.Command.Tests\Xcaciv.Command.Tests.csproj", "{1AA7D848-7F82-4190-8CB8-FDE32B74B0E7}"
2626
EndProject
2727
Global
2828
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -46,10 +46,6 @@ Global
4646
{9B087B2C-5852-4B56-B213-727CB6048784}.Debug|Any CPU.Build.0 = Debug|Any CPU
4747
{9B087B2C-5852-4B56-B213-727CB6048784}.Release|Any CPU.ActiveCfg = Release|Any CPU
4848
{9B087B2C-5852-4B56-B213-727CB6048784}.Release|Any CPU.Build.0 = Release|Any CPU
49-
{B517F2E2-DFEF-4C9E-9C5E-DBCF7873AAA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50-
{B517F2E2-DFEF-4C9E-9C5E-DBCF7873AAA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
51-
{B517F2E2-DFEF-4C9E-9C5E-DBCF7873AAA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
52-
{B517F2E2-DFEF-4C9E-9C5E-DBCF7873AAA9}.Release|Any CPU.Build.0 = Release|Any CPU
5349
{B062C312-8C65-45E6-AF49-0692502DA605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5450
{B062C312-8C65-45E6-AF49-0692502DA605}.Debug|Any CPU.Build.0 = Debug|Any CPU
5551
{B062C312-8C65-45E6-AF49-0692502DA605}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -58,15 +54,19 @@ Global
5854
{59AB8C54-097B-48E9-B42F-37E4252B81D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
5955
{59AB8C54-097B-48E9-B42F-37E4252B81D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
6056
{59AB8C54-097B-48E9-B42F-37E4252B81D9}.Release|Any CPU.Build.0 = Release|Any CPU
57+
{1AA7D848-7F82-4190-8CB8-FDE32B74B0E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58+
{1AA7D848-7F82-4190-8CB8-FDE32B74B0E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
59+
{1AA7D848-7F82-4190-8CB8-FDE32B74B0E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
60+
{1AA7D848-7F82-4190-8CB8-FDE32B74B0E7}.Release|Any CPU.Build.0 = Release|Any CPU
6161
EndGlobalSection
6262
GlobalSection(SolutionProperties) = preSolution
6363
HideSolutionNode = FALSE
6464
EndGlobalSection
6565
GlobalSection(NestedProjects) = preSolution
6666
{9B087B2C-5852-4B56-B213-727CB6048784} = {6FEF8306-B2C7-489D-9ECC-23BCC8B53C41}
67-
{B517F2E2-DFEF-4C9E-9C5E-DBCF7873AAA9} = {6FEF8306-B2C7-489D-9ECC-23BCC8B53C41}
6867
{B062C312-8C65-45E6-AF49-0692502DA605} = {6FEF8306-B2C7-489D-9ECC-23BCC8B53C41}
6968
{59AB8C54-097B-48E9-B42F-37E4252B81D9} = {6FEF8306-B2C7-489D-9ECC-23BCC8B53C41}
69+
{1AA7D848-7F82-4190-8CB8-FDE32B74B0E7} = {6FEF8306-B2C7-489D-9ECC-23BCC8B53C41}
7070
EndGlobalSection
7171
GlobalSection(ExtensibilityGlobals) = postSolution
7272
SolutionGuid = {2C5022E1-F2BA-4FD4-8841-D4B32BD6A7E8}

‎src/Directory.Packages.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<!-- Applicaiton Versions -->
66
<ItemGroup>
77
<PackageVersion Include="System.IO.Abstractions" Version="21.0.2" />
8-
<PackageVersion Include="Xcaciv.Loader" Version="1.0.2.11" />
8+
<PackageVersion Include="Xcaciv.Loader" Version="1.0.2.15" />
99
</ItemGroup>
1010
<!-- Unit Test Versions -->
1111
<ItemGroup>

‎src/Xcaciv.Command.FileLoader/Crawler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public IDictionary<string, PackageDescription> LoadPackageDescriptions(string ba
6262
var commands = new Dictionary<string, CommandDescription>();
6363
packagDesc.Version = context.GetVersion();
6464

65-
foreach (var command in context.GetAllInstances<ICommand>())
65+
foreach (var command in context.GetAllInstances<ICommandDelegate>())
6666
{
6767
commands[command.BaseCommand] = new CommandDescription()
6868
{

‎src/Xcaciv.Command.FileLoaderTests/Xcaciv.Command.FileLoaderTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<TargetFramework>net8.0</TargetFramework>
44
<Nullable>enable</Nullable>
55
<IsPackable>false</IsPackable>
6+
<IsTestProject>true</IsTestProject>
67
<AssemblyName>Xcaciv.Command.FileLoaderTests</AssemblyName>
78
<RootNamespace>Xcaciv.Command.FileLoaderTests</RootNamespace>
89
</PropertyGroup>

‎src/Xcaciv.Command.Interface/CommandDescription.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ public class CommandDescription
1515
/// regex for clensing command
1616
/// no exceptions
1717
/// </summary>
18-
public static Regex InvalidCommandChars = new Regex(@"[^-_\da-zA-Z ""]+");
18+
public static Regex InvalidCommandChars = new Regex(@"[^-_\da-zA-Z ]+");
19+
/// <summary>
20+
/// regex for clensing parameters
21+
/// no exceptions
22+
/// </summary>
23+
public static Regex InvalidParameterChars = new Regex(@"[^-_\da-zA-Z .*?\[\]|""~!@#$%^&*\(\)]+");
1924
/// <summary>
2025
/// text command
2126
/// </summary>

‎src/Xcaciv.Command.Interface/DeligateCommand.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Xcaciv.Command.Interface
88
{
9-
public class DeligateCommand : ICommand
9+
public class DeligateCommand : ICommandDelegate
1010
{
1111
public DeligateCommand(string command, Func<IInputContext, IAsyncEnumerable<string>> commandFunction)
1212
{
@@ -31,7 +31,7 @@ public ValueTask DisposeAsync()
3131
/// <param name="parameters"></param>
3232
/// <param name="messageContext"></param>
3333
/// <returns></returns>
34-
async IAsyncEnumerable<string> ICommand.Main(IInputContext input, IStatusContext statusContext)
34+
async IAsyncEnumerable<string> ICommandDelegate.Main(IInputContext input, IStatusContext statusContext)
3535
{
3636
if (this.commandFunction != null)
3737
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+

2+
namespace Xcaciv.Command.Interface
3+
{
4+
public interface ICommandController
5+
{
6+
void AddPackageDirectory(string directory);
7+
void LoadCommands(string subDirectory = "bin");
8+
Task Run(string commandLine, ITextIoContext output);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
7-
namespace Xcaciv.Command.Interface
8-
{
9-
/// <summary>
10-
/// interface for commands that can be issued from a shell
11-
/// </summary>
12-
public interface ICommand : IAsyncDisposable
13-
{
14-
/// <summary>
15-
/// unique typed command - alphanumeric with dash or underscore
16-
/// </summary>
17-
string BaseCommand { get; }
18-
/// <summary>
19-
/// Display name - may contain special characters
20-
/// </summary>
21-
string FriendlyName { get; }
22-
/// <summary>
23-
/// primary command execution method
24-
/// </summary>
25-
/// <param name="parameters"></param>
26-
/// <param name="messageContext">used for progress and status messages</param>
27-
/// <returns></returns>
28-
IAsyncEnumerable<string> Main(IInputContext input, IStatusContext statusContext);
29-
/// <summary>
30-
/// output usage instructions via message context
31-
/// </summary>
32-
/// <param name="messageContext"></param>
33-
/// <returns></returns>
34-
Task Help(ITextIoContext messageContext);
35-
}
36-
}
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Xcaciv.Command.Interface
8+
{
9+
/// <summary>
10+
/// interface for commands that can be issued from a shell
11+
/// </summary>
12+
public interface ICommandDelegate : IAsyncDisposable
13+
{
14+
/// <summary>
15+
/// unique typed command - alphanumeric with dash or underscore
16+
/// </summary>
17+
string BaseCommand { get; }
18+
/// <summary>
19+
/// Display name - may contain special characters
20+
/// </summary>
21+
string FriendlyName { get; }
22+
/// <summary>
23+
/// primary command execution method
24+
/// </summary>
25+
/// <param name="parameters"></param>
26+
/// <param name="messageContext">used for progress and status messages</param>
27+
/// <returns></returns>
28+
IAsyncEnumerable<string> Main(IInputContext input, IStatusContext statusContext);
29+
/// <summary>
30+
/// output usage instructions via message context
31+
/// </summary>
32+
/// <param name="messageContext"></param>
33+
/// <returns></returns>
34+
Task Help(ITextIoContext messageContext);
35+
}
36+
}
3737

‎src/Xcaciv.Command.Interface/ICommandFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ public interface ICommandFactory
1515
/// new up a instance of a particular command and box it
1616
/// </summary>
1717
/// <returns></returns>
18-
ICommand CreateCommandInstance();
18+
ICommandDelegate CreateCommandInstance();
1919
}
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,60 @@
1-
using Xunit;
2-
using Xcaciv.Command;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
8-
using Xunit.Abstractions;
9-
using System.IO.Abstractions;
10-
using Xcaciv.Command.FileLoader;
11-
12-
namespace Xcaciv.CommandTests
13-
{
14-
public class CommandControllerTests
15-
{
16-
private ITestOutputHelper _testOutput;
17-
private string commandPackageDir = @"..\..\..\..\zTestCommandPackage\bin\{1}\";
18-
public CommandControllerTests(ITestOutputHelper output)
19-
{
20-
_testOutput = output;
21-
#if DEBUG
22-
_testOutput.WriteLine("Tests in Debug mode");
23-
commandPackageDir = commandPackageDir.Replace("{1}", "Debug");
24-
#else
25-
this._testOutput.WriteLine("Tests in Release mode??");
26-
this.commandPackageDir = commandPackageDir.Replace("{1}", "Release");
27-
#endif
28-
}
29-
[Fact()]
30-
public async Task RunCommandsTestAsync()
31-
{
32-
var commands = new CommandController(new Crawler(), @"..\..\..\..\..\");
33-
commands.AddPackageDirectory(commandPackageDir);
34-
35-
commands.LoadCommands(string.Empty);
36-
var textio = new TestImpementations.TestTextIo();
37-
// simulate user input
38-
await commands.Run("echo what is up", textio);
39-
40-
// verify the output of the first run
41-
// by looking at the output of the second output line
42-
Assert.Equal("> what", textio.Children.First().Output[1]);
43-
}
44-
[Fact()]
45-
public async Task PipeCommandsTestAsync()
46-
{
47-
var commands = new CommandController(new Crawler(), @"..\..\..\..\..\");
48-
commands.AddPackageDirectory(commandPackageDir);
49-
50-
commands.LoadCommands(string.Empty);
51-
var textio = new TestImpementations.TestTextIo();
52-
// simulate user input
53-
await commands.Run("echo what is up | echo2 | echoe ", textio);
54-
55-
// verify the output of the first run
56-
// by looking at the output of the second output line
57-
Assert.Equal("> :d2hhdC13aGF0:-:d2hhdC13aGF0:-> :aXMtaXM=:-:aXMtaXM=:-> :dXAtdXA=:-:dXAtdXA=:", textio.ToString());
58-
}
59-
}
1+
using Xunit;
2+
using Xcaciv.Command;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Xunit.Abstractions;
9+
using System.IO.Abstractions;
10+
using Xcaciv.Command.FileLoader;
11+
12+
namespace Xcaciv.Command.Tests
13+
{
14+
public class CommandControllerTests
15+
{
16+
private ITestOutputHelper _testOutput;
17+
private string commandPackageDir = @"..\..\..\..\zTestCommandPackage\bin\{1}\";
18+
public CommandControllerTests(ITestOutputHelper output)
19+
{
20+
_testOutput = output;
21+
#if DEBUG
22+
_testOutput.WriteLine("Tests in Debug mode");
23+
commandPackageDir = commandPackageDir.Replace("{1}", "Debug");
24+
#else
25+
this._testOutput.WriteLine("Tests in Release mode??");
26+
this.commandPackageDir = commandPackageDir.Replace("{1}", "Release");
27+
#endif
28+
}
29+
[Fact()]
30+
public async Task RunCommandsTestAsync()
31+
{
32+
var commands = new CommandController(new Crawler(), @"..\..\..\..\..\");
33+
commands.AddPackageDirectory(commandPackageDir);
34+
35+
commands.LoadCommands(string.Empty);
36+
var textio = new TestImpementations.TestTextIo();
37+
// simulate user input
38+
await commands.Run("echo what is up", textio);
39+
40+
// verify the output of the first run
41+
// by looking at the output of the second output line
42+
Assert.Equal("> what", textio.Children.First().Output[1]);
43+
}
44+
[Fact()]
45+
public async Task PipeCommandsTestAsync()
46+
{
47+
var commands = new CommandController(new Crawler(), @"..\..\..\..\..\");
48+
commands.AddPackageDirectory(commandPackageDir);
49+
50+
commands.LoadCommands(string.Empty);
51+
var textio = new TestImpementations.TestTextIo();
52+
// simulate user input
53+
await commands.Run("echo what is up | echo2 | echoe ", textio);
54+
55+
// verify the output of the first run
56+
// by looking at the output of the second output line
57+
Assert.Equal("> :d2hhdC13aGF0:-:d2hhdC13aGF0:-> :aXMtaXM=:-:aXMtaXM=:-> :dXAtdXA=:-:dXAtdXA=:", textio.ToString());
58+
}
59+
}
6060
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Xunit;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Xcaciv.Command.FileLoader;
8+
using Xunit.Abstractions;
9+
10+
namespace Xcaciv.Command.Tests.Commands
11+
{
12+
public class RegifCommandTests
13+
{
14+
private ITestOutputHelper _testOutput;
15+
private string commandPackageDir = @"..\..\..\..\zTestCommandPackage\bin\{1}\";
16+
public RegifCommandTests(ITestOutputHelper output)
17+
{
18+
_testOutput = output;
19+
#if DEBUG
20+
_testOutput.WriteLine("Tests in Debug mode");
21+
commandPackageDir = commandPackageDir.Replace("{1}", "Debug");
22+
#else
23+
this._testOutput.WriteLine("Tests in Release mode??");
24+
this.commandPackageDir = commandPackageDir.Replace("{1}", "Release");
25+
#endif
26+
}
27+
28+
[Fact()]
29+
public async Task HandleExecutionTestAsync()
30+
{
31+
var commands = new CommandController(new Crawler(), @"..\..\..\..\..\");
32+
commands.AddPackageDirectory(commandPackageDir);
33+
commands.LoadDefaultCommands();
34+
commands.LoadCommands(string.Empty);
35+
36+
37+
var textio = new TestImpementations.TestTextIo();
38+
// simulate user input
39+
await commands.Run("echo what is up | regif is", textio);
40+
41+
// verify the output of the first run
42+
// by looking at the output of the second output line
43+
Assert.Equal("> --> is-is-> -", textio.ToString());
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Xunit;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Xcaciv.Command.FileLoader;
8+
9+
namespace Xcaciv.Command.Tests.Commands
10+
{
11+
public class SayCommandTests
12+
{
13+
14+
[Fact()]
15+
public async Task HandleExecutionTest()
16+
{
17+
var commands = new CommandController(new Crawler(), "");
18+
commands.LoadDefaultCommands();
19+
20+
var textio = new TestImpementations.TestTextIo();
21+
// simulate user input
22+
await commands.Run("say what is up", textio);
23+
24+
// verify the output of the first run
25+
// by looking at the output of the second output line
26+
Assert.Equal("> what is up", textio.Children.First().Output[1]);
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,70 @@
1-
using Xunit;
2-
using Xcaciv.Command;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
8-
9-
namespace Xcaciv.CommandTests
10-
{
11-
public class ManagerTests
12-
{
13-
[Fact()]
14-
public void GetCommandTest()
15-
{
16-
var expected = "DIR";
17-
var commandLine = $"{expected} - some options here";
18-
var manager = new CommandController();
19-
20-
var actual = CommandController.GetCommand(commandLine);
21-
22-
Assert.Equal(expected, actual);
23-
}
24-
25-
[Fact()]
26-
public void PrepareArgsTest()
27-
{
28-
var command = "DIR";
29-
var expected = new[] { "-some", "options", "here" };
30-
var commandLine = $"{command} " + String.Join(' ', expected);
31-
var manager = new CommandController();
32-
33-
var actual = CommandController.PrepareArgs(commandLine);
34-
Assert.Equal(expected, actual);
35-
}
36-
37-
[Fact()]
38-
public void GetCommand_HostileInput_Filters()
39-
{
40-
var expected = "DIR";
41-
var commandLine = $"{expected}*'`%^! -some options here";
42-
43-
var actual = CommandController.GetCommand(commandLine);
44-
45-
Assert.Equal(expected, actual);
46-
}
47-
48-
[Fact()]
49-
public void PrepareArgs_HostileInput_Filters()
50-
{
51-
var command = "DIR";
52-
var expected = new[] { "-some", "options", "here" };
53-
var commandLine = $"{command} *'`%^!" + String.Join(' ', expected);
54-
55-
var actual = CommandController.PrepareArgs(commandLine);
56-
Assert.Equal(expected, actual);
57-
}
58-
59-
[Fact()]
60-
public void PrepareArgs_Quotes_Groups()
61-
{
62-
var command = "DIR";
63-
var expected = new[] { "-some", "\"two word\"", "and_three_word", "options" };
64-
var commandLine = $"{command} " + String.Join(' ', expected);
65-
66-
var actual = CommandController.PrepareArgs(commandLine);
67-
Assert.Equal(expected, actual);
68-
}
69-
}
1+
using Xunit;
2+
using Xcaciv.Command;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace Xcaciv.Command.Tests
10+
{
11+
public class ManagerTests
12+
{
13+
[Fact()]
14+
public void GetCommandTest()
15+
{
16+
var expected = "DIR";
17+
var commandLine = $"{expected} - some options here";
18+
var manager = new CommandController();
19+
20+
var actual = CommandController.GetCommand(commandLine);
21+
22+
Assert.Equal(expected, actual);
23+
}
24+
25+
[Fact()]
26+
public void PrepareArgsTest()
27+
{
28+
var command = "DIR";
29+
var expected = new[] { "-some", "options", "here" };
30+
var commandLine = $"{command} " + string.Join(' ', expected);
31+
var manager = new CommandController();
32+
33+
var actual = CommandController.PrepareArgs(commandLine);
34+
Assert.Equal(expected, actual);
35+
}
36+
37+
[Fact()]
38+
public void GetCommand_HostileInput_Filters()
39+
{
40+
var expected = "DIR";
41+
var commandLine = $"{expected}*'`%^! -some options here";
42+
43+
var actual = CommandController.GetCommand(commandLine);
44+
45+
Assert.Equal(expected, actual);
46+
}
47+
48+
[Fact()]
49+
public void PrepareArgs_HostileInput_Filters()
50+
{
51+
var command = "DIR";
52+
var expected = new[] { "-some", "options", "here" };
53+
var commandLine = $"{command} *'`%^!" + string.Join(' ', expected);
54+
55+
var actual = CommandController.PrepareArgs(commandLine);
56+
Assert.Equal(expected, actual);
57+
}
58+
59+
[Fact()]
60+
public void PrepareArgs_Quotes_Groups()
61+
{
62+
var command = "DIR";
63+
var expected = new[] { "-some", @"two word", "and_three_word", "options" , ".*? [0-9|a-z] ~!@#$%^&*()" };
64+
var commandLine = $@"{command} """ + string.Join(@""" """, expected) + '"';
65+
66+
var actual = CommandController.PrepareArgs(commandLine);
67+
Assert.Equal(expected, actual);
68+
}
69+
}
7070
}
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,99 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Reflection.Metadata.Ecma335;
5-
using System.Reflection.PortableExecutable;
6-
using System.Text;
7-
using System.Threading.Channels;
8-
using System.Threading.Tasks;
9-
using Xcaciv.Command;
10-
using Xcaciv.Command.Interface;
11-
12-
namespace Xcaciv.CommandTests.TestImpementations
13-
{
14-
public class TestTextIo : AbstractTextIo
15-
{
16-
/// <summary>
17-
/// test collection of children to verify commmand behavior
18-
/// </summary>
19-
public List<TestTextIo> Children { get; private set; } = new List<TestTextIo>();
20-
21-
/// <summary>
22-
/// test collection of output chunks to verify behavior
23-
/// </summary>
24-
public List<string> Output { get; private set; } = new List<string>();
25-
26-
public Dictionary<string, string> PromptAnswers { get; private set; } = new Dictionary<string, string>();
27-
28-
public TestTextIo(string[]? arguments = null) : base("TestTextIo", null)
29-
{
30-
this.Parameters = arguments ?? string.Empty.Split(' ');
31-
}
32-
33-
public override Task<ITextIoContext> GetChild(string[]? childArguments = null)
34-
{
35-
var child = new TestTextIo(childArguments)
36-
{
37-
Parent = this.Id
38-
};
39-
this.Children.Add(child);
40-
return Task.FromResult<ITextIoContext>(child);
41-
}
42-
43-
public override Task<string> PromptForCommand(string prompt)
44-
{
45-
this.Output.Add($"PROMPT> {prompt}");
46-
47-
var answer = PromptAnswers.ContainsKey(prompt) ?
48-
PromptAnswers[prompt] :
49-
prompt;
50-
51-
return Task.FromResult(answer);
52-
}
53-
54-
public override Task<int> SetProgress(int total, int step)
55-
{
56-
return Task.FromResult(step);
57-
}
58-
59-
public override Task SetStatusMessage(string message)
60-
{
61-
this.Output.Add(message);
62-
return Task.CompletedTask;
63-
}
64-
65-
public override Task OutputChunk(string message)
66-
{
67-
this.Output.Add("> " + message);
68-
return base.OutputChunk(message);
69-
}
70-
71-
public override Task HandleOutputChunk(string chunk)
72-
{
73-
this.Output.Add(chunk);
74-
return Task.CompletedTask;
75-
}
76-
77-
public override string ToString()
78-
{
79-
string output = string.Empty;
80-
if (this.HasPipedInput)
81-
{
82-
// combine output into one string seperated by new lines
83-
// and then add the children output
84-
output = String.Join(Environment.NewLine, this.Output);
85-
foreach (var chidl in this.Children)
86-
{
87-
output += chidl.ToString() + Environment.NewLine;
88-
}
89-
}
90-
91-
92-
93-
output += String.Join('-', this.Output);
94-
95-
return output;
96-
}
97-
98-
}
99-
}
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection.Metadata.Ecma335;
5+
using System.Reflection.PortableExecutable;
6+
using System.Text;
7+
using System.Threading.Channels;
8+
using System.Threading.Tasks;
9+
using Xcaciv.Command;
10+
using Xcaciv.Command.Interface;
11+
12+
namespace Xcaciv.Command.Tests.TestImpementations
13+
{
14+
public class TestTextIo : AbstractTextIo
15+
{
16+
/// <summary>
17+
/// test collection of children to verify commmand behavior
18+
/// </summary>
19+
public List<TestTextIo> Children { get; private set; } = new List<TestTextIo>();
20+
21+
/// <summary>
22+
/// test collection of output chunks to verify behavior
23+
/// </summary>
24+
public List<string> Output { get; private set; } = new List<string>();
25+
26+
public Dictionary<string, string> PromptAnswers { get; private set; } = new Dictionary<string, string>();
27+
28+
public TestTextIo(string[]? arguments = null) : base("TestTextIo", null)
29+
{
30+
Parameters = arguments ?? string.Empty.Split(' ');
31+
}
32+
33+
public override Task<ITextIoContext> GetChild(string[]? childArguments = null)
34+
{
35+
var child = new TestTextIo(childArguments)
36+
{
37+
Parent = Id
38+
};
39+
Children.Add(child);
40+
return Task.FromResult<ITextIoContext>(child);
41+
}
42+
43+
public override Task<string> PromptForCommand(string prompt)
44+
{
45+
Output.Add($"PROMPT> {prompt}");
46+
47+
var answer = PromptAnswers.ContainsKey(prompt) ?
48+
PromptAnswers[prompt] :
49+
prompt;
50+
51+
return Task.FromResult(answer);
52+
}
53+
54+
public override Task<int> SetProgress(int total, int step)
55+
{
56+
return Task.FromResult(step);
57+
}
58+
59+
public override Task SetStatusMessage(string message)
60+
{
61+
Output.Add(message);
62+
return Task.CompletedTask;
63+
}
64+
65+
public override Task OutputChunk(string message)
66+
{
67+
Output.Add("> " + message);
68+
return base.OutputChunk(message);
69+
}
70+
71+
public override Task HandleOutputChunk(string chunk)
72+
{
73+
Output.Add(chunk);
74+
return Task.CompletedTask;
75+
}
76+
77+
public override string ToString()
78+
{
79+
string output = string.Empty;
80+
if (HasPipedInput)
81+
{
82+
// combine output into one string seperated by new lines
83+
// and then add the children output
84+
output = string.Join(Environment.NewLine, Output);
85+
foreach (var chidl in Children)
86+
{
87+
output += chidl.ToString() + Environment.NewLine;
88+
}
89+
}
90+
91+
92+
93+
output += string.Join('-', Output);
94+
95+
return output;
96+
}
97+
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<IsPackable>false</IsPackable>
7+
<IsTestProject>true</IsTestProject>
8+
<AssemblyName>Xcaciv.Command.Tests</AssemblyName>
9+
<RootNamespace>Xcaciv.Command.Tests</RootNamespace>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
14+
<PackageReference Include="coverlet.collector">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
18+
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
19+
<PackageReference Include="xunit" />
20+
<PackageReference Include="xunit.runner.visualstudio">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<ProjectReference Include="..\Xcaciv.Command.FileLoader\Xcaciv.Command.FileLoader.csproj" />
28+
<ProjectReference Include="..\Xcaciv.Command.Interface\Xcaciv.Command.Interface.csproj" />
29+
<ProjectReference Include="..\Xcaciv.Command\Xcaciv.Command.csproj" />
30+
</ItemGroup>
31+
32+
33+
</Project>

‎src/Xcaciv.Command/CommandController.cs

+58-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
4+
using System.Data;
5+
using System.Diagnostics;
36
using System.IO.Abstractions;
47
using System.Linq;
58
using System.Text;
@@ -74,7 +77,6 @@ public void LoadCommands(string subDirectory = "bin")
7477
{
7578
if (this.PackageBinaryDirectories.Directories.Count == 0) throw new Exceptions.NoPluginsFoundException("No base package directory configured. (Did you set the restricted directory?)");
7679

77-
this.Commands.Clear();
7880
foreach (var directory in this.PackageBinaryDirectories.Directories)
7981
{
8082
foreach (var command in Crawler.LoadPackageDescriptions(directory, subDirectory).SelectMany(o => o.Value.Commands))
@@ -83,6 +85,40 @@ public void LoadCommands(string subDirectory = "bin")
8385
}
8486
}
8587
}
88+
89+
public async void LoadDefaultCommands()
90+
{
91+
// This is the package action
92+
var key = "Default";
93+
var packagDescription = new PackageDescription()
94+
{
95+
Name = key,
96+
FullPath = "",
97+
};
98+
99+
foreach (var command in AssemblyContext.GetLoadedTypes<ICommandDelegate>())
100+
{
101+
try
102+
{
103+
await using (var commandInstance = AssemblyContext.GetInstance<ICommandDelegate>(command))
104+
{
105+
var description = new CommandDescription()
106+
{
107+
BaseCommand = commandInstance.BaseCommand,
108+
FullTypeName = command.FullName ?? String.Empty,
109+
PackageDescription = packagDescription
110+
};
111+
AddCommand(description);
112+
}
113+
}
114+
catch (Exception e)
115+
{
116+
Debug.WriteLine($"Exception loading {command.FullName}");
117+
Debug.WriteLine(e);
118+
}
119+
}
120+
121+
}
86122
/// <summary>
87123
/// install a single command into the index
88124
/// </summary>
@@ -163,14 +199,20 @@ private async Task ExecuteCommand(string commandKey, ITextIoContext ioContext)
163199
try
164200
{
165201
var commandDiscription = this.Commands[commandKey];
166-
using (var context = AssemblyContext.LoadFromPath(commandDiscription.PackageDescription.FullPath))
202+
var executeDeligate = Type.GetType(commandDiscription.FullTypeName);
203+
if (executeDeligate == null)
167204
{
168-
var commandInstance = context.GetInstance<ICommand>(commandDiscription.FullTypeName);
169-
await foreach(var resultMessage in commandInstance.Main(ioContext, ioContext))
205+
using (var context = AssemblyContext.LoadFromPath(commandDiscription.PackageDescription.FullPath))
170206
{
171-
await ioContext.OutputChunk(resultMessage);
207+
var commandInstance = context.GetInstance<ICommandDelegate>(commandDiscription.FullTypeName);
208+
await ExecuteCommand(ioContext, commandInstance);
172209
}
173210
}
211+
else
212+
{
213+
var commandInstance = AssemblyContext.GetInstance<ICommandDelegate>(executeDeligate);
214+
await ExecuteCommand(ioContext, commandInstance);
215+
}
174216
}
175217
catch (Exception ex)
176218
{
@@ -181,6 +223,15 @@ private async Task ExecuteCommand(string commandKey, ITextIoContext ioContext)
181223
await ioContext.Complete($"ExecuteCommand: {commandKey} Done.");
182224
}
183225
}
226+
227+
private static async Task ExecuteCommand(ITextIoContext ioContext, ICommandDelegate commandInstance)
228+
{
229+
await foreach (var resultMessage in commandInstance.Main(ioContext, ioContext))
230+
{
231+
await ioContext.OutputChunk(resultMessage);
232+
}
233+
}
234+
184235
/// <summary>
185236
/// parse primary command from a command line
186237
/// </summary>
@@ -203,9 +254,9 @@ public static string GetCommand(string commandLine)
203254
/// <returns></returns>
204255
public static string[] PrepareArgs(string commandLine)
205256
{
206-
var args = System.Text.RegularExpressions.Regex.Matches(commandLine, @"[\""].+?[\""]|[\w-]+")
257+
var args = System.Text.RegularExpressions.Regex.Matches(commandLine, @"[\""].*?[\""]|[\w-]+")
207258
.Cast<System.Text.RegularExpressions.Match>()
208-
.Select(o => CommandDescription.InvalidCommandChars.Replace(o.Value, ""))
259+
.Select(o => CommandDescription.InvalidParameterChars.Replace(o.Value, "").Trim('"'))
209260
.ToArray();
210261

211262
// the first item in the array is the command
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xcaciv.Command.Interface;
7+
8+
namespace Xcaciv.Command.Commands
9+
{
10+
public abstract class AbstractCommand : Xcaciv.Command.Interface.ICommandDelegate
11+
{
12+
public abstract string BaseCommand { get; }
13+
14+
public abstract string FriendlyName { get; }
15+
16+
public abstract string HelpString { get; }
17+
18+
/// <summary>
19+
/// this should be overwritten to dispose of any unmanaged items
20+
/// </summary>
21+
/// <returns></returns>
22+
public virtual ValueTask DisposeAsync()
23+
{
24+
return ValueTask.CompletedTask;
25+
}
26+
27+
public virtual Task Help(ITextIoContext messageContext)
28+
{
29+
return Task.FromResult(HelpString);
30+
}
31+
32+
public async IAsyncEnumerable<string> Main(IInputContext input, IStatusContext statusContext)
33+
{
34+
await statusContext.SetStatusMessage($"...");
35+
if (input.HasPipedInput)
36+
{
37+
await foreach (var p in input.ReadInputPipeChunks())
38+
{
39+
if (string.IsNullOrEmpty(p)) continue;
40+
yield return this.HandlePipedChunk(p, input.Parameters, statusContext);
41+
}
42+
}
43+
else
44+
{
45+
yield return HandleExecution(input.Parameters, statusContext);
46+
}
47+
await statusContext.SetStatusMessage($"done");
48+
}
49+
50+
public abstract string HandlePipedChunk(string pipedChunk, string[] parameters, IStatusContext status);
51+
52+
public abstract string HandleExecution(string[] parameters, IStatusContext status);
53+
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Text.RegularExpressions;
6+
using System.Threading.Tasks;
7+
using Xcaciv.Command.Interface;
8+
9+
namespace Xcaciv.Command.Commands
10+
{
11+
public class RegifCommand : AbstractCommand
12+
{
13+
public override string BaseCommand { get; } = "REGIF";
14+
15+
16+
public override string FriendlyName { get; } = "regular expression filter";
17+
18+
19+
public override string HelpString { get; } = "<some command> | regif '<regex expression>'";
20+
/// <summary>
21+
/// regex object for reuse
22+
/// </summary>
23+
protected Regex? expression { get; set; } = null;
24+
25+
protected string regex { get; set; } = string.Empty;
26+
27+
public override string HandleExecution(string[] parameters, IStatusContext status)
28+
{
29+
var output = new StringBuilder();
30+
setRegexExpression(parameters);
31+
foreach (var stringToCheck in parameters.Skip(1))
32+
{
33+
if (this.expression?.IsMatch(stringToCheck) ?? false)
34+
{
35+
output.Append(" ");
36+
output.Append(parameters[1]);
37+
}
38+
}
39+
return output.ToString().Trim();
40+
}
41+
42+
public override string HandlePipedChunk(string stringToCheck, string[] parameters, IStatusContext status)
43+
{
44+
if (parameters.Length > 0)
45+
{
46+
setRegexExpression(parameters);
47+
}
48+
49+
return (this.expression?.IsMatch(stringToCheck) ?? false) ? stringToCheck : string.Empty;
50+
}
51+
/// <summary>
52+
/// setup the regex object
53+
/// </summary>
54+
/// <param name="parameters"></param>
55+
private void setRegexExpression(string[] parameters)
56+
{
57+
if (this.expression == null && parameters.Length >0)
58+
{
59+
this.expression = new Regex(parameters[0]);
60+
}
61+
}
62+
63+
64+
}
65+
}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xcaciv.Command.Interface;
7+
8+
namespace Xcaciv.Command.Commands
9+
{
10+
public class SayCommand : AbstractCommand
11+
{
12+
public override string BaseCommand { get; } = "SAY";
13+
14+
15+
public override string FriendlyName { get; } = "say something";
16+
17+
18+
public override string HelpString { get; } = "SAY <thing to print>";
19+
20+
public override string HandleExecution(string[] parameters, IStatusContext status)
21+
{
22+
return String.Join(" ", parameters);
23+
}
24+
25+
public override string HandlePipedChunk(string pipedChunk, string[] parameters, IStatusContext status)
26+
{
27+
return pipedChunk;
28+
}
29+
30+
}
31+
}

‎src/Xcaciv.Command/DeligateCommand.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Xcaciv.Command
99
{
10-
public class DeligateCommand : ICommand
10+
public class DeligateCommand : ICommandDelegate
1111
{
1212
public DeligateCommand(string command, Func<IInputContext, IAsyncEnumerable<string>> commandFunction)
1313
{
@@ -27,7 +27,7 @@ public ValueTask DisposeAsync()
2727

2828
public string FriendlyName => BaseCommand;
2929

30-
async IAsyncEnumerable<string> ICommand.Main(IInputContext input, IStatusContext statusContext)
30+
async IAsyncEnumerable<string> ICommandDelegate.Main(IInputContext input, IStatusContext statusContext)
3131
{
3232
if (commandFunction != null)
3333
{

‎src/Xcaciv.Command/Xcaciv.Command.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<ImplicitUsings>enable</ImplicitUsings>
44
<Nullable>enable</Nullable>
5-
<Version>1.2.1</Version>
5+
<Version>1.3.1</Version>
66
<AssemblyName>Xcaciv.Command</AssemblyName>
77
<RootNamespace>Xcaciv.Command</RootNamespace>
88
<IsPublishable>True</IsPublishable>

‎src/Xcaciv.CommandTests/Xcaciv.CommandTests.csproj

-26
This file was deleted.

‎src/zTestCommandPackage.Tests/zTestCommandPackage.Tests.csproj

+15-17
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,30 @@
22

33
<PropertyGroup>
44
<TargetFramework>net8.0</TargetFramework>
5-
<ImplicitUsings>enable</ImplicitUsings>
65
<Nullable>enable</Nullable>
7-
86
<IsPackable>false</IsPackable>
97
<IsTestProject>true</IsTestProject>
8+
<AssemblyName>zTestCommandPackage.Tests</AssemblyName>
9+
<RootNamespace>zTestCommandPackage.Tests</RootNamespace>
1010
</PropertyGroup>
1111

12-
<ItemGroup>
13-
<PackageReference Include="coverlet.collector" />
14-
<PackageReference Include="Microsoft.NET.Test.Sdk" />
15-
<PackageReference Include="MSTest.TestAdapter" />
16-
<PackageReference Include="MSTest.TestFramework" />
17-
<PackageReference Include="xunit" />
18-
<PackageReference Include="xunit.runner.visualstudio">
19-
<PrivateAssets>all</PrivateAssets>
20-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21-
</PackageReference>
22-
</ItemGroup>
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
14+
<PackageReference Include="coverlet.collector">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
18+
<PackageReference Include="System.IO.Abstractions.TestingHelpers" />
19+
<PackageReference Include="xunit" />
20+
<PackageReference Include="xunit.runner.visualstudio">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
24+
</ItemGroup>
2325

2426
<ItemGroup>
2527
<ProjectReference Include="..\Xcaciv.Command\Xcaciv.Command.csproj" />
2628
<ProjectReference Include="..\zTestCommandPackage\zTestCommandPackage.csproj" />
2729
</ItemGroup>
2830

29-
<ItemGroup>
30-
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
31-
</ItemGroup>
32-
3331
</Project>

‎src/zTestCommandPackage/EchoChamberCommand.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace zTestCommandPackage
1010
{
11-
public class EchoChamberCommand : EchoCommand, ICommand
11+
public class EchoChamberCommand : EchoCommand, ICommandDelegate
1212
{
1313
public EchoChamberCommand()
1414
{

‎src/zTestCommandPackage/EchoCommand.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace zTestCommandPackage
1010
{
11-
public class EchoCommand : ICommand
11+
public class EchoCommand : ICommandDelegate
1212
{
1313
public string BaseCommand { get; protected set; } = "ECHO";
1414

‎src/zTestCommandPackage/EchoEncodeCommand.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
 using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection.Metadata.Ecma335;
@@ -8,7 +8,7 @@
88

99
namespace zTestCommandPackage
1010
{
11-
public class EchoEncodeCommand : EchoCommand, ICommand
11+
public class EchoEncodeCommand : EchoCommand, ICommandDelegate
1212
{
1313
public EchoEncodeCommand()
1414
{

0 commit comments

Comments
 (0)
Please sign in to comment.