Skip to content

Commit

Permalink
Update AutoMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
feast107 committed Sep 12, 2024
1 parent 9f6a7be commit 9045bfd
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 8 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ Auto generate anything you may want

### `Antelcat.AutoGen.ComponentModel` :

+ #### `[AutoMetadataFrom(Type, MemberTypes)]`
Auto generate code using `Template` from target type members

![AutoMetadata](./docs/AutoMetadata.png)

+ #### `[AutoStringTo(string, Accessibility)]` :

Auto generate string To extension

only on `assembly` and `static partial class`

![AutoStringTo](./docs/GenerateStringTo.png)
![AutoStringTo](./docs/AutoStringTo.png)

+ #### `Mapping` :

Expand All @@ -25,7 +30,7 @@ Auto generate anything you may want

> Only on `partial method`
![AutoMapTo](./docs/GenerateMap.png)
![AutoMapTo](./docs/AutoMap.png)

> You can use to generate `shallow copy`
Expand All @@ -49,7 +54,7 @@ Auto generate anything you may want

+ #### `[MapConstructor(params string[])]` :

Specified property to be added in constructor, will auto detect if `null`
Specified property to be added in constructor, will auto-detect if `null`


+ #### `[AutoFilePath]`:
Expand Down
File renamed without changes
Binary file added docs/AutoMetadata.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
23 changes: 23 additions & 0 deletions src/Antelcat.AutoGen.Sample/Models/Diagnostics/Sample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Reflection;
using Antelcat.AutoGen.ComponentModel.Diagnostic;

namespace Antelcat.AutoGen.Sample.Models.Diagnostics;


[AutoMetadataFrom(typeof(Simulator), MemberTypes.Property,
Leading = "public global::System.Collections.Generic.IEnumerable<string> Writables(){",
Template =
"""
#if {CanWrite}
yield return nameof({Name});
#endif
""",
Final = "}")]
public partial class Simulator
{
public string A { get; set; }
public string B { get; }
public string C { get; set; }
public string D { get; }
}
4 changes: 2 additions & 2 deletions src/Antelcat.AutoGen.Sample/Models/Mapping/FileDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Antelcat.AutoGen.Sample.Models.Mapping;

