mirror of
https://github.com/yaakov-h/Shamir.git
synced 2025-01-18 16:56:33 +00:00
Move production code into place
This commit is contained in:
parent
3028f83d19
commit
ffa89f5845
8 changed files with 196 additions and 306 deletions
82
src/console/CommandTree/CommandTreeExtensions.cs
Normal file
82
src/console/CommandTree/CommandTreeExtensions.cs
Normal file
|
@ -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<string> args)
|
||||||
|
{
|
||||||
|
if (tree is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tree));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree.FindCommand(ImmutableStack<ICommandTree>.Empty, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildHelpText(IImmutableStack<ICommandTree> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
src/console/CommandTree/DefaultCommandTree.cs
Normal file
45
src/console/CommandTree/DefaultCommandTree.cs
Normal file
|
@ -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<ICommandTree> subtrees, ImmutableArray<ICommand> 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<ICommandTree> SubTrees { get; }
|
||||||
|
public ImmutableArray<ICommand> Commands { get; }
|
||||||
|
|
||||||
|
public ICommand FindCommand(IImmutableStack<ICommandTree> path, ReadOnlySpan<string> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/console/CommandTree/DefaultHelpTextCommand.cs
Normal file
26
src/console/CommandTree/DefaultHelpTextCommand.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Shamir.Console
|
||||||
|
{
|
||||||
|
public sealed class DefaultHelpTextCommand : ICommand
|
||||||
|
{
|
||||||
|
public DefaultHelpTextCommand(IImmutableStack<ICommandTree> path)
|
||||||
|
{
|
||||||
|
this.Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "help";
|
||||||
|
public string Description => "Print help text";
|
||||||
|
|
||||||
|
public IImmutableStack<ICommandTree> Path { get; }
|
||||||
|
|
||||||
|
public ValueTask<int> ExecuteAsync()
|
||||||
|
{
|
||||||
|
System.Console.Error.WriteLine(GetHelpText());
|
||||||
|
return ValueTask.FromResult(1); // TODO: const
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetHelpText() => CommandTreeExtensions.BuildHelpText(Path);
|
||||||
|
}
|
||||||
|
}
|
9
src/console/CommandTree/ICommand.cs
Normal file
9
src/console/CommandTree/ICommand.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Shamir.Console
|
||||||
|
{
|
||||||
|
public interface ICommand : IOperationsNode
|
||||||
|
{
|
||||||
|
ValueTask<int> ExecuteAsync();
|
||||||
|
}
|
||||||
|
}
|
12
src/console/CommandTree/ICommandTree.cs
Normal file
12
src/console/CommandTree/ICommandTree.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace Shamir.Console
|
||||||
|
{
|
||||||
|
public interface ICommandTree : IOperationsNode
|
||||||
|
{
|
||||||
|
ImmutableArray<ICommandTree> SubTrees { get; }
|
||||||
|
ImmutableArray<ICommand> Commands { get; }
|
||||||
|
ICommand FindCommand(IImmutableStack<ICommandTree> path, ReadOnlySpan<string> args);
|
||||||
|
}
|
||||||
|
}
|
8
src/console/CommandTree/IOperationsNode.cs
Normal file
8
src/console/CommandTree/IOperationsNode.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Shamir.Console
|
||||||
|
{
|
||||||
|
public interface IOperationsNode
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
string Description { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,155 +1,25 @@
|
||||||
using System;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommandLine;
|
|
||||||
using CommandLine.Text;
|
|
||||||
using SysConsole = System.Console;
|
|
||||||
|
|
||||||
namespace Shamir.Console
|
namespace Shamir.Console
|
||||||
{
|
{
|
||||||
public class CdnOperations : ICommandSet
|
public static class Program
|
||||||
{
|
|
||||||
[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<Type> VerbOptionTypes
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
yield return typeof(CdnLsOptions);
|
|
||||||
yield return typeof(CdnAddOptions);
|
|
||||||
yield return typeof(CdnMvOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand? FindCommand(ReadOnlySpan<string> args)
|
|
||||||
{
|
|
||||||
if (args.Length == 0) return null;
|
|
||||||
|
|
||||||
var parserResult = Parser.Default.ParseArguments<CdnLsOptions, CdnAddOptions, CdnMvOptions>(args.ToArray());
|
|
||||||
return parserResult.MapResult<CdnLsOptions, CdnAddOptions, CdnMvOptions, ICommand>(
|
|
||||||
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<int> ExecuteAsync()
|
|
||||||
{
|
|
||||||
SysConsole.WriteLine($"Executing: cdn-ls");
|
|
||||||
return ValueTask.FromResult(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CdnAddCommand : ICommand
|
|
||||||
{
|
|
||||||
public CdnAddCommand(CdnAddOptions options)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask<int> ExecuteAsync()
|
|
||||||
{
|
|
||||||
SysConsole.WriteLine($"Executing: cdn-add");
|
|
||||||
return ValueTask.FromResult(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CdnMvCommand : ICommand
|
|
||||||
{
|
|
||||||
public CdnMvCommand(CdnMvOptions options)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask<int> ExecuteAsync()
|
|
||||||
{
|
|
||||||
SysConsole.WriteLine($"Executing: cdn-mv");
|
|
||||||
return ValueTask.FromResult(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HelpTextCommand : ICommand
|
|
||||||
{
|
|
||||||
public HelpTextCommand(ParserResult<object> result)
|
|
||||||
{
|
|
||||||
this.result = result ?? throw new ArgumentNullException(nameof(result));
|
|
||||||
Debug.Assert(result.Tag == ParserResultType.NotParsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly ParserResult<object> result;
|
|
||||||
|
|
||||||
public ValueTask<int> ExecuteAsync()
|
|
||||||
{
|
|
||||||
var helpText = HelpText.AutoBuild(result);
|
|
||||||
SysConsole.Error.WriteLine(helpText);
|
|
||||||
return ValueTask.FromResult(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ICommand
|
|
||||||
{
|
|
||||||
ValueTask<int> ExecuteAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ICommandSet
|
|
||||||
{
|
|
||||||
ICommand? FindCommand(ReadOnlySpan<string> args);
|
|
||||||
static IEnumerable<Type> VerbOptionTypes { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CommandTree : ICommandSet
|
|
||||||
{
|
|
||||||
IEnumerable<Type> VerbOptionTypes { get; }
|
|
||||||
public ICommand? FindCommand(ReadOnlySpan<string> args)
|
|
||||||
{
|
|
||||||
if (args.Length == 0) return null;
|
|
||||||
|
|
||||||
return args[0] switch {
|
|
||||||
"cdn" => new CdnOperations().FindCommand(args[1..]),
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Program
|
|
||||||
{
|
{
|
||||||
public static async Task<int> Main(string[] args)
|
public static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
var tree = new CommandTree();
|
var tree = new DefaultCommandTree(
|
||||||
|
"shamir",
|
||||||
|
"command-line multitool",
|
||||||
|
ImmutableArray.Create<ICommandTree>(
|
||||||
|
|
||||||
|
),
|
||||||
|
ImmutableArray.Create<ICommand>(
|
||||||
|
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var command = tree.FindCommand(args);
|
var command = tree.FindCommand(args);
|
||||||
if (command is null)
|
|
||||||
{
|
|
||||||
PrintHelpText(tree);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await command.ExecuteAsync();
|
return await command.ExecuteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintHelpText(ICommandSet tree)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
@ -85,167 +82,8 @@ Commands:
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
|
public ValueTask<int> ExecuteAsync() => ValueTask.FromResult(0);
|
||||||
public ValueTask<int> ExecuteAsync()
|
|
||||||
{
|
|
||||||
return ValueTask.FromResult(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IOperationsNode
|
|
||||||
{
|
|
||||||
string Name { get; }
|
|
||||||
string Description { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICommand : IOperationsNode
|
|
||||||
{
|
|
||||||
ValueTask<int> ExecuteAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICommandTree : IOperationsNode
|
|
||||||
{
|
|
||||||
ImmutableArray<ICommandTree> SubTrees { get; }
|
|
||||||
ImmutableArray<ICommand> Commands { get; }
|
|
||||||
ICommand FindCommand(IImmutableStack<ICommandTree> path, ReadOnlySpan<string> args);
|
|
||||||
}
|
|
||||||
|
|
||||||
class DefaultCommandTree : ICommandTree
|
|
||||||
{
|
|
||||||
public DefaultCommandTree(string name, string description, ImmutableArray<ICommandTree> subtrees, ImmutableArray<ICommand> 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<ICommandTree> SubTrees { get; }
|
|
||||||
public ImmutableArray<ICommand> Commands { get; }
|
|
||||||
|
|
||||||
public ICommand FindCommand(IImmutableStack<ICommandTree> path, ReadOnlySpan<string> 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<ICommandTree> path)
|
|
||||||
{
|
|
||||||
this.Path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name => "help";
|
|
||||||
public string Description => "Print help text";
|
|
||||||
|
|
||||||
public IImmutableStack<ICommandTree> Path { get; }
|
|
||||||
|
|
||||||
public ValueTask<int> 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<string> args)
|
|
||||||
{
|
|
||||||
if (tree is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(tree));
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree.FindCommand(ImmutableStack<ICommandTree>.Empty, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string BuildHelpText(IImmutableStack<ICommandTree> 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue