Skip to content

Commit

Permalink
Adds output formatters (#127)
Browse files Browse the repository at this point in the history
- Adds output formatters
- Adds output formatters
  • Loading branch information
PascalSenn authored Aug 11, 2023
1 parent 5f15347 commit e9d74b1
Show file tree
Hide file tree
Showing 44 changed files with 1,712 additions and 256 deletions.
40 changes: 31 additions & 9 deletions .build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ public class Build : NukeBuild
var commands = new List<CommandTask>();
var queue = new Stack<string>();
var parentSymbols = new Stack<Symbol[]>();
parentSymbols.Push(builder.Command.Options
.Where(x => x is { Name: not "help" and not "version" })
.OfType<Symbol>()
.Concat(builder.Command.Arguments)
.ToArray());
foreach (var subcommand in builder.Command.Subcommands)
{
Visit(subcommand, queue);
Visit(subcommand, queue, parentSymbols);
}
var buffer = new ArrayBufferWriter<byte>();
Expand All @@ -46,23 +52,26 @@ public class Build : NukeBuild
utf8JsonWriter.WriteString("definiteArgument", command.Argument);
utf8JsonWriter.WriteStartObject("settingsClass");
utf8JsonWriter.WriteStartArray("properties");
foreach (var property in command.Command.Options)
var parentOptions = command.ParentSymbols.OfType<Option>();
foreach (var property in command.Command.Options.Concat(parentOptions))
{
utf8JsonWriter.WriteStartObject();
utf8JsonWriter.WriteString("name", EncodeName(property.Name));
utf8JsonWriter.WriteString("type", "string");
utf8JsonWriter.WriteString("format", $"--{property.Name} {{value}}");
utf8JsonWriter.WriteString("help", property.Description);
utf8JsonWriter.WriteString("help", property.Description?.Replace("\n", " "));
utf8JsonWriter.WriteEndObject();
}
foreach (var property in command.Command.Arguments)
var parentArguments = command.ParentSymbols.OfType<Argument>();
foreach (var property in command.Command.Arguments.Concat(parentArguments))
{
utf8JsonWriter.WriteStartObject();
utf8JsonWriter.WriteString("name", EncodeName(property.Name));
utf8JsonWriter.WriteString("type", "string");
utf8JsonWriter.WriteString("format", "{value}");
utf8JsonWriter.WriteString("help", property.Description);
utf8JsonWriter.WriteString("help", property.Description?.Replace("\n", " "));
utf8JsonWriter.WriteEndObject();
}
Expand Down Expand Up @@ -95,22 +104,31 @@ public class Build : NukeBuild
GenerateCode(ToolSpecfication, namespaceProvider: _ => "Confix.Nuke");
return;
void Visit(Command command, Stack<string> parents)
void Visit(Command command, Stack<string> parents, Stack<Symbol[]> symbols)
{
parents.Push(command.Name);
if (command.Subcommands.Count == 0)
{
var reversed = parents.Reverse().ToList();
var name = string.Join("", reversed.Select(Capitalize));
commands.Add(new(name, string.Join(" ", reversed), command));
commands.Add(
new(name,
string.Join(" ", reversed),
command,
symbols.SelectMany(x => x).ToArray()));
}
else
{
symbols.Push(command.Options.OfType<Symbol>()
.Concat(command.Arguments)
.ToArray());
foreach (var subcommand in command.Subcommands)
{
Visit(subcommand, parents);
Visit(subcommand, parents, symbols);
}
symbols.Pop();
}
parents.Pop();
Expand All @@ -129,4 +147,8 @@ private static string Capitalize(string name)
}
}

public record CommandTask(string Name, string Argument, Command Command);
public record CommandTask(
string Name,
string Argument,
Command Command,
IReadOnlyList<Symbol> ParentSymbols);
10 changes: 10 additions & 0 deletions src/Confix.Tool/src/Confix.Library/Common/ContextData/Context.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Confix.Tool;

