Releases: nemesissoft/Nemesis.TextParsers
Update meta
- After every release a PR is created with release notes prepended in file ReleaseNotes.md in root directory
- Add release notes and meta for DI project
Full Changelog: 2.9.6...2.9.15
Dependency injection support
Highlight
- Add support for dependency injection by @MichalBrylka in #17
//use Nemesis.TextParsers.DependencyInjection package
//consider the following ASP.Net demo
var builder = WebApplication.CreateBuilder(args);
//...
builder.Services.ConfigureNemesisTextParsers(builder.Configuration.GetRequiredSection("ParsingSettings"))
.AddNemesisTextParsers();
var app = builder.Build();
app.MapGet("/parsingConfigurations/{type}", (string type, SettingsStore store) =>
{
//use injected SettingsStore
});
app.MapGet("/parseType/{type}", (string type, [FromQuery] string text, ITransformerStore transformerStore) =>
{
//use injected ITransformerStore
});
What else has changed
- Add support for transforming Int128/UInt128
- Remove scanning assembly for standard registrations and checking proper static registrations with tests
- Add tests for settings deserialization
- Convert Settings types to Records
- Upgrade to .net 8
- Cache intermediate results for code gen
- Add support for Wolverine-styled DescribeHandlerMatch - breaking change. Rename: ICanCreateTransformer -> ITransformerHandler, *Creator -> *Handler
- Improve code gen tests
Full Changelog: 2.9.2...2.9.6
Update packaging options
Full Changelog: 2.9...2.9.2
Source code generator for enum types
What's Changed
- Implement source code generator for enum by @MichalBrylka in #16
Full Changelog: 2.8.2...2.9.1
Code generator for enum types
With this feature it is enough to annotate enum with 2 attributes:
[Auto.AutoEnumTransformer(
//1. optionally pass parser settings
CaseInsensitive = true, AllowParsingNumerics = true,
//2. TransformerClassName can be left blank. In that case the name of enum is used with "Transformer" suffix
TransformerClassName = "MonthCodeGenTransformer",
//3. optionally pass namespace to generate the transformer class within. If not provided the namespace of the enum will be used
TransformerClassNamespace = "ABC"
)]
//4. decorate enum with TransformerAttribute that points to automatically generated transformer
[Transformer(typeof(ABC.MonthCodeGenTransformer))]
public enum Month : byte
{
None = 0,
January = 1, February = 2, March = 3,
April = 4, May = 5, June = 6,
July = 7, August = 8, September = 9,
October = 10, November = 11, December = 12
}
This in turn generates the following parser using best practices (some lines are ommited for brevity):
Source code for generated parser
public sealed class MonthCodeGenTransformer : TransformerBase<Nemesis.TextParsers.CodeGen.Sample.Month>
{
public override string Format(Nemesis.TextParsers.CodeGen.Sample.Month element) => element switch
{
Nemesis.TextParsers.CodeGen.Sample.Month.None => nameof(Nemesis.TextParsers.CodeGen.Sample.Month.None),
Nemesis.TextParsers.CodeGen.Sample.Month.January => nameof(Nemesis.TextParsers.CodeGen.Sample.Month.January),
// ...
Nemesis.TextParsers.CodeGen.Sample.Month.December => nameof(Nemesis.TextParsers.CodeGen.Sample.Month.December),
_ => element.ToString("G"),
};
protected override Nemesis.TextParsers.CodeGen.Sample.Month ParseCore(in ReadOnlySpan<char> input) =>
input.IsWhiteSpace() ? default : (Nemesis.TextParsers.CodeGen.Sample.Month)ParseElement(input);
private static byte ParseElement(ReadOnlySpan<char> input)
{
if (input.IsEmpty || input.IsWhiteSpace()) return default;
input = input.Trim();
if (IsNumeric(input) && byte.TryParse(input
#if NETFRAMEWORK
.ToString() //legacy frameworks do not support parsing from ReadOnlySpan<char>
#endif
, out var number))
return number;
else
return ParseName(input);
static bool IsNumeric(ReadOnlySpan<char> input) =>
input.Length > 0 && input[0] is var first &&
(char.IsDigit(first) || first is '-' or '+');
}
private static byte ParseName(ReadOnlySpan<char> input)
{
if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.None)))
return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.None;
else if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.January)))
return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.January;
else if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.February)))
return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.February;
// ...
else if (IsEqual(input, nameof(Nemesis.TextParsers.CodeGen.Sample.Month.December)))
return (byte)Nemesis.TextParsers.CodeGen.Sample.Month.December;
else throw new FormatException(@$"Enum of type 'Nemesis.TextParsers.CodeGen.Sample.Month' cannot be parsed from '{input.ToString()}'.
Valid values are: [None or January or February or March or April or May or June or July or August or September or October or November or December] or number within byte range.
Ignore case option on.");
static bool IsEqual(ReadOnlySpan<char> input, string label) =>
MemoryExtensions.Equals(input, label.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
}
Integrate with Github Actions
- Add Github Actions for build. Remove integration with AppVeyor
- Add architecture tests to check if settings can be properly handled
- Add debuging option for code gen
Improve TupleHelper
User is no longer required to call for TupleHelper.ParseNext method. It's code was incorporated into ParseElement method. It's now enough to call:
var _helper = new TupleHelper(',', '∅', '\\', '(', ')');
var input = "(3.14, Ala ma kota)";
var enumerator = _helper.ParseStart(input, 2, "DoubleAndString");
var key = _helper.ParseElement(ref enumerator, TextTransformer.Default.GetTransformer<double>());
var value = _helper.ParseElement(ref enumerator, TextTransformer.Default.GetTransformer<string>(), 2, "DoubleAndString");
_helper.ParseEnd(ref enumerator, 2, "DoubleAndString");
All generated calls were updated accordingly
Full Changelog: v2.7.2...2.8.2
Add support for ReadOnlyObservableCollection
v2.7.2 Add support for ReadOnlyObservableCollection
Optimize build targets
v2.7.1 Optimize build targets
Upgrade to .NET 7
Migrate to NET 7.0
Remove support for net 5.0 (out of support)
Add central package management
Add Generic Math version of LightLinq
Pack readme inside nuget package
Modernize code base
v2.6.3 New API key for nuget + modernize code base
Add code gen package
Add code generation package that automatically generates necessary transformers
C# 9.0 Code generation
With introduction of new code-gen engine, you can opt to have your transformer generated automatically without any imperative code.
//1. use specially provided (via code-gen) Auto.AutoDeconstructable attribute
[Auto.AutoDeconstructable]
//2. provide deconstructable aspect options or leave this attribute out - default options will be engaged
[DeconstructableSettings('_', '∅', '%', '〈', '〉')]
readonly partial /*3. partial modifier is VERY important - you need this cause generated code is placed in different file*/ struct StructPoint3d
{
public double X { get; }
public double Y { get; }
public double Z { get; }
//4. specify constructor and matching deconstructor
public StructPoint3d(double x, double y, double z) { X = x; Y = y; Z = z; }
public void Deconstruct(out double x, out double y, out double z) { x = X; y = Y; z = Z; }
}
//5. sit back, relax and enjoy - code-gen will do the job for you :-)
This in turn might generate the following (parts of code ommited for brevity)
using /* ... */;
[Transformer(typeof(StructPoint3dTransformer))]
readonly partial struct StructPoint3d { }
sealed class StructPoint3dTransformer : TransformerBase<StructPoint3d>
{
private readonly ITransformer<double> _transformer_x = TextTransformer.Default.GetTransformer<double>();
/* specify remaining transformers... */
private const int ARITY = 3;
private readonly TupleHelper _helper = new TupleHelper('_', '∅', '%', '〈', '〉');
protected override StructPoint3d ParseCore(in ReadOnlySpan<char> input)
{
var enumerator = _helper.ParseStart(input, ARITY);
var t1 = _helper.ParseElement(ref enumerator, _transformer_x);
/* parse Y and Z... */
_helper.ParseEnd(ref enumerator, ARITY);
return new StructPoint3d(t1, t2, t3);
}
public override string Format(StructPoint3d element)
{
Span<char> initialBuffer = stackalloc char[32];
var accumulator = new ValueSequenceBuilder<char>(initialBuffer);
try
{
_helper.StartFormat(ref accumulator);
var (x, y, z) = element;
_helper.FormatElement(_transformer_x, x, ref accumulator);
/* format Y and Z... */
_helper.EndFormat(ref accumulator);
return accumulator.AsSpan().ToString();
}
finally { accumulator.Dispose(); }
}
}
Code gen diagnositcs
Various diagnositcs exist to guide end user in creation of proper types that can be consumed by automatic generation. They might for example:
- check if types decorated with Auto* attributes are declared partial (prerequisite for additive code generation)
- validate settings passed via declarative syntax
- validate internal structure of type (i.e. check if constructor has matching Deconstruct method)
- check if external dependencies are included