Move production code into place

This commit is contained in:
Yaakov 2021-09-25 23:29:46 +10:00
parent 3028f83d19
commit ffa89f5845
8 changed files with 196 additions and 306 deletions

View 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();
}
}
}

View 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));
}
}
}

View 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);
}
}

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Shamir.Console
{
public interface ICommand : IOperationsNode
{
ValueTask<int> ExecuteAsync();
}
}

View 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);
}
}

View file

@ -0,0 +1,8 @@
namespace Shamir.Console
{
public interface IOperationsNode
{
string Name { get; }
string Description { get; }
}
}

View file

@ -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)
{
}
} }
} }

View file

@ -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();
} }
} }
} }