public static class Context
{
public static Key<bool> DisableStatus { get; } = new("Confix.Tool.Settings.DisableStatus");

public static Key<object> Output { get; } = new("Confix.Tool.Output");

public readonly record struct Key<T>(string Id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.Runtime.CompilerServices;
using Confix.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace Confix.Tool;

public static class ContextDataCommandLineBuilderExtensions
{
private static readonly ConditionalWeakTable<CommandLineBuilder, ContextData> _contextData =
new();

public static CommandLineBuilder AddContextData(this CommandLineBuilder builder)
{
var contextData = new ContextData();
builder.AddSingleton(contextData);
_contextData.Add(builder, contextData);

return builder;
}

public static CommandLineBuilder SetContextData<T>(
this CommandLineBuilder builder,
Context.Key<T> key,
T value)
where T : notnull
{
builder.AddMiddleware((context, next) =>
{
var data = context.BindingContext.GetRequiredService<ContextData>();
data.Data.Set(key, value);
return next(context);
});

return builder;
}

public static IDictionary<string, object> GetContextData(this InvocationContext context)
{
return context.BindingContext.GetRequiredService<ContextData>().Data;
}

public static IDictionary<string, object> GetContextData(this CommandLineBuilder builder)
{
return _contextData.GetOrCreateValue(builder).Data;
}

public static void SetContextData<T>(
this InvocationContext context,
in Context.Key<T> key,
T value)
where T : notnull
{
var data = context.BindingContext.GetRequiredService<ContextData>();
data.Data.Set(key, value);
}

private class ContextData
{
public IDictionary<string, object> Data { get; } = new Dictionary<string, object>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Diagnostics.CodeAnalysis;

namespace Confix.Tool;

public static class ContextDataExtensions
{
public static T? Get<T>(this IDictionary<string, object> contextData, in Context.Key<T> key)
{
if (contextData.TryGetValue(key.Id, out var value))
{
return (T?) value;
}

return default;
}

public static void Set<T>(
this IDictionary<string, object> contextData,
in Context.Key<T> key,
T value)
where T : notnull
{
contextData[key.Id] = value;
}

public static bool TryGetValue<T>(
this IDictionary<string, object> contextData,
in Context.Key<T> key,
[NotNullWhen(true)] out T? value)
{
if (contextData.TryGetValue(key.Id, out var v))
{
value = (T) v;
return true;
}

value = default;
return false;
}

public static T GetOrAddValue<T>(
this IDictionary<string, object> contextData,
in Context.Key<T> key) where T : new()
{
if (!contextData.TryGetValue(key.Id, out var v) || v is not T value)
{
value = new T();
contextData[key.Id] = value;
}

return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ public static CommandLineBuilder UseVerbosity(this CommandLineBuilder builder)

return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.CommandLine.Invocation;

namespace Confix.Tool.Commands.Logging;

public interface IOutputFormatter
{
bool CanHandle(OutputFormat format, object value);

Task<string> FormatAsync(InvocationContext context, OutputFormat format, object value);
}

public interface IOutputFormatter<in T> : IOutputFormatter
{
bool IOutputFormatter.CanHandle(OutputFormat format, object value)
=> value is T t && CanHandle(format, t);

bool CanHandle(OutputFormat format, T value);

Task<string> IOutputFormatter.FormatAsync(
InvocationContext context,
OutputFormat format,
object value)
{
if (value is T t)
{
return FormatAsync(context, format, t);
}

throw new ArgumentException($"Expected {typeof(T).Name} but got {value.GetType().Name}");
}

Task<string> FormatAsync(InvocationContext context, OutputFormat format, T value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.CommandLine.Builder;
using Spectre.Console;

namespace Confix.Tool.Commands.Logging;

public static class OutputFormatCommandLineBuilderExtensions
{
private static Context.Key<List<IOutputFormatter>> _key =
new("Confix.Tool.Common.OutputFormatter");

public static CommandLineBuilder AddOutputFormatter<T>(this CommandLineBuilder builder)
where T : IOutputFormatter, new()
{
builder.GetOutputFormatters().Add(new T());
return builder;
}

public static CommandLineBuilder UseOutputFormat(this CommandLineBuilder builder)
{
builder.AddMiddleware(async (context, next) =>
{
var format =
context.ParseResult.GetValueForOption(FormatOption.Instance) ??
context.ParseResult.GetValueForOption(FormatOptionWithDefault.Instance);
if (format is not null)
{
context.SetContextData(Context.DisableStatus, true);
// we disable logging if the format option is specified
using (App.Log.SetVerbosity(Verbosity.Quiet))
{
await next(context);
}
var outputFormatters = builder.GetOutputFormatters();
if (builder.GetOutput() is { } output)
{
string? formattedValue = null;
foreach (var outputFormatter in outputFormatters)
{
if (!outputFormatter.CanHandle(format.Value, output))
{
continue;
}
formattedValue =
await outputFormatter.FormatAsync(context, format.Value, output);
break;
}
context.Console.Out.Write(formattedValue);
}
}
else
{
await next(context);
}
});

return builder;
}

private static List<IOutputFormatter> GetOutputFormatters(this CommandLineBuilder builder)
=> builder.GetContextData().GetOrAddValue(_key);

private static object? GetOutput(this CommandLineBuilder context)
{
return context.GetContextData().Get(Context.Output);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Confix.Tool.Common.Pipelines;

namespace Confix.Tool.Commands.Logging;

public static class OutputMiddlewareContextExtensions
{
public static void SetOutput(this IMiddlewareContext context, object? result)
{
if (result is null)
{
return;
}

context.ContextData.Set(Context.Output, result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class CommandExtensions
async Task<int> Handler(InvocationContext context)
{
// create the pipeline from the definition with the binding context
var executor = definition.BuildExecutor(context.BindingContext);
var executor = definition.BuildExecutor(context);

command.Arguments.ForEach(argument =>
{
Expand Down
14 changes: 12 additions & 2 deletions src/Confix.Tool/src/Confix.Library/Common/Pipelines/Pipeline.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.CommandLine;
using System.CommandLine.Invocation;

namespace Confix.Tool.Common.Pipelines;

Expand Down Expand Up @@ -31,9 +32,18 @@ private void Initialize()

public IReadOnlySet<Option> Options { get; private set; } = new HashSet<Option>();

public PipelineExecutor BuildExecutor(IServiceProvider services)
public PipelineExecutor BuildExecutor(InvocationContext context)
{
return new PipelineExecutor(BuildDelegate(services), services, ContextData);
var services = context.BindingContext;

// as we only use a single instance of the pipeline, we reuse the context data
var contextData = context.GetContextData();
foreach (var (key, value) in ContextData)
{
contextData[key] = value;
}

return new PipelineExecutor(BuildDelegate(services), services, contextData);
}

public async Task ExecuteAsync(IMiddlewareContext context)
Expand Down
Loading

0 comments on commit e9d74b1

Please sign in to comment.