From 959cc45ce09df847a3ad9ea71a624f5f3c42d104 Mon Sep 17 00:00:00 2001 From: Eric_Lian Date: Sat, 30 Nov 2024 00:00:02 +0800 Subject: [PATCH] feat: runtime comment provider --- .../TestInlineCommentProvider.cs | 20 +++++++++++ .../TestPrecedingCommentProvider.cs | 20 +++++++++++ Tomlet.Tests/CommentSerializationTests.cs | 33 ++++++++++++++++--- .../CommentProviderTestModel.cs | 15 +++++++++ .../TomlCommentProviderAttribute.cs | 33 +++++++++++++++++++ .../Attributes/TomlInlineCommentAttribute.cs | 8 ++--- .../TomlInlineCommentProviderAttribute.cs | 15 +++++++++ .../TomlPrecedingCommentAttribute.cs | 8 ++--- .../TomlPrecedingCommentProviderAttribute.cs | 15 +++++++++ Tomlet/CommentProviderUtil.cs | 18 ++++++++++ Tomlet/Extensions/GenericExtensions.cs | 3 +- Tomlet/ICommentProvider.cs | 6 ++++ Tomlet/TomlCompositeSerializer.cs | 23 +++++++++---- Tomlet/TomlSimpleCommentProvider.cs | 16 +++++++++ 14 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 Tomlet.Tests/CommentProvider/TestInlineCommentProvider.cs create mode 100644 Tomlet.Tests/CommentProvider/TestPrecedingCommentProvider.cs create mode 100644 Tomlet.Tests/TestModelClasses/CommentProviderTestModel.cs create mode 100644 Tomlet/Attributes/TomlCommentProviderAttribute.cs create mode 100644 Tomlet/Attributes/TomlInlineCommentProviderAttribute.cs create mode 100644 Tomlet/Attributes/TomlPrecedingCommentProviderAttribute.cs create mode 100644 Tomlet/CommentProviderUtil.cs create mode 100644 Tomlet/ICommentProvider.cs create mode 100644 Tomlet/TomlSimpleCommentProvider.cs diff --git a/Tomlet.Tests/CommentProvider/TestInlineCommentProvider.cs b/Tomlet.Tests/CommentProvider/TestInlineCommentProvider.cs new file mode 100644 index 0000000..2425f95 --- /dev/null +++ b/Tomlet.Tests/CommentProvider/TestInlineCommentProvider.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Tomlet.Tests.CommentProvider; + +public class TestInlineCommentProvider : ICommentProvider +{ + public static Dictionary Comments = new Dictionary(); + + private readonly string _name; + + public TestInlineCommentProvider(string name) + { + _name = name; + } + + public string GetComment() + { + return Comments[_name]; + } +} \ No newline at end of file diff --git a/Tomlet.Tests/CommentProvider/TestPrecedingCommentProvider.cs b/Tomlet.Tests/CommentProvider/TestPrecedingCommentProvider.cs new file mode 100644 index 0000000..b71dc5b --- /dev/null +++ b/Tomlet.Tests/CommentProvider/TestPrecedingCommentProvider.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Tomlet.Tests.CommentProvider; + +public class TestPrecedingCommentProvider : ICommentProvider +{ + public static Dictionary Comments = new Dictionary(); + + private readonly string _name; + + public TestPrecedingCommentProvider(string name) + { + _name = name; + } + + public string GetComment() + { + return Comments[_name]; + } +} \ No newline at end of file diff --git a/Tomlet.Tests/CommentSerializationTests.cs b/Tomlet.Tests/CommentSerializationTests.cs index f53aa9f..6fb1131 100644 --- a/Tomlet.Tests/CommentSerializationTests.cs +++ b/Tomlet.Tests/CommentSerializationTests.cs @@ -1,4 +1,6 @@ +using System; using Tomlet.Models; +using Tomlet.Tests.CommentProvider; using Tomlet.Tests.TestModelClasses; using Xunit; @@ -72,7 +74,7 @@ public void CommentsOnTableArraysWork() tomlString.Comments.InlineComment = "Inline comment on value"; table.PutValue("key", tomlString); - var tableArray = new TomlArray {table}; + var tableArray = new TomlArray { table }; tableArray.Comments.PrecedingComment = "This is a preceding comment on the table-array itself"; doc.PutValue("table-array", tableArray); @@ -91,7 +93,7 @@ public void CommentsOnTableArraysWork() public void CommentsOnPrimitiveArraysWork() { var doc = TomlDocument.CreateEmpty(); - var tomlNumbers = new TomlArray {1, 2, 3}; + var tomlNumbers = new TomlArray { 1, 2, 3 }; doc.PutValue("numbers", tomlNumbers); tomlNumbers[0].Comments.PrecedingComment = "This is a preceding comment on the first value of the array"; @@ -103,7 +105,7 @@ public void CommentsOnPrimitiveArraysWork() 2, # This is an inline comment on the second value of the array 3, ]".ReplaceLineEndings(); - + //Replace tabs with spaces because this source file uses spaces var actual = doc.SerializedValue.Trim().Replace("\t", " ").ReplaceLineEndings(); Assert.Equal(expected, actual); @@ -115,10 +117,33 @@ public void CommentAttributesWork() var config = TomletMain.To(TestResources.ExampleMailboxConfigurationTestInput); var doc = TomletMain.DocumentFrom(config); - + Assert.Equal("The name of the mailbox", doc.GetValue("mailbox").Comments.InlineComment); Assert.Equal("Your username for the mailbox", doc.GetValue("username").Comments.InlineComment); Assert.Equal("The password you use to access the mailbox", doc.GetValue("password").Comments.InlineComment); Assert.Equal("The rules for the mailbox follow", doc.GetArray("rules").Comments.PrecedingComment); } + + [Fact] + public void CommentProviderTest() + { + TestPrecedingCommentProvider.Comments["PrecedingComment"] = Guid.NewGuid().ToString(); + TestInlineCommentProvider.Comments["InlineComment"] = Guid.NewGuid().ToString(); + + var data = new CommentProviderTestModel() + { + PrecedingComment = "Dynamic Preceding Comment", + InlineComment = "Inline Comment", + }; + + var doc = TomletMain.DocumentFrom(data); + + Assert.Equal(TestPrecedingCommentProvider.Comments["PrecedingComment"], + doc.GetValue("PrecedingComment").Comments.PrecedingComment); + Assert.Equal("PlainInlineComment", doc.GetValue("PrecedingComment").Comments.InlineComment); + + Assert.Equal(TestInlineCommentProvider.Comments["InlineComment"], + doc.GetValue("InlineComment").Comments.InlineComment); + Assert.Equal("PlainPrecedingComment", doc.GetValue("InlineComment").Comments.PrecedingComment); + } } \ No newline at end of file diff --git a/Tomlet.Tests/TestModelClasses/CommentProviderTestModel.cs b/Tomlet.Tests/TestModelClasses/CommentProviderTestModel.cs new file mode 100644 index 0000000..003d5e7 --- /dev/null +++ b/Tomlet.Tests/TestModelClasses/CommentProviderTestModel.cs @@ -0,0 +1,15 @@ +using Tomlet.Attributes; +using Tomlet.Tests.CommentProvider; + +namespace Tomlet.Tests.TestModelClasses; + +public class CommentProviderTestModel +{ + [TomlPrecedingCommentProvider(typeof(TestPrecedingCommentProvider), new object[] { "PrecedingComment" })] + [TomlInlineComment("PlainInlineComment")] + public string PrecedingComment { get; set; } + + [TomlInlineCommentProvider(typeof(TestInlineCommentProvider), new object[] { "InlineComment" })] + [TomlPrecedingComment("PlainPrecedingComment")] + public string InlineComment { get; set; } +} \ No newline at end of file diff --git a/Tomlet/Attributes/TomlCommentProviderAttribute.cs b/Tomlet/Attributes/TomlCommentProviderAttribute.cs new file mode 100644 index 0000000..47d16f4 --- /dev/null +++ b/Tomlet/Attributes/TomlCommentProviderAttribute.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; + +namespace Tomlet.Attributes; + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public class TomlCommentProviderAttribute : Attribute +{ + private readonly Type _provider; + private readonly object[] _args; + private readonly Type[] _constructorParamsType; + + public string GetComment() + { + var constructor = _provider.GetConstructor(_constructorParamsType) ?? + throw new ArgumentException("Fail to get a constructor matching the parameters"); + var instance = constructor.Invoke(_args) as ICommentProvider ?? + throw new Exception("Fail to create an instance of the provider"); + return instance.GetComment(); + } + + public TomlCommentProviderAttribute(Type provider, object[] args) + { + if (!typeof(ICommentProvider).IsAssignableFrom(provider)) + { + throw new ArgumentException("Provider must implement ICommentProvider"); + } + + _provider = provider; + _args = args ?? new object[] { }; + _constructorParamsType = args?.Select(a => a.GetType()).ToArray() ?? new Type[] { }; + } +} \ No newline at end of file diff --git a/Tomlet/Attributes/TomlInlineCommentAttribute.cs b/Tomlet/Attributes/TomlInlineCommentAttribute.cs index ddee5f5..7de822a 100644 --- a/Tomlet/Attributes/TomlInlineCommentAttribute.cs +++ b/Tomlet/Attributes/TomlInlineCommentAttribute.cs @@ -3,12 +3,10 @@ namespace Tomlet.Attributes; [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public class TomlInlineCommentAttribute : Attribute +public class TomlInlineCommentAttribute : TomlInlineCommentProviderAttribute { - internal string Comment { get; } - - public TomlInlineCommentAttribute(string comment) + public TomlInlineCommentAttribute(string comment) : base(typeof(TomlSimpleCommentProvider), + new object[] { comment }) { - Comment = comment; } } \ No newline at end of file diff --git a/Tomlet/Attributes/TomlInlineCommentProviderAttribute.cs b/Tomlet/Attributes/TomlInlineCommentProviderAttribute.cs new file mode 100644 index 0000000..76a9d55 --- /dev/null +++ b/Tomlet/Attributes/TomlInlineCommentProviderAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tomlet.Attributes; + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public class TomlInlineCommentProviderAttribute : TomlCommentProviderAttribute +{ + public TomlInlineCommentProviderAttribute(Type provider) : base(provider, new object[] { }) + { + } + + public TomlInlineCommentProviderAttribute(Type provider, object[] args) : base(provider, args) + { + } +} \ No newline at end of file diff --git a/Tomlet/Attributes/TomlPrecedingCommentAttribute.cs b/Tomlet/Attributes/TomlPrecedingCommentAttribute.cs index 0413092..e061bac 100644 --- a/Tomlet/Attributes/TomlPrecedingCommentAttribute.cs +++ b/Tomlet/Attributes/TomlPrecedingCommentAttribute.cs @@ -3,12 +3,10 @@ namespace Tomlet.Attributes; [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public class TomlPrecedingCommentAttribute : Attribute +public class TomlPrecedingCommentAttribute : TomlPrecedingCommentProviderAttribute { - internal string Comment { get; } - - public TomlPrecedingCommentAttribute(string comment) + public TomlPrecedingCommentAttribute(string comment) : base(typeof(TomlSimpleCommentProvider), + new object[] { comment }) { - Comment = comment; } } \ No newline at end of file diff --git a/Tomlet/Attributes/TomlPrecedingCommentProviderAttribute.cs b/Tomlet/Attributes/TomlPrecedingCommentProviderAttribute.cs new file mode 100644 index 0000000..45ab6e6 --- /dev/null +++ b/Tomlet/Attributes/TomlPrecedingCommentProviderAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tomlet.Attributes; + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public class TomlPrecedingCommentProviderAttribute : TomlCommentProviderAttribute +{ + public TomlPrecedingCommentProviderAttribute(Type provider) : base(provider, new object[] { }) + { + } + + public TomlPrecedingCommentProviderAttribute(Type provider, object[] args) : base(provider, args) + { + } +} \ No newline at end of file diff --git a/Tomlet/CommentProviderUtil.cs b/Tomlet/CommentProviderUtil.cs new file mode 100644 index 0000000..76b4f80 --- /dev/null +++ b/Tomlet/CommentProviderUtil.cs @@ -0,0 +1,18 @@ +using System; + +namespace Tomlet; + +internal static class CommentProviderUtil +{ + public static string GetComment(Type provider) + { + var constructor = provider.GetConstructor(Type.EmptyTypes); + if (constructor == null) + { + throw new ArgumentException("Provider must have a parameterless constructor"); + } + + var instance = (ICommentProvider)constructor.Invoke(null); + return instance.GetComment(); + } +} \ No newline at end of file diff --git a/Tomlet/Extensions/GenericExtensions.cs b/Tomlet/Extensions/GenericExtensions.cs index e56ac53..d418127 100644 --- a/Tomlet/Extensions/GenericExtensions.cs +++ b/Tomlet/Extensions/GenericExtensions.cs @@ -148,7 +148,8 @@ public static void Deconstruct(this KeyValuePair pai public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrEmpty(s) || string.IsNullOrEmpty(s.Trim()); - internal static T? GetCustomAttribute(this MemberInfo info) where T : Attribute => info.GetCustomAttributes(false).Where(a => a is T).Cast().FirstOrDefault(); + internal static T? GetCustomAttribute(this MemberInfo info) where T : Attribute + => info.GetCustomAttributes(false).Where(a => typeof(T).IsAssignableFrom(a.GetType())).Cast().FirstOrDefault(); internal static void EnsureLegalChar(this int c, int currentLineNum) { diff --git a/Tomlet/ICommentProvider.cs b/Tomlet/ICommentProvider.cs new file mode 100644 index 0000000..c67590c --- /dev/null +++ b/Tomlet/ICommentProvider.cs @@ -0,0 +1,6 @@ +namespace Tomlet; + +public interface ICommentProvider +{ + public string GetComment(); +} \ No newline at end of file diff --git a/Tomlet/TomlCompositeSerializer.cs b/Tomlet/TomlCompositeSerializer.cs index bcb034b..b8d940e 100644 --- a/Tomlet/TomlCompositeSerializer.cs +++ b/Tomlet/TomlCompositeSerializer.cs @@ -29,11 +29,22 @@ public static TomlSerializationMethods.Serialize For(Type type, TomlSeri var fields = type.GetFields(memberFlags); var fieldAttribs = fields - .ToDictionary(f => f, f => new {inline = GenericExtensions.GetCustomAttribute(f), preceding = GenericExtensions.GetCustomAttribute(f), noInline = GenericExtensions.GetCustomAttribute(f)}); + .ToDictionary(f => f, f => new + { + inline = GenericExtensions.GetCustomAttribute(f), + preceding = GenericExtensions.GetCustomAttribute(f), + noInline = GenericExtensions.GetCustomAttribute(f) + }); var props = type.GetProperties(memberFlags) .ToArray(); var propAttribs = props - .ToDictionary(p => p, p => new {inline = GenericExtensions.GetCustomAttribute(p), preceding = GenericExtensions.GetCustomAttribute(p), prop = GenericExtensions.GetCustomAttribute(p), noInline = GenericExtensions.GetCustomAttribute(p)}); + .ToDictionary(p => p, p => new + { + inline = GenericExtensions.GetCustomAttribute(p), + preceding = GenericExtensions.GetCustomAttribute(p), + prop = GenericExtensions.GetCustomAttribute(p), + noInline = GenericExtensions.GetCustomAttribute(p) + }); var isForcedNoInline = GenericExtensions.GetCustomAttribute(type) != null; @@ -75,8 +86,8 @@ public static TomlSerializationMethods.Serialize For(Type type, TomlSeri //in its supertype. continue; - tomlValue.Comments.InlineComment = commentAttribs.inline?.Comment; - tomlValue.Comments.PrecedingComment = commentAttribs.preceding?.Comment; + tomlValue.Comments.InlineComment = commentAttribs.inline?.GetComment(); + tomlValue.Comments.PrecedingComment = commentAttribs.preceding?.GetComment(); if(commentAttribs.noInline != null && tomlValue is TomlTable table) table.ForceNoInline = true; @@ -104,8 +115,8 @@ public static TomlSerializationMethods.Serialize For(Type type, TomlSeri var thisPropAttribs = propAttribs[prop]; - tomlValue.Comments.InlineComment = thisPropAttribs.inline?.Comment; - tomlValue.Comments.PrecedingComment = thisPropAttribs.preceding?.Comment; + tomlValue.Comments.InlineComment = thisPropAttribs.inline?.GetComment(); + tomlValue.Comments.PrecedingComment = thisPropAttribs.preceding?.GetComment(); if (thisPropAttribs.noInline != null && tomlValue is TomlTable table) table.ForceNoInline = true; diff --git a/Tomlet/TomlSimpleCommentProvider.cs b/Tomlet/TomlSimpleCommentProvider.cs new file mode 100644 index 0000000..c5cb530 --- /dev/null +++ b/Tomlet/TomlSimpleCommentProvider.cs @@ -0,0 +1,16 @@ +namespace Tomlet; + +internal class TomlSimpleCommentProvider : ICommentProvider +{ + private readonly string _comment; + + public TomlSimpleCommentProvider(string comment) + { + _comment = comment; + } + + public string GetComment() + { + return _comment; + } +} \ No newline at end of file