Skip to content

Commit

Permalink
Add Typed Argument Accumulator
Browse files Browse the repository at this point in the history
This collects together arguments into a class instance rather than a
dictionary.
  • Loading branch information
iwillspeak committed Aug 15, 2021
1 parent 20d55cd commit 117cbe2
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
73 changes: 73 additions & 0 deletions src/DocoptNet/ApplicationResultAccumulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace DocoptNet
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

interface IApplicationResultAccumulator<T>
{
Expand All @@ -25,6 +29,12 @@ static class ApplicationResultAccumulators
public static readonly IApplicationResultAccumulator<IDictionary<string, Value>> ValueDictionary = new ValueDictionaryAccumulator();
public static readonly IApplicationResultAccumulator<IDictionary<string, ValueObject>> ValueObjectDictionary = new ValueObjectDictionaryAccumulator();

public static IApplicationResultAccumulator<T> ForType<T>()
where T: new()
{
return new TypedArgumentsAccumulator<T>();
}

sealed class ValueDictionaryAccumulator : IApplicationResultAccumulator<IDictionary<string, Value>>
{
public IDictionary<string, Value> New() => new Dictionary<string, Value>();
Expand Down Expand Up @@ -68,5 +78,68 @@ static IDictionary<string, ValueObject> Adding(IDictionary<string, ValueObject>
return dict;
}
}

sealed class TypedArgumentsAccumulator<T> : IApplicationResultAccumulator<T>
where T: new()
{
public T New() => new T();
public T Command(T state, string name, bool value) => Adding(state, name, value);
public T Command(T state, string name, int value) => Adding(state, name, value);
public T Argument(T state, string name) => Adding(state, name, null);
public T Argument(T state, string name, string value) => Adding(state, name, value);
public T Argument(T state, string name, StringList value) => Adding(state, name, value);
public T Option(T state, string name) => Adding(state, name, null);
public T Option(T state, string name, bool value) => Adding(state, name, value);
public T Option(T state, string name, string value) => Adding(state, name, value);
public T Option(T state, string name, int value) => Adding(state, name, value);
public T Option(T state, string name, StringList value) => Adding(state, name, value);
public T Error(DocoptBaseException exception) => default!;

static T Adding(T args, string name, object? value)
{
var propertyName = GetPropertyName(name);
if (!s_Properties.TryGetValue(propertyName, out var prop))
{
throw new ArgumentException($"Can't find property ${propertyName} for argument {name}", nameof(name));
}
prop.SetValue(args, value);
return args;
}

private static string GetPropertyName(string name)
{
if (name.StartsWith("--"))
{
return $"Flag{Pascalise(name.Substring(2))}";
}
if (name.StartsWith("<") && name.EndsWith(">"))
{
return $"Arg{Pascalise(name.Substring(1, name.Length - 2))}";
}
return Pascalise(name);
}

private static string Pascalise(string kebabString)
{
var pascalBuilder = new StringBuilder();
var boundary = true;
foreach (var c in kebabString)
{
if (c == '-')
{
boundary = true;
}
else
{
pascalBuilder.Append(boundary ? char.ToUpper(c) : c);
boundary = false;
}
}
return pascalBuilder.ToString();
}

private static IDictionary<string, PropertyInfo> s_Properties =
typeof(T).GetTypeInfo().GetProperties().ToDictionary(p => p.Name);
}
}
}
58 changes: 58 additions & 0 deletions tests/DocoptNet.Tests/ApplicationResultAccumulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,63 @@ public void Error_returns_null()
Assert.That(Accumulator.Error(new DocoptInputErrorException()), Is.Null);
}
}



[TestFixture]
public class TypedArguments
{
static readonly IApplicationResultAccumulator<TestArguments> Accumulator = ApplicationResultAccumulators.ForType<TestArguments>();

private class TestArguments
{
public bool Command { get; set; }
public string? ArgArgument { get; set; }
public string? FlagOption { get; set; }
public int FlagMaxDegreeOfParallelism { get; set; }
}

[Test]
public void Command_adds_entry_with_value()
{
var args = Accumulator.New();
args = Accumulator.Command(args, "command", true);
var value = args.Command;
Assert.That(value, Is.EqualTo(true));
}

[Test]
public void Argument_adds_entry_with_value()
{
var args = Accumulator.New();
args = Accumulator.Argument(args, "<argument>", "value");
var value = args.ArgArgument;
Assert.That(value, Is.EqualTo("value"));
}

[Test]
public void Option_adds_entry_with_value()
{
var args = Accumulator.New();
args = Accumulator.Option(args, "--option", "value");
var value = args.FlagOption;
Assert.That(value, Is.EqualTo("value"));
}

[Test]
public void Option_with_multiple_words_is_pascalised()
{
var args = Accumulator.New();
args = Accumulator.Option(args, "--max-degree-of-parallelism", 101);
var value = args.FlagMaxDegreeOfParallelism;
Assert.That(value, Is.EqualTo(101));
}

[Test]
public void Error_returns_null()
{
Assert.That(Accumulator.Error(new DocoptInputErrorException()), Is.Null);
}
}
}
}

0 comments on commit 117cbe2

Please sign in to comment.