diff --git a/src/DocoptNet/ApplicationResultAccumulator.cs b/src/DocoptNet/ApplicationResultAccumulator.cs new file mode 100644 index 00000000..8aa3dd24 --- /dev/null +++ b/src/DocoptNet/ApplicationResultAccumulator.cs @@ -0,0 +1,72 @@ +#nullable enable + +namespace DocoptNet +{ + using System.Collections.Generic; + + interface IApplicationResultAccumulator + { + T New(); + T Command(T state, string name, bool value); + T Command(T state, string name, int value); + T Argument(T state, string name); + T Argument(T state, string name, string value); + T Argument(T state, string name, StringList value); + T Option(T state, string name); + T Option(T state, string name, bool value); + T Option(T state, string name, string value); + T Option(T state, string name, int value); + T Option(T state, string name, StringList value); + T Error(DocoptBaseException exception); + } + + static class ApplicationResultAccumulators + { + public static readonly IApplicationResultAccumulator> ValueDictionary = new ValueDictionaryAccumulator(); + public static readonly IApplicationResultAccumulator> ValueObjectDictionary = new ValueObjectDictionaryAccumulator(); + + sealed class ValueDictionaryAccumulator : IApplicationResultAccumulator> + { + public IDictionary New() => new Dictionary(); + public IDictionary Command(IDictionary state, string name, bool value) => Adding(state, name, value); + public IDictionary Command(IDictionary state, string name, int value) => Adding(state, name, value); + public IDictionary Argument(IDictionary state, string name) => Adding(state, name, Value.None); + public IDictionary Argument(IDictionary state, string name, string value) => Adding(state, name, value); + public IDictionary Argument(IDictionary state, string name, StringList value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name) => Adding(state, name, Value.None); + public IDictionary Option(IDictionary state, string name, bool value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name, string value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name, int value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name, StringList value) => Adding(state, name, value); + public IDictionary Error(DocoptBaseException exception) => null!; + + static IDictionary Adding(IDictionary dict, string name, Value value) + { + dict[name] = value; + return dict; + } + } + + sealed class ValueObjectDictionaryAccumulator : IApplicationResultAccumulator> + { + public IDictionary New() => new Dictionary(); + public IDictionary Command(IDictionary state, string name, bool value) => Adding(state, name, value); + public IDictionary Command(IDictionary state, string name, int value) => Adding(state, name, value); + public IDictionary Argument(IDictionary state, string name) => Adding(state, name, null); + public IDictionary Argument(IDictionary state, string name, string value) => Adding(state, name, value); + public IDictionary Argument(IDictionary state, string name, StringList value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name) => Adding(state, name, null); + public IDictionary Option(IDictionary state, string name, bool value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name, string value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name, int value) => Adding(state, name, value); + public IDictionary Option(IDictionary state, string name, StringList value) => Adding(state, name, value); + public IDictionary Error(DocoptBaseException exception) => null!; + + static IDictionary Adding(IDictionary dict, string name, object? value) + { + dict[name] = new ValueObject(value); + return dict; + } + } + } +} diff --git a/src/DocoptNet/Argument.cs b/src/DocoptNet/Argument.cs index f658ecbc..7a0e0cb4 100644 --- a/src/DocoptNet/Argument.cs +++ b/src/DocoptNet/Argument.cs @@ -1,31 +1,36 @@ namespace DocoptNet { - using System.Collections; + using System.Globalization; class Argument: LeafPattern { - public Argument(string name, ValueObject value = null) : base(name, value) + public Argument(string name) : base(name, Value.None) { } public Argument(string name, string value) - : base(name, new ValueObject(value)) + : base(name, value) { } - public Argument(string name, ICollection coll) - : base(name, new ValueObject(coll)) + public Argument(string name, string[] values) + : base(name, StringList.BottomTop(values)) { } + /// + /// This is only used by tests as a convenience. The instantiated + /// is a string representation of the integer. + /// + public Argument(string name, int value) - : base(name, new ValueObject(value)) + : this(name, value.ToString(CultureInfo.InvariantCulture)) { } public override Node ToNode() { - return new ArgumentNode(this.Name, (this.Value != null && this.Value.IsList) ? ValueType.List : ValueType.String); + return new ArgumentNode(this.Name, Value.IsStringList ? ValueType.List : ValueType.String); } public override string GenerateCode() @@ -33,7 +38,7 @@ public override string GenerateCode() var s = Name.Replace("<", "").Replace(">", "").ToLowerInvariant(); s = "Arg" + GenerateCodeHelper.ConvertToPascalCase(s); - if (Value != null && Value.IsList) + if (Value.IsStringList) { return $"public ArrayList {s} {{ get {{ return _args[\"{Name}\"].AsList; }} }}"; } diff --git a/src/DocoptNet/Command.cs b/src/DocoptNet/Command.cs index 31246c0a..3680f74d 100644 --- a/src/DocoptNet/Command.cs +++ b/src/DocoptNet/Command.cs @@ -1,8 +1,8 @@ namespace DocoptNet { - class Command : Argument + class Command : LeafPattern { - public Command(string name, ValueObject value = null) : base(name, value ?? new ValueObject(false)) + public Command(string name, bool value = false) : base(name, value ? Value.True : Value.False) { } diff --git a/src/DocoptNet/Docopt.cs b/src/DocoptNet/Docopt.cs index 6c724dab..3891a5f7 100644 --- a/src/DocoptNet/Docopt.cs +++ b/src/DocoptNet/Docopt.cs @@ -25,6 +25,27 @@ public IDictionary Apply(string doc, ICollection ar protected IDictionary Apply(string doc, Tokens tokens, bool help = true, object version = null, bool optionsFirst = false, bool exit = false) + { + return Apply(doc, tokens, ApplicationResultAccumulators.ValueObjectDictionary, help, version, optionsFirst, exit); + } + + internal T Apply(string doc, IApplicationResultAccumulator accumulator) + { + return Apply(doc, new Tokens(Enumerable.Empty(), typeof (DocoptInputErrorException)), accumulator); + } + + internal T Apply(string doc, ICollection argv, + IApplicationResultAccumulator accumulator, + bool help = true, object version = null, + bool optionsFirst = false, bool exit = false) + { + return Apply(doc, new Tokens(argv, typeof (DocoptInputErrorException)), accumulator, help, version, optionsFirst, exit); + } + + internal T Apply(string doc, Tokens tokens, + IApplicationResultAccumulator accumulator, + bool help = true, object version = null, + bool optionsFirst = false, bool exit = false) { try { @@ -46,18 +67,33 @@ protected IDictionary Apply(string doc, Tokens tokens, optionsShortcut.Children = docOptions.Distinct().Except(patternOptions).ToList(); } - if (help && arguments.Any(o => o is { Name: "-h" or "--help", Value: { IsNullOrEmpty: false } })) + static bool IsNullOrEmptyString(object obj) => obj is null or string { Length: 0 }; + + if (help && arguments.Any(o => o is { Name: "-h" or "--help" } && !IsNullOrEmptyString(o.Value))) OnPrintExit(doc); - if (version is not null && arguments.Any(o => o is { Name: "--version", Value: { IsNullOrEmpty: false } })) + if (version is not null && arguments.Any(o => o is { Name: "--version" } && !IsNullOrEmptyString(o.Value))) OnPrintExit(version.ToString()); if (pattern.Fix().Match(arguments) is (true, { Count: 0 }, var collected)) { - var dict = new Dictionary(); - foreach (var p in pattern.Flat().OfType().Concat(collected)) - dict[p.Name] = p.Value; - return dict; + return pattern.Flat() + .OfType() + .Concat(collected) + .Aggregate(accumulator.New(), (state, p) => (p, p.Value.Box) switch + { + (Command , bool v ) => accumulator.Command(state, p.Name, v), + (Command , int v ) => accumulator.Command(state, p.Name, v), + (Argument, null ) => accumulator.Argument(state, p.Name), + (Argument, string v ) => accumulator.Argument(state, p.Name, v), + (Argument, StringList v ) => accumulator.Argument(state, p.Name, v.Reverse()), + (Option , bool v ) => accumulator.Option(state, p.Name, v), + (Option , int v ) => accumulator.Option(state, p.Name, v), + (Option , string v ) => accumulator.Option(state, p.Name, v), + (Option , null ) => accumulator.Option(state, p.Name), + (Option , StringList v ) => accumulator.Option(state, p.Name, v.Reverse()), + var other => throw new NotSupportedException($"Unsupported pattern: {other}"), + }); } throw new DocoptInputErrorException(exitUsage); } @@ -68,7 +104,7 @@ protected IDictionary Apply(string doc, Tokens tokens, OnPrintExit(e.Message, e.ErrorCode); - return null; + return accumulator.Error(e); } } @@ -154,7 +190,7 @@ internal static IList ParseArgv(Tokens tokens, ICollection