There is a way but it's ugly -unfortunately. To summarize its key points:
- You will have to create your own Parser.
- Constructor initialization and immutability go out the window -you will see why.
- The way to pass the values to your object is very hacky -at least to my taste.
- You can't guarantee that each key appears once
- It's so big and complex that you might as well say "nah let's split on each line ending, then split on
=
and then take it from there."
This solution is not mine -I would have never thought of something like that. It was taken from Mike Hadlow's blog. The missing parts from EasyNetQ ConnectionStringGrammar.cs -where surprisingly Mike is a contributor to this file.
First you create your property value types. Pretty much you've already done this on your first snippet but regardless here it is:
static Parser<string> Text = Parse.AnyChar.Except(Parse.LineTerminator).Many().Text()
and for numbers
static Parser<int> Number = Parse.Number.Select(int.Parse);
Then the method that will create a key-value parser
public static Parser<ThingsVisitor> CreateKeyValueParser<T>(
string keyName,
Parser<T> valueParser,
Expression<Func<Things, T>> getter)
{
return
from key in Parse.String(keyName).Token()
from separator in Parse.Char('=')
from value in valueParser
select (ThingsVisitor)(t =>
{
CreateSetter(getter)(t, value);
return t;
});
}
ThingsVisitor
is just an alias: using ThingsVisitor = System.Func<Things, Things>;
. Things
is in leu of MyClass
. CreateSetter()
extracts the setter from the expression of the getter. I leave that to the reader -it's not hard if you follow the links.
Then you use the method to create your properties parser
Parser<ThingsVisitor> Props = new List<Parser<ThingsVisitor>>
{
CreateKeyValueParser("OneThing", Text, t => t.OneThing),
CreateKeyValueParser("AnotherThing", Number, t => t.AnotherThing),
CreateKeyValueParser("YetAnotherThing", Text, t => t.YetAnotherThing)
}.Aggregate((a, b) => a.Or(b));
Then you define the parser for the whole input
Parser<IEnumerable<ThingsVisitor>> PropsParser =
from first in Props
from rest in Parse.LineEnd.Then(_ => Props).Many()
select new[] { first }.Concat(rest);
And finally, you you can parse the input
Things things = new();
IEnumerable<ThingsVisitor> parser = PropsParser.Parse(@"OneThing=Foo
AnotherThing=32
YetAnotherThing=Baz");
parser.Aggregate(things, (thing, visitorFunction) => visitorFunction(thing));