Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: runtime comment provider #47

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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];
}
}
33 changes: 29 additions & 4 deletions Tomlet.Tests/CommentSerializationTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using Tomlet.Models;
using Tomlet.Tests.CommentProvider;
using Tomlet.Tests.TestModelClasses;
using Xunit;

Expand Down Expand Up @@ -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);
Expand All @@ -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";
Expand All @@ -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);
Expand All @@ -115,10 +117,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;
}
}