Skip to content

Commit

Permalink
feat: runtime comment provider
Browse files Browse the repository at this point in the history
  • Loading branch information
ExerciseBook committed Nov 29, 2024
1 parent 4393f63 commit cc60916
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 21 deletions.
20 changes: 20 additions & 0 deletions Tomlet.Tests/CommentProvider/TestInlineCommentProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;

namespace Tomlet.Tests.CommentProvider;

public class TestInlineCommentProvider : ICommentProvider
{
public static Dictionary<string, string> Comments = new Dictionary<string, string>();

private readonly string _name;

public TestInlineCommentProvider(string name)
{
_name = name;
}

public string GetComment()
{
return Comments[_name];
}
}
20 changes: 20 additions & 0 deletions Tomlet.Tests/CommentProvider/TestPrecedingCommentProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;

namespace Tomlet.Tests.CommentProvider;

public class TestPrecedingCommentProvider : ICommentProvider
{
public static Dictionary<string, string> Comments = new Dictionary<string, string>();

private readonly string _name;

public TestPrecedingCommentProvider(string name)
{
_name = name;
}

public string GetComment()
{
return Comments[_name];
}
}
34 changes: 30 additions & 4 deletions Tomlet.Tests/CommentSerializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using Tomlet.Models;
using Tomlet.Tests.CommentProvider;
using Tomlet.Tests.TestModelClasses;
using Xunit;
using Xunit.Abstractions;

namespace Tomlet.Tests;

Expand Down Expand Up @@ -72,7 +75,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);
Expand All @@ -91,7 +94,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";
Expand All @@ -103,7 +106,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);
Expand All @@ -115,10 +118,33 @@ public void CommentAttributesWork()
var config = TomletMain.To<ExampleMailboxConfigClass>(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);
}
}
15 changes: 15 additions & 0 deletions Tomlet.Tests/TestModelClasses/CommentProviderTestModel.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
33 changes: 33 additions & 0 deletions Tomlet/Attributes/TomlCommentProviderAttribute.cs
Original file line number Diff line number Diff line change
@@ -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[] { };
}
}
8 changes: 3 additions & 5 deletions Tomlet/Attributes/TomlInlineCommentAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
15 changes: 15 additions & 0 deletions Tomlet/Attributes/TomlInlineCommentProviderAttribute.cs
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
8 changes: 3 additions & 5 deletions Tomlet/Attributes/TomlPrecedingCommentAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
15 changes: 15 additions & 0 deletions Tomlet/Attributes/TomlPrecedingCommentProviderAttribute.cs
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
18 changes: 18 additions & 0 deletions Tomlet/CommentProviderUtil.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
3 changes: 2 additions & 1 deletion Tomlet/Extensions/GenericExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pai

public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrEmpty(s) || string.IsNullOrEmpty(s.Trim());

internal static T? GetCustomAttribute<T>(this MemberInfo info) where T : Attribute => info.GetCustomAttributes(false).Where(a => a is T).Cast<T>().FirstOrDefault();
internal static T? GetCustomAttribute<T>(this MemberInfo info) where T : Attribute
=> info.GetCustomAttributes(false).Where(a => typeof(T).IsAssignableFrom(a.GetType())).Cast<T>().FirstOrDefault();

internal static void EnsureLegalChar(this int c, int currentLineNum)
{
Expand Down
6 changes: 6 additions & 0 deletions Tomlet/ICommentProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Tomlet;

public interface ICommentProvider
{
public string GetComment();
}
23 changes: 17 additions & 6 deletions Tomlet/TomlCompositeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,22 @@ public static TomlSerializationMethods.Serialize<object> For(Type type, TomlSeri

var fields = type.GetFields(memberFlags);
var fieldAttribs = fields
.ToDictionary(f => f, f => new {inline = GenericExtensions.GetCustomAttribute<TomlInlineCommentAttribute>(f), preceding = GenericExtensions.GetCustomAttribute<TomlPrecedingCommentAttribute>(f), noInline = GenericExtensions.GetCustomAttribute<TomlDoNotInlineObjectAttribute>(f)});
.ToDictionary(f => f, f => new
{
inline = GenericExtensions.GetCustomAttribute<TomlInlineCommentProviderAttribute>(f),
preceding = GenericExtensions.GetCustomAttribute<TomlPrecedingCommentProviderAttribute>(f),
noInline = GenericExtensions.GetCustomAttribute<TomlDoNotInlineObjectAttribute>(f)
});
var props = type.GetProperties(memberFlags)
.ToArray();
var propAttribs = props
.ToDictionary(p => p, p => new {inline = GenericExtensions.GetCustomAttribute<TomlInlineCommentAttribute>(p), preceding = GenericExtensions.GetCustomAttribute<TomlPrecedingCommentAttribute>(p), prop = GenericExtensions.GetCustomAttribute<TomlPropertyAttribute>(p), noInline = GenericExtensions.GetCustomAttribute<TomlDoNotInlineObjectAttribute>(p)});
.ToDictionary(p => p, p => new
{
inline = GenericExtensions.GetCustomAttribute<TomlInlineCommentProviderAttribute>(p),
preceding = GenericExtensions.GetCustomAttribute<TomlPrecedingCommentProviderAttribute>(p),
prop = GenericExtensions.GetCustomAttribute<TomlPropertyAttribute>(p),
noInline = GenericExtensions.GetCustomAttribute<TomlDoNotInlineObjectAttribute>(p)
});

var isForcedNoInline = GenericExtensions.GetCustomAttribute<TomlDoNotInlineObjectAttribute>(type) != null;

Expand Down Expand Up @@ -75,8 +86,8 @@ public static TomlSerializationMethods.Serialize<object> 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;
Expand Down Expand Up @@ -104,8 +115,8 @@ public static TomlSerializationMethods.Serialize<object> 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;
Expand Down
16 changes: 16 additions & 0 deletions Tomlet/TomlSimpleCommentProvider.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit cc60916

Please sign in to comment.