diff --git a/src/console/CommandTree/CommandTreeExtensions.cs b/src/console/CommandTree/CommandTreeExtensions.cs new file mode 100644 index 0000000..66f6082 --- /dev/null +++ b/src/console/CommandTree/CommandTreeExtensions.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Shamir.Console +{ + public static class CommandTreeExtensions + { + public static ICommand FindCommand(this ICommandTree tree, ReadOnlySpan args) + { + if (tree is null) + { + throw new ArgumentNullException(nameof(tree)); + } + + return tree.FindCommand(ImmutableStack.Empty, args); + } + + public static string BuildHelpText(IImmutableStack path) + { + var sb = new StringBuilder(); + sb.AppendLine("Group"); + sb.Append(" "); + + var pathNodes = path.ToArray(); + for (var i = pathNodes.Length - 1; i > 0; i--) + { + sb.Append(pathNodes[i].Name); + sb.Append(' '); + } + + var tree = pathNodes[0]; + sb.Append(tree.Name); + sb.Append(" : "); + sb.AppendLine(tree.Description); + sb.AppendLine(); + + if (!tree.SubTrees.IsEmpty) + { + sb.AppendLine("Subgroups:"); + + var maxSpacing = tree.SubTrees.Max(c => c.Name.Length) + 1; + + foreach (var child in tree.SubTrees) + { + sb.Append(" "); + sb.Append(child.Name); + for (var i = 0; i < maxSpacing - child.Name.Length; i++) + { + sb.Append(' '); + } + sb.Append(": "); + sb.AppendLine(child.Description); + } + + sb.AppendLine(); + } + + if (!tree.Commands.IsEmpty) + { + sb.AppendLine("Commands:"); + + var maxSpacing = tree.Commands.Max(c => c.Name.Length) + 1; + + foreach (var command in tree.Commands) + { + sb.Append(" "); + sb.Append(command.Name); + for (var i = 0; i < maxSpacing - command.Name.Length; i++) + { + sb.Append(' '); + } + sb.Append(": "); + sb.AppendLine(command.Description); + } + } + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/console/CommandTree/DefaultCommandTree.cs b/src/console/CommandTree/DefaultCommandTree.cs new file mode 100644 index 0000000..0eeeaf5 --- /dev/null +++ b/src/console/CommandTree/DefaultCommandTree.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Immutable; + +namespace Shamir.Console +{ + public sealed class DefaultCommandTree : ICommandTree + { + public DefaultCommandTree(string name, string description, ImmutableArray subtrees, ImmutableArray commands) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + this.SubTrees = subtrees; + this.Commands = commands; + } + + public string Name { get; } + public string Description { get; } + public ImmutableArray SubTrees { get; } + public ImmutableArray Commands { get; } + + public ICommand FindCommand(IImmutableStack path, ReadOnlySpan args) + { + if (args.Length > 0) + { + foreach (var command in Commands) + { + if (command.Name == args[0]) + { + return command; + } + } + + foreach (var tree in SubTrees) + { + if (tree.Name == args[0]) + { + return tree.FindCommand(path.Push(this), args[1..]); + } + } + } + + return new DefaultHelpTextCommand(path.Push(this)); + } + } +} \ No newline at end of file diff --git a/src/console/CommandTree/DefaultHelpTextCommand.cs b/src/console/CommandTree/DefaultHelpTextCommand.cs new file mode 100644 index 0000000..bd078fe --- /dev/null +++ b/src/console/CommandTree/DefaultHelpTextCommand.cs @@ -0,0 +1,26 @@ +using System.Collections.Immutable; +using System.Threading.Tasks; + +namespace Shamir.Console +{ + public sealed class DefaultHelpTextCommand : ICommand + { + public DefaultHelpTextCommand(IImmutableStack path) + { + this.Path = path; + } + + public string Name => "help"; + public string Description => "Print help text"; + + public IImmutableStack Path { get; } + + public ValueTask ExecuteAsync() + { + System.Console.Error.WriteLine(GetHelpText()); + return ValueTask.FromResult(1); // TODO: const + } + + public string GetHelpText() => CommandTreeExtensions.BuildHelpText(Path); + } +} \ No newline at end of file diff --git a/src/console/CommandTree/ICommand.cs b/src/console/CommandTree/ICommand.cs new file mode 100644 index 0000000..308b031 --- /dev/null +++ b/src/console/CommandTree/ICommand.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Shamir.Console +{ + public interface ICommand : IOperationsNode + { + ValueTask ExecuteAsync(); + } +} \ No newline at end of file diff --git a/src/console/CommandTree/ICommandTree.cs b/src/console/CommandTree/ICommandTree.cs new file mode 100644 index 0000000..4d03bfe --- /dev/null +++ b/src/console/CommandTree/ICommandTree.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Immutable; + +namespace Shamir.Console +{ + public interface ICommandTree : IOperationsNode + { + ImmutableArray SubTrees { get; } + ImmutableArray Commands { get; } + ICommand FindCommand(IImmutableStack path, ReadOnlySpan args); + } +} \ No newline at end of file diff --git a/src/console/CommandTree/IOperationsNode.cs b/src/console/CommandTree/IOperationsNode.cs new file mode 100644 index 0000000..5c826ab --- /dev/null +++ b/src/console/CommandTree/IOperationsNode.cs @@ -0,0 +1,8 @@ +namespace Shamir.Console +{ + public interface IOperationsNode + { + string Name { get; } + string Description { get; } + } +} \ No newline at end of file diff --git a/src/console/Program.cs b/src/console/Program.cs index f28e7b0..30dee75 100644 --- a/src/console/Program.cs +++ b/src/console/Program.cs @@ -1,155 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; +using System.Collections.Immutable; using System.Threading.Tasks; -using CommandLine; -using CommandLine.Text; -using SysConsole = System.Console; namespace Shamir.Console { - public class CdnOperations : ICommandSet - { - [Verb("ls", HelpText = "List files on the CDN")] - public class CdnLsOptions - { - } - - [Verb("add", HelpText = "Upload a file to the CDN")] - public class CdnAddOptions - { - } - - [Verb("mv", HelpText = "Move a file to the CDN")] - public class CdnMvOptions - { - } - - public static IEnumerable VerbOptionTypes - { - get - { - yield return typeof(CdnLsOptions); - yield return typeof(CdnAddOptions); - yield return typeof(CdnMvOptions); - } - } - - public ICommand? FindCommand(ReadOnlySpan args) - { - if (args.Length == 0) return null; - - var parserResult = Parser.Default.ParseArguments(args.ToArray()); - return parserResult.MapResult( - options => new CdnLsCommand(options), - options => new CdnAddCommand(options), - options => new CdnMvCommand(options), - errors => new HelpTextCommand(parserResult) - ); - } - - public class CdnLsCommand : ICommand - { - public CdnLsCommand(CdnLsOptions options) - { - } - - public ValueTask ExecuteAsync() - { - SysConsole.WriteLine($"Executing: cdn-ls"); - return ValueTask.FromResult(0); - } - } - - public class CdnAddCommand : ICommand - { - public CdnAddCommand(CdnAddOptions options) - { - } - - public ValueTask ExecuteAsync() - { - SysConsole.WriteLine($"Executing: cdn-add"); - return ValueTask.FromResult(0); - } - } - - public class CdnMvCommand : ICommand - { - public CdnMvCommand(CdnMvOptions options) - { - } - - public ValueTask ExecuteAsync() - { - SysConsole.WriteLine($"Executing: cdn-mv"); - return ValueTask.FromResult(0); - } - } - - public class HelpTextCommand : ICommand - { - public HelpTextCommand(ParserResult result) - { - this.result = result ?? throw new ArgumentNullException(nameof(result)); - Debug.Assert(result.Tag == ParserResultType.NotParsed); - } - - readonly ParserResult result; - - public ValueTask ExecuteAsync() - { - var helpText = HelpText.AutoBuild(result); - SysConsole.Error.WriteLine(helpText); - return ValueTask.FromResult(1); - } - } - } - - public interface ICommand - { - ValueTask ExecuteAsync(); - } - - public interface ICommandSet - { - ICommand? FindCommand(ReadOnlySpan args); - static IEnumerable VerbOptionTypes { get; } - } - - public class CommandTree : ICommandSet - { - IEnumerable VerbOptionTypes { get; } - public ICommand? FindCommand(ReadOnlySpan args) - { - if (args.Length == 0) return null; - - return args[0] switch { - "cdn" => new CdnOperations().FindCommand(args[1..]), - _ => null, - }; - } - } - - class Program + public static class Program { public static async Task Main(string[] args) { - var tree = new CommandTree(); + var tree = new DefaultCommandTree( + "shamir", + "command-line multitool", + ImmutableArray.Create( + + ), + ImmutableArray.Create( + + ) + ); + var command = tree.FindCommand(args); - if (command is null) - { - PrintHelpText(tree); - return 1; - } - return await command.ExecuteAsync(); } - - static void PrintHelpText(ICommandSet tree) - { - - } } } diff --git a/tests/console.tests/CommandTreeTests.cs b/tests/console.tests/CommandTreeTests.cs index 1f64d8c..aa93565 100644 --- a/tests/console.tests/CommandTreeTests.cs +++ b/tests/console.tests/CommandTreeTests.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; using System.Threading.Tasks; using NUnit.Framework; @@ -85,167 +82,8 @@ Commands: } public string Name { get; } - public string Description { get; } - - public ValueTask ExecuteAsync() - { - return ValueTask.FromResult(0); - } - } - } - - interface IOperationsNode - { - string Name { get; } - string Description { get; } - } - - interface ICommand : IOperationsNode - { - ValueTask ExecuteAsync(); - } - - interface ICommandTree : IOperationsNode - { - ImmutableArray SubTrees { get; } - ImmutableArray Commands { get; } - ICommand FindCommand(IImmutableStack path, ReadOnlySpan args); - } - - class DefaultCommandTree : ICommandTree - { - public DefaultCommandTree(string name, string description, ImmutableArray subtrees, ImmutableArray commands) - { - this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.Description = description ?? throw new ArgumentNullException(nameof(description)); - this.SubTrees = subtrees; - this.Commands = commands; - } - - public string Name { get; } - public string Description { get; } - public ImmutableArray SubTrees { get; } - public ImmutableArray Commands { get; } - - public ICommand FindCommand(IImmutableStack path, ReadOnlySpan args) - { - if (args.Length > 0) - { - foreach (var command in Commands) - { - if (command.Name == args[0]) - { - return command; - } - } - - foreach (var tree in SubTrees) - { - if (tree.Name == args[0]) - { - return tree.FindCommand(path.Push(this), args[1..]); - } - } - } - - return new DefaultHelpTextCommand(path.Push(this)); - } - } - - class DefaultHelpTextCommand : ICommand - { - public DefaultHelpTextCommand(IImmutableStack path) - { - this.Path = path; - } - - public string Name => "help"; - public string Description => "Print help text"; - - public IImmutableStack Path { get; } - - public ValueTask ExecuteAsync() - { - System.Console.Error.WriteLine(GetHelpText()); - return ValueTask.FromResult(1); // TODO: const - } - - public string GetHelpText() => CommandTreeExtensions.BuildHelpText(Path); - } - - static class CommandTreeExtensions - { - public static ICommand? FindCommand(this ICommandTree tree, ReadOnlySpan args) - { - if (tree is null) - { - throw new ArgumentNullException(nameof(tree)); - } - - return tree.FindCommand(ImmutableStack.Empty, args); - } - - public static string BuildHelpText(IImmutableStack path) - { - var sb = new StringBuilder(); - sb.AppendLine("Group"); - sb.Append(" "); - - var pathNodes = path.ToArray(); - for (var i = pathNodes.Length - 1; i > 0; i--) - { - sb.Append(pathNodes[i].Name); - sb.Append(' '); - } - - var tree = pathNodes[0]; - sb.Append(tree.Name); - sb.Append(" : "); - sb.AppendLine(tree.Description); - sb.AppendLine(); - - if (!tree.SubTrees.IsEmpty) - { - sb.AppendLine("Subgroups:"); - - var maxSpacing = tree.SubTrees.Max(c => c.Name.Length) + 1; - - foreach (var child in tree.SubTrees) - { - sb.Append(" "); - sb.Append(child.Name); - for (var i = 0; i < maxSpacing - child.Name.Length; i++) - { - sb.Append(' '); - } - sb.Append(": "); - sb.AppendLine(child.Description); - } - - sb.AppendLine(); - } - - if (!tree.Commands.IsEmpty) - { - sb.AppendLine("Commands:"); - - var maxSpacing = tree.Commands.Max(c => c.Name.Length) + 1; - - foreach (var command in tree.Commands) - { - sb.Append(" "); - sb.Append(command.Name); - for (var i = 0; i < maxSpacing - command.Name.Length; i++) - { - sb.Append(' '); - } - sb.Append(": "); - sb.AppendLine(command.Description); - } - } - - return sb.ToString(); + public ValueTask ExecuteAsync() => ValueTask.FromResult(0); } } } \ No newline at end of file