public partial class FileDescriptor : ObservableObject
{
public required string FullName { get; set; }
public virtual long Length { get; set; }
public required string FullName { get; set; }
public virtual long Length { get; set; }

[ObservableProperty] private string property;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\AutoKeyAccessorAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\AutoKeyEnumerableAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\AutoStringToAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Diagnostic\AutoMetadataFrom.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Diagnostic\AutoReport.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Diagnostic\AutoWatchAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\Mapping\AutoMapAttribute.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Reflection;
using Antelcat.AutoGen.ComponentModel.Abstractions;

namespace Antelcat.AutoGen.ComponentModel.Diagnostic
{
/// <summary>
/// Auto generate code using <see cref="Template"/> and members
/// specified by <see cref="MemberTypes"/> from given <see cref="Type"/>
/// </summary>
/// <param name="forType">Type which contains target members</param>
/// <param name="memberTypes">Types of the members</param>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true,
Inherited = false)]
public class AutoMetadataFrom(Type forType, MemberTypes memberTypes) : AutoGenAttribute
{
internal Type ForType => forType;
internal MemberTypes MemberTypes => memberTypes;

/// <summary>
/// Template applying to generate code, you can use
/// {Name} {PropertyType} {CanRead} ... those members
/// come from inherits of <see cref="MemberInfo"/>
/// </summary>
public string Template { get; set; } = string.Empty;

/// <summary>
/// Plain text added to the leading of generated code
/// </summary>
public string? Leading { get; set; }

/// <summary>
/// Plain text added to the final of generated code
/// </summary>
public string? Final { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Antelcat.AutoGen.ComponentModel.Diagnostic;
using Antelcat.AutoGen.SourceGenerators.Extensions;
using Antelcat.AutoGen.SourceGenerators.Generators.Base;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Antelcat.AutoGen.SourceGenerators.Generators.Diagnostic;

[Generator(LanguageNames.CSharp)]
public class MetadataGenerator : AttributeDetectBaseGenerator<AutoMetadataFrom>
{
private static IEnumerable<string> GetPlaceholders(string template)
{
foreach (var match in Regex.Matches(template, "{[\\w+((\\.)?)]+}"))
{
var str = match.ToString();
yield return str.Substring(1, str.Length - 2);
}
}

private static StringBuilder Resolve(MemberInfo info, StringBuilder stringBuilder)
{
foreach (var placeholder in GetPlaceholders(stringBuilder.ToString()))
{
object value = info;
foreach (var part in placeholder.Split('.'))
{
if (value is not MemberInfo memberInfo) continue;
var val = GetValue(memberInfo, part);
if (val is null) return stringBuilder;
value = val;
}

stringBuilder = stringBuilder.Replace('{' + placeholder + '}',
value is Feast.CodeAnalysis.CompileTime.Type compile
? compile.Symbol.GetFullyQualifiedName()
: value.ToString());
}

return stringBuilder;
}

private static object? GetValue(MemberInfo info, string propertyName)
{
var type = info.GetType();
return PropsMap.GetOrAdd(info.GetType(),
_ =>
{
ConcurrentDictionary<string, PropertyInfo?> ret = [];
var prop = type.GetProperty(propertyName);
if (prop is null) return ret;
ret.TryAdd(propertyName, prop);
return ret;
}).GetOrAdd(propertyName, _ => type.GetProperty(propertyName))?
.GetValue(info);
}

private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo?>> PropsMap = [];

protected override bool FilterSyntax(SyntaxNode node) => true;

protected override void Initialize(SourceProductionContext context,
Compilation compilation,
ImmutableArray<GeneratorAttributeSyntaxContext> syntaxArray)
{
foreach (var groupedSyntaxContext in syntaxArray.GroupBy(x => (x.TargetSymbol as INamedTypeSymbol)!,
SymbolEqualityComparer.Default))
{
var @class = (groupedSyntaxContext.Key as INamedTypeSymbol)!;
foreach (var syntaxContext in groupedSyntaxContext)
{
foreach (var (metadata, index) in syntaxContext.Attributes.GetAttributes<AutoMetadataFrom>()
.Select((x, i) => (x, i)))
{
var partial = @class.PartialTypeDeclaration();
List<string> members = [];
if (metadata.Leading != null) members.Add(metadata.Leading);
var target = metadata.ForType;
var fileName =
$"{@class.ToType().QualifiedFullFileName()}_From_{target.QualifiedFullFileName()}_{index}.cs";

const BindingFlags flags = BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static;
Map(MemberTypes.Field, () => target.GetFields(flags).Where(x => !x.IsSpecialName));
Map(MemberTypes.Property, () => target.GetProperties(flags));
Map(MemberTypes.Constructor, () => target.GetConstructors(flags));
Map(MemberTypes.NestedType, () => target.GetNestedTypes(flags));
Map(MemberTypes.Event, () => target.GetEvents(flags));
Map(MemberTypes.Method, () => target.GetMethods(flags).Where(x => !x.IsSpecialName));

if (metadata.Final != null) members.Add(metadata.Final);
var member = ParseMemberDeclaration(string.Join("", members));
if (member != null) partial = partial.AddMembers(member);

var file = CompilationUnit()
.AddPartialType(@class, x => partial)
.NormalizeWhitespace();
context.AddSource(fileName, file.GetText(Encoding.UTF8));
continue;

void Map(MemberTypes memberTypes, Func<IEnumerable<MemberInfo>> memberGetter)
{
if (!metadata.MemberTypes.HasFlag(memberTypes)) return;
members.AddRange(memberGetter()
.Select(field => Resolve(field, new StringBuilder(metadata.Template))
.ToString())
);
}
}
}
}
}
}
6 changes: 3 additions & 3 deletions src/Antelcat.AutoGen/Antelcat.AutoGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
<IsPackable>true</IsPackable>
<LangVersion>preview</LangVersion>

<Version>1.2.7</Version>
<FileVersion>1.2.7</FileVersion>
<AssemblyVersion>1.2.7</AssemblyVersion>
<Version>2.0.0-preview1</Version>
<FileVersion>2.0.0</FileVersion>
<AssemblyVersion>2.0.0</AssemblyVersion>

<Authors>Antelcat</Authors>
<Title>Antelcat.AutoGen</Title>
Expand Down

0 comments on commit 9045bfd

Please sign in to comment.