diff --git a/Directory.Build.props b/Directory.Build.props index a1904d6..fa81925 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,6 +10,9 @@ Michał Bryłka, Leszek Kowalski MIT OR Apache-2.0 Copyright (c) Michał Bryłka. Icon by http://www.iconka.com + false + true + https://github.com/nemesissoft/Nemesis.TextParsers true diff --git a/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs b/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs index 98178af..fd9f9c6 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/ApprovalTests/AutoDeconstructableGeneratorApprovalTests.cs @@ -15,17 +15,17 @@ namespace Nemesis.TextParsers.CodeGen.Tests.ApprovalTests internal class AutoDeconstructableGeneratorApprovalTests { [Test] public void ApprovalTestsRecord() => RunCase("Record"); - + [Test] public void ApprovalTestsStruct() => RunCase("ReadOnlyStruct"); - + [Test] public void ApprovalTestsLarge() => RunCase("Large"); - + [Test] public void ApprovalTestsComplexTypes() => RunCase("ComplexType"); - + [Test] public void ApprovalTestsSimpleWrapperStruct() => RunCase("SimpleWrapperStruct"); - - + + [Test] public void HouseKeeping() => ApprovalMaintenance.VerifyNoAbandonedFiles(); @@ -34,11 +34,11 @@ private static void RunCase(string index) var (_, source, _) = EndToEndCases.AutoDeconstructableCases().SingleOrDefault(t => t.name == index); Assert.That(source, Is.Not.Null); Assert.That(source, Is.Not.Empty); - + var compilation = CreateCompilation( $@"using Nemesis.TextParsers.Settings; namespace Nemesis.TextParsers.CodeGen.Tests {{ {source} }}"); - var generatedTrees = AutoDeconstructableGeneratorTests.GetGeneratedTreesOnly(compilation); + var generatedTrees = GetGeneratedTreesOnly(compilation); var actual = ScrubGeneratorComments(generatedTrees.Single()); diff --git a/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs b/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs index c1d9edd..fea66dc 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/AutoDeconstructableGeneratorTests.cs @@ -1,45 +1,38 @@ extern alias original; -using System; using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Nemesis.CodeAnalysis; -using Nemesis.TextParsers.CodeGen.Deconstructable; - using NUnit.Framework; using static Nemesis.TextParsers.CodeGen.Tests.Utils; +namespace Nemesis.TextParsers.CodeGen.Tests; -namespace Nemesis.TextParsers.CodeGen.Tests +[TestFixture] +public partial class AutoDeconstructableGeneratorTests { - [TestFixture] - public partial class AutoDeconstructableGeneratorTests - { - private static readonly IEnumerable _endToEndCases = EndToEndCases.AutoDeconstructableCases() - .Select((t, i) => new TestCaseData($@" + private static readonly IEnumerable _endToEndCases = EndToEndCases.AutoDeconstructableCases() + .Select((t, i) => new TestCaseData($@" using Nemesis.TextParsers.Settings; namespace Nemesis.TextParsers.CodeGen.Tests {{ {t.source} }}", t.expectedCode) - .SetName($"E2E_{i + 1:00}_{t.name}")); + .SetName($"E2E_{i + 1:00}_{t.name}")); - [TestCaseSource(nameof(_endToEndCases))] - public void EndToEndTests(string source, string expectedCode) - { - var compilation = CreateCompilation(source); + [TestCaseSource(nameof(_endToEndCases))] + public void EndToEndTests(string source, string expectedCode) + { + var compilation = CreateCompilation(source); - var generatedTrees = GetGeneratedTreesOnly(compilation); + var generatedTrees = GetGeneratedTreesOnly(compilation); - var actual = ScrubGeneratorComments(generatedTrees.Single()); + var actual = ScrubGeneratorComments(generatedTrees.Single()); - Assert.That(actual, Is.EqualTo(expectedCode).Using(IgnoreNewLinesComparer.EqualityComparer)); - } + Assert.That(actual, Is.EqualTo(expectedCode).Using(IgnoreNewLinesComparer.EqualityComparer)); + } - private static readonly IEnumerable _settingsCases = new (string typeDefinition, string expectedCodePart)[] - { - (@"[DeconstructableSettings(':', '␀', '/', '{', '}')] + private static readonly IEnumerable _settingsCases = new (string typeDefinition, string expectedCodePart)[] + { + (@"[DeconstructableSettings(':', '␀', '/', '{', '}')] readonly partial struct Child { public byte Age { get; } @@ -49,87 +42,59 @@ readonly partial struct Child }", "private readonly TupleHelper _helper = new TupleHelper(':', '␀', '/', '{', '}');"), - (@"[DeconstructableSettings('_', '␀', '*', '<', '>')] + (@"[DeconstructableSettings('_', '␀', '*', '<', '>')] partial record T(byte B) { }", @"new TupleHelper('_', '␀', '*', '<', '>');"), - (@"[DeconstructableSettings('_', '␀', '*', '<')] + (@"[DeconstructableSettings('_', '␀', '*', '<')] partial record T(byte B) { }", @"new TupleHelper('_', '␀', '*', '<', ')');"), - (@"[DeconstructableSettings('_', '␀', '*')] + (@"[DeconstructableSettings('_', '␀', '*')] partial record T(byte B) { }", @"new TupleHelper('_', '␀', '*', '(', ')');"), - (@"[DeconstructableSettings('_', '␀')] + (@"[DeconstructableSettings('_', '␀')] partial record T(byte B) { }", @"new TupleHelper('_', '␀', '\\', '(', ')');"), - (@"[DeconstructableSettings('_')] + (@"[DeconstructableSettings('_')] partial record T(byte B) { }", @"new TupleHelper('_', '∅', '\\', '(', ')');"), - (@"[DeconstructableSettings] + (@"[DeconstructableSettings] partial record T(byte B) { }", @"new TupleHelper(';', '∅', '\\', '(', ')');"), - (@"partial record T(byte B) { }", @"_helper = transformerStore.SettingsStore.GetSettingsFor().ToTupleHelper();"), + (@"partial record T(byte B) { }", @"_helper = transformerStore.SettingsStore.GetSettingsFor().ToTupleHelper();"), - (@"[DeconstructableSettings(useDeconstructableEmpty: true)] + (@"[DeconstructableSettings(useDeconstructableEmpty: true)] partial record T(byte B) { }", @"public override T GetEmpty() => new T(_transformer_B.GetEmpty());"), - (@"[DeconstructableSettings(useDeconstructableEmpty: false)] + (@"[DeconstructableSettings(useDeconstructableEmpty: false)] partial record T(byte B) { }", @"NOT CONTAIN:GetEmpty()"), - } - .Select((t, i) => new TestCaseData($@"using Nemesis.TextParsers.Settings; namespace Tests {{ [Auto.AutoDeconstructable] {t.typeDefinition} }}", t.expectedCodePart) - .SetName($"Settings{i + 1:00}")); - - [TestCaseSource(nameof(_settingsCases))] - public void SettingsRetrieval_ShouldEmitProperValues(string source, string expectedCodePart) - { - bool matchNotContain = expectedCodePart.StartsWith("NOT CONTAIN:"); - if (matchNotContain) - expectedCodePart = expectedCodePart.Substring(12); - - //arrange - var compilation = CreateCompilation(source); - - //act - var generatedTrees = GetGeneratedTreesOnly(compilation); - var actual = generatedTrees.Single(); + } + .Select((t, i) => new TestCaseData($@"using Nemesis.TextParsers.Settings; namespace Tests {{ [Auto.AutoDeconstructable] {t.typeDefinition} }}", t.expectedCodePart) + .SetName($"Settings{i + 1:00}")); + [TestCaseSource(nameof(_settingsCases))] + public void SettingsRetrieval_ShouldEmitProperValues(string source, string expectedCodePart) + { + bool matchNotContain = expectedCodePart.StartsWith("NOT CONTAIN:"); + if (matchNotContain) + expectedCodePart = expectedCodePart.Substring(12); - //assert - Assert.That(actual, matchNotContain ? Does.Not.Contain(expectedCodePart) : Does.Contain(expectedCodePart)); - } + //arrange + var compilation = CreateCompilation(source); - public static IReadOnlyList GetGeneratedTreesOnly(Compilation compilation, int requiredCardinality = 1) - { - var newComp = CompilationUtils.RunGenerators(compilation, out var diagnostics, new AutoDeconstructableGenerator()); - Assert.That(diagnostics, Is.Empty); - - SyntaxTree attributeTree = null; - foreach (var tree in newComp.SyntaxTrees) - { - var attributeDeclaration = tree.GetRoot().DescendantNodes().OfType() - .FirstOrDefault(cds => string.Equals(cds.Identifier.ValueText, AutoDeconstructableGenerator.ATTRIBUTE_NAME, StringComparison.Ordinal)); - if (attributeDeclaration != null) - { - attributeTree = tree; - break; - } - } - Assert.That(attributeTree, Is.Not.Null, "Auto attribute not found among generated trees"); + //act + var generatedTrees = GetGeneratedTreesOnly(compilation); + var actual = generatedTrees.Single(); - var toRemove = compilation.SyntaxTrees.Append(attributeTree); - var generatedTrees = newComp.RemoveSyntaxTrees(toRemove).SyntaxTrees.ToList(); - Assert.That(generatedTrees, Has.Count.EqualTo(requiredCardinality)); - - return generatedTrees.Select(tree => - ((CompilationUnitSyntax)tree.GetRoot()) - .ToFullString()).ToList(); - } + //assert + Assert.That(actual, matchNotContain ? Does.Not.Contain(expectedCodePart) : Does.Contain(expectedCodePart)); + } - [Test] - public void Generate_When_StaticUsing_And_Mnemonics() - { - var compilation = CreateCompilation(@"using SD = System.Double; + [Test] + public void Generate_When_StaticUsing_And_Mnemonics() + { + var compilation = CreateCompilation(@"using SD = System.Double; using static Tests3.ContainerClass3; namespace Tests1 @@ -148,11 +113,11 @@ namespace Tests3 public static class ContainerClass3 { public class NestedClass3 { } } }"); - var generatedTrees = GetGeneratedTreesOnly(compilation); + var generatedTrees = GetGeneratedTreesOnly(compilation); - var actual = ScrubGeneratorComments(generatedTrees.Single()); + var actual = ScrubGeneratorComments(generatedTrees.Single()); - Assert.That(actual, Is.EqualTo(@"//HEAD + Assert.That(actual, Is.EqualTo(@"//HEAD using System; using Nemesis.TextParsers; using Nemesis.TextParsers.Parsers; @@ -235,7 +200,6 @@ public override string Format(Numbers element) } } }").Using(IgnoreNewLinesComparer.EqualityComparer)); - } } } diff --git a/Nemesis.TextParsers.CodeGen.Tests/GlobalSuppressions.cs b/Nemesis.TextParsers.CodeGen.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..1c0bb7f --- /dev/null +++ b/Nemesis.TextParsers.CodeGen.Tests/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Multiple target frameworks without support for range operator", Scope = "namespaceanddescendants", Target = "~N:Nemesis.TextParsers.CodeGen.Tests")] diff --git a/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj b/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj index 4970acc..df92887 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj +++ b/Nemesis.TextParsers.CodeGen.Tests/Nemesis.TextParsers.CodeGen.Tests.csproj @@ -3,8 +3,7 @@ net6.0 true - $(NoWarn);NU1603 - false + $(NoWarn);NU1603 diff --git a/Nemesis.TextParsers.CodeGen.Tests/Utils.cs b/Nemesis.TextParsers.CodeGen.Tests/Utils.cs index df60fa5..ca8466a 100644 --- a/Nemesis.TextParsers.CodeGen.Tests/Utils.cs +++ b/Nemesis.TextParsers.CodeGen.Tests/Utils.cs @@ -1,58 +1,89 @@ extern alias original; using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using System.Reflection; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Nemesis.CodeAnalysis; +using Nemesis.TextParsers.CodeGen.Deconstructable; +using NUnit.Framework; -namespace Nemesis.TextParsers.CodeGen.Tests +namespace Nemesis.TextParsers.CodeGen.Tests; + +internal static class Utils { - internal static class Utils + public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) { - public static Compilation CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) - { - var (compilation, _, _) = CompilationUtils.CreateTestCompilation(source, new[] - { - typeof(BigInteger).GetTypeInfo().Assembly, - typeof(original::Nemesis.TextParsers.ITransformer).GetTypeInfo().Assembly - }, outputKind); + var (compilation, _, _) = CompilationUtils.CreateTestCompilation(source, new[] + { + typeof(BigInteger).GetTypeInfo().Assembly, + typeof(original::Nemesis.TextParsers.ITransformer).GetTypeInfo().Assembly + }, outputKind); - return compilation; - } + return compilation; + } - private static readonly Regex _headerPattern = new(@"/\*\s* .+? \s*\*/", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); - private static readonly Regex _generatorPattern = new(@""".*Generator""\s*,\s*""([0-9.]+)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); + private static readonly Regex _headerPattern = new(@"/\*\s* .+? \s*\*/", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); + private static readonly Regex _generatorPattern = new(@""".*Generator""\s*,\s*""([0-9.]+)""", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); - public static string ScrubGeneratorComments(string text) - { - text = _generatorPattern.Replace(text, "string.Empty, string.Empty"); - text = _headerPattern.Replace(text, "//HEAD"); + public static string ScrubGeneratorComments(string text) + { + text = _generatorPattern.Replace(text, "string.Empty, string.Empty"); + text = _headerPattern.Replace(text, "//HEAD"); + + return text; + } + + public static IReadOnlyList GetGeneratedTreesOnly(Compilation compilation, int requiredCardinality = 1) + { + var newComp = CompilationUtils.RunGenerators(compilation, out var diagnostics, new AutoDeconstructableGenerator()); + Assert.That(diagnostics, Is.Empty); - return text; + SyntaxTree attributeTree = null; + foreach (var tree in newComp.SyntaxTrees) + { + var attributeDeclaration = tree.GetRoot().DescendantNodes().OfType() + .FirstOrDefault(cds => string.Equals(cds.Identifier.ValueText, AutoDeconstructableGenerator.ATTRIBUTE_NAME, StringComparison.Ordinal)); + if (attributeDeclaration != null) + { + attributeTree = tree; + break; + } } + Assert.That(attributeTree, Is.Not.Null, "Auto attribute not found among generated trees"); + + var toRemove = compilation.SyntaxTrees.Append(attributeTree); + + var generatedTrees = newComp.RemoveSyntaxTrees(toRemove).SyntaxTrees.ToList(); + Assert.That(generatedTrees, Has.Count.EqualTo(requiredCardinality)); + + return generatedTrees.Select(tree => + ((CompilationUnitSyntax)tree.GetRoot()) + .ToFullString()).ToList(); } +} - internal class IgnoreNewLinesComparer : IComparer, IEqualityComparer - { - public static readonly IComparer Comparer = new IgnoreNewLinesComparer(); +internal class IgnoreNewLinesComparer : IComparer, IEqualityComparer +{ + public static readonly IComparer Comparer = new IgnoreNewLinesComparer(); - public static readonly IEqualityComparer EqualityComparer = new IgnoreNewLinesComparer(); + public static readonly IEqualityComparer EqualityComparer = new IgnoreNewLinesComparer(); - public int Compare(string x, string y) => string.CompareOrdinal(NormalizeNewLines(x), NormalizeNewLines(y)); + public int Compare(string x, string y) => string.CompareOrdinal(NormalizeNewLines(x), NormalizeNewLines(y)); - public bool Equals(string x, string y) => NormalizeNewLines(x) == NormalizeNewLines(y); + public bool Equals(string x, string y) => NormalizeNewLines(x) == NormalizeNewLines(y); - public int GetHashCode(string s) => NormalizeNewLines(s)?.GetHashCode() ?? 0; + public int GetHashCode(string s) => NormalizeNewLines(s)?.GetHashCode() ?? 0; - public static string NormalizeNewLines(string s) => s? - .Replace(Environment.NewLine, "") - .Replace("\n", "") - .Replace("\r", ""); - } + public static string NormalizeNewLines(string s) => s? + .Replace(Environment.NewLine, "") + .Replace("\n", "") + .Replace("\r", ""); } diff --git a/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj b/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj index 5ccfd6e..931c062 100644 --- a/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj +++ b/Nemesis.TextParsers.CodeGen/Nemesis.TextParsers.CodeGen.csproj @@ -2,11 +2,10 @@ netstandard2.0 - - + true $(PackageIdPrefix)$(AssemblyName) codegen codegeneration generation trnasformer parse ReadOnlySpan Span Memory fast allocation noAllocation Contains various parser optimized for speed and no allocation @@ -19,9 +18,7 @@ true - review-icon.png - true - https://github.com/nemesissoft/Nemesis.TextParsers + review-icon.png $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true diff --git a/Nemesis.TextParsers.Tests/RecordsTests.cs b/Nemesis.TextParsers.Tests/RecordsTests.cs index 8fbdf40..f1a5b68 100644 --- a/Nemesis.TextParsers.Tests/RecordsTests.cs +++ b/Nemesis.TextParsers.Tests/RecordsTests.cs @@ -45,10 +45,13 @@ public void ShouldBeAbleToReparseComplexRecords() Assert.That(formatted, Is.EqualTo("(Mike;36;(Comodo Dragon)|(Lizard))")); var parsed = sut.Parse(formatted); - Assert.That(parsed.Name, Is.EqualTo("Mike")); - Assert.That(parsed.Age, Is.EqualTo(36)); - Assert.That(parsed.Animals[0].Name, Is.EqualTo("Comodo Dragon")); - Assert.That(parsed.Animals[1].Name, Is.EqualTo("Lizard")); + Assert.Multiple(() => + { + Assert.That(parsed.Name, Is.EqualTo("Mike")); + Assert.That(parsed.Age, Is.EqualTo(36)); + Assert.That(parsed.Animals[0].Name, Is.EqualTo("Comodo Dragon")); + Assert.That(parsed.Animals[1].Name, Is.EqualTo("Lizard")); + }); } enum Habitat { Terrestrial/*, Aquatic, Amphibian*/ } @@ -85,11 +88,14 @@ public void ShouldBeAbleToReparse() Assert.That(formatted, Is.EqualTo(@"Janusz-Korwin\-Mikke-78")); var parsed = sut.Parse(formatted); - Assert.That(parsed.FirstName, Is.EqualTo("Janusz")); - Assert.That(parsed.FamilyName, Is.EqualTo("Korwin-Mikke")); - Assert.That(parsed.Age, Is.EqualTo(78)); + Assert.Multiple(() => + { + Assert.That(parsed.FirstName, Is.EqualTo("Janusz")); + Assert.That(parsed.FamilyName, Is.EqualTo("Korwin-Mikke")); + Assert.That(parsed.Age, Is.EqualTo(78)); - Assert.That(parsed, Is.EqualTo(politician)); + Assert.That(parsed, Is.EqualTo(politician)); + }); } [Transformer(typeof(PersonTransformer))] @@ -120,11 +126,12 @@ public void ShouldBeAbleToReparse(string input, double expectedFirst, double exp var sut = Sut.GetTransformer>(); var parsed = sut.Parse(input); - Assert.That(parsed?.First ?? double.NaN, Is.EqualTo(expectedFirst)); - Assert.That(parsed?.Second ?? double.NaN, Is.EqualTo(expectedSecond)); - Assert.That(parsed?.Third ?? double.NaN, Is.EqualTo(expectedThird)); - - + Assert.Multiple(() => + { + Assert.That(parsed?.First ?? double.NaN, Is.EqualTo(expectedFirst)); + Assert.That(parsed?.Second ?? double.NaN, Is.EqualTo(expectedSecond)); + Assert.That(parsed?.Third ?? double.NaN, Is.EqualTo(expectedThird)); + }); var formatted = sut.Format(parsed); var parsed2 = sut.Parse(formatted); Assert.That(parsed, Is.EqualTo(parsed2)); diff --git a/Nemesis.TextParsers/Nemesis.TextParsers.csproj b/Nemesis.TextParsers/Nemesis.TextParsers.csproj index 83f7fc9..1c5d3e4 100644 --- a/Nemesis.TextParsers/Nemesis.TextParsers.csproj +++ b/Nemesis.TextParsers/Nemesis.TextParsers.csproj @@ -3,18 +3,17 @@ net7.0;net6.0;netcoreapp3.1;netstandard2.1;netstandard2.0 split stringSplit tokenize token parse format list dictionary TextConverter ReadOnlySpan Span Memory fast allocation noAllocation - Contains various parser optimized for speed and no allocation - + Contains various parser optimized for speed and no allocation. - $(PackageIdPrefix)$(AssemblyName) - review-icon.png - true - https://github.com/nemesissoft/Nemesis.TextParsers + true + $(PackageIdPrefix)$(AssemblyName) + properties\review-icon.png + properties\README.md + RELEASE_NOTES_PLACEHOLDER - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true true @@ -22,7 +21,13 @@ - + + Properties\icon.png + + + + Properties\README.md + @@ -59,10 +64,5 @@ <_Parameter1>$(MSBuildProjectName).Tests, PublicKey=00240000048000001a010000060200000024000052534131300800000100010035c8d69e21b106b1164c8fc9c108ed2c08b283d13af6028fc6d6dd07ddd98039bcd99689793df5eef77230ce0a469dfb3ba7575ec699a6e001224ef90b3ce3437e873f0e5bc267a992a78ce1ecb85545d021f17ce51dccf9b3b2cb418aa9adcd2cf93fcc53ab12cb80a5cd51dcf6f3f3be70777b5dbf6d43dc20801be7f9d8220d8ac1082391647e650ff596673c8cd40257f113c8d59f8b150cebc991eeedc69a9c1d442f93089a276aad3122cf90feafb02a384524fcab4d269de23aa5666c6fcc8b89766455d8e0fe9e65d1034673382c596cc60ee8d1b1b4fedb767ff05d7d6cdae0c0db091c24311ae373f98887826256298d72a772a3a8abee357a28f6a5bb4f4369ab - - - - + \ No newline at end of file diff --git a/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs b/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs index 339add3..ad148f9 100644 --- a/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs +++ b/Nemesis.TextParsers/Parsers/03_SimpleTypes.cs @@ -878,7 +878,7 @@ protected override Regex ParseCore(in ReadOnlySpan input) _helper.ParseEnd(ref enumerator, 2, TYPE_NAME); - return new Regex(pattern, options); + return new(pattern, options); } public override string Format(Regex re) @@ -907,7 +907,9 @@ public override string Format(Regex re) private RegexTransformer() { } +#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. public override Regex GetEmpty() => new("", RegexOptions.None); +#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. } internal class RegexOptionsTransformer : TransformerBase diff --git a/README.md b/README.md index 30edc81..7ceda10 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ TL;DR - are you looking for performant, non allocating serializer from structura ### Other popular choices -When stucked with a task of parsing various items form strings we ofter opt for TypeConverter (https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.typeconverter) ? We tend to create methods like: +When stucked with a task of parsing various items form strings we often opt for TypeConverter (https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.typeconverter) ? We tend to create methods like: ```csharp public static T FromString(string text) => (T)TypeDescriptor.GetConverter(typeof(T)) @@ -143,9 +143,7 @@ Open source software is free but creating it is not. Should you wish to support * [![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Michal.Brylka/donate) ![Liberapay receiving](https://img.shields.io/liberapay/receives/Michal.Brylka?color=blue&style=flat-square) - ## Todo / road map - [ ] ability to format to buffer i.e. TryFormat pattern -- [x] become more DI friendly adding support for cross cutting concerns i.e. logging and own TransformerStore customizations - [ ] support for ILookup<,>, IGrouping<,>, ReadOnlyObservableCollection<> - [ ] support for native parsing/formatting of F# types (map, collections, records...) diff --git a/Specification.md b/Specification.md index a4028dc..ca7482e 100644 --- a/Specification.md +++ b/Specification.md @@ -55,7 +55,8 @@ Generally, not affected by custom settings, parsed using InvariantCulture. The f * RegexOptions.IgnorePatternWhitespace → 'x' * RegexOptions.RightToLeft → 'r' * RegexOptions.ECMAScript → 'e' - * RegexOptions.CultureInvariant → 'v' + * RegexOptions.CultureInvariant → 'v' + * RegexOptions.NonBacktracking → 'b' ### KeyValuePair<,> (compound parser) @@ -72,7 +73,7 @@ While generally possible, one might be tempted to format/parse octuples and larg Format can be customized using settings ### Transformables aspect -User can register his own transformer. More on this topic in **Transformables** section below. +User can register his own transformer. More on this topic in [Transformables](#transformables) section below. ### FactoryMethod (legacy) It is possible to use type's ```ToString``` method for formatting and static ```FromText(ReadOnlySpan text)``` or ```FromText(string text)``` method for parsing. If given entity's code is not owned at parsing point, it's possible to provide separate FactoryMethod transformer @@ -106,7 +107,7 @@ Generally parsed as separated with '|' and optionally enclosed in brackets/brace Format can be customized using settings - separately for arrays and other collections. ### Deconstructables aspect -Values can be formatted automatically using deconstructor and parsed using matching constructor's metadata. More on this topic in **Deconstructables** section below. Format can be customized using settings. +Values can be formatted automatically using deconstructor and parsed using matching constructor's metadata. More on this topic in [Deconstructables](#deconstructables) section below. Format can be customized using settings. ### TypeConverters (for legacy reasons) @@ -135,7 +136,8 @@ internal class CustomList : ICustomList, IEquatable : TransformerBase> { - //4. Transformable aspect supports simple injection of ITransformerStore via it's constructor - user can use other transformers already registered for simple/complex types + //4. Transformable aspect supports simple injection of ITransformerStore via it's constructor + // User can thus use other transformers already registered for simple/complex types private readonly ITransformerStore _transformerStore; public CustomListTransformer(ITransformerStore transformerStore) => _transformerStore = transformerStore; @@ -149,7 +151,6 @@ internal class CustomListTransformer : TransformerBase } ``` +Since records contain appropriate constructor/deconstructor, one can use [Deconstructables](#deconstructables) aspect on them. + Finally, you can implement own _`Nemesis.TextParsers.ITransformer`_ or (if you really have no other option) _`System.ComponentModel.TypeConverter`_ class ## 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. ```csharp +// add to csproj + //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 +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; }