Skip to content

Commit c6e238d

Browse files
ThisAssembly.Resources Improvements (#157)
* Improve collection of extensions `HashSet<>` is not cacheable, so it will not be effective at reducing the number of calls to generator. This will reduce performance. Switching to `.Collect()`/`ImmutableArray` will allow the caching system to work as intended. * Add support for files w/ different extensions; invalid filenames Fixes #155 Fixes #156
1 parent 2923ea3 commit c6e238d

File tree

7 files changed

+81
-30
lines changed

7 files changed

+81
-30
lines changed

src/ThisAssembly.Resources/CSharp.sbntxt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@
3333
{{ func render }}
3434
public static partial class {{ $0.Name }}
3535
{
36-
{{~ if $0.Resource ~}}
37-
{{- resource $0.Resource ~}}
36+
{{~ if $0.Resources ~}}
37+
{{~ for $r in $0.Resources ~}}
38+
{{- resource $r ~}}
39+
{{~ end ~}}
3840
{{~ else ~}}
3941
{{ render $0.NestedArea }}
4042
{{~ end ~}}

src/ThisAssembly.Resources/Model.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Linq;
66
using System.Reflection;
7+
using System.Text.RegularExpressions;
78

89
[DebuggerDisplay("Values = {RootArea.Values.Count}")]
910
record Model(Area RootArea)
@@ -15,24 +16,36 @@ record Model(Area RootArea)
1516
record Area(string Name)
1617
{
1718
public Area? NestedArea { get; private set; }
18-
public Resource? Resource { get; private set; }
19+
public IEnumerable<Resource>? Resources { get; private set; }
1920

20-
public static Area Load(Resource resource, string rootArea = "Resources")
21+
public static Area Load(string basePath, List<Resource> resources, string rootArea = "Resources")
2122
{
2223
var root = new Area(rootArea);
2324

2425
// Splits: ([area].)*[name]
2526
var area = root;
26-
var parts = resource.Name.Split(new[] { "\\", "/" }, StringSplitOptions.RemoveEmptyEntries);
27-
foreach (var part in parts.AsSpan()[..^1])
27+
var parts = basePath.Split(new[] { "\\", "/" }, StringSplitOptions.RemoveEmptyEntries);
28+
var end = resources.Count == 1 ? ^1 : ^0;
29+
30+
foreach (var part in parts.AsSpan()[..end])
2831
{
29-
area.NestedArea = new Area(part);
32+
var partStr = SanitizePart(part);
33+
area.NestedArea = new Area(partStr);
3034
area = area.NestedArea;
3135
}
3236

33-
area.Resource = resource with { Name = Path.GetFileNameWithoutExtension(parts[^1]), Path = resource.Name, };
37+
area.Resources = resources;
3438
return root;
3539
}
40+
41+
static readonly Regex invalidCharsRegex = new(@"\W");
42+
static string SanitizePart(string? part)
43+
{
44+
var partStr = invalidCharsRegex.Replace(part, "_");
45+
if (char.IsDigit(partStr[0]))
46+
partStr = "_" + partStr;
47+
return partStr;
48+
}
3649
}
3750

3851
[DebuggerDisplay("{Name}")]

src/ThisAssembly.Resources/ResourcesGenerator.cs

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Collections.Immutable;
33
using System.IO;
44
using System.Linq;
55
using System.Text;
@@ -30,47 +30,75 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3030
x.Right.GetOptions(x.Left).TryGetValue("build_metadata.EmbeddedResource.Value", out var resourceName);
3131
x.Right.GetOptions(x.Left).TryGetValue("build_metadata.EmbeddedResource.Kind", out var kind);
3232
x.Right.GetOptions(x.Left).TryGetValue("build_metadata.EmbeddedResource.Comment", out var comment);
33-
return (resourceName!, kind, comment: string.IsNullOrWhiteSpace(comment) ? null : comment);
33+
return (resourceName: resourceName!, kind, comment: string.IsNullOrWhiteSpace(comment) ? null : comment);
3434
})
35+
.Collect()
3536
.Combine(context.AnalyzerConfigOptionsProvider
36-
.Select((p, _) =>
37+
.SelectMany((p, _) =>
3738
{
3839
if (!p.GlobalOptions.TryGetValue("build_property.EmbeddedResourceStringExtensions", out var extensions) ||
3940
extensions == null)
40-
return new HashSet<string>();
41+
return Array.Empty<string>();
4142

42-
return new HashSet<string>(extensions.Split('|'), StringComparer.OrdinalIgnoreCase);
43-
}));
43+
return extensions.Split('|');
44+
})
45+
.WithComparer(StringComparer.OrdinalIgnoreCase)
46+
.Collect());
4447

4548
context.RegisterSourceOutput(
4649
files,
4750
GenerateSource);
4851
}
4952

50-
static void GenerateSource(SourceProductionContext spc, ((string resourceName, string? kind, string? comment), HashSet<string> extensions) arg2)
53+
static void GenerateSource(
54+
SourceProductionContext spc,
55+
(
56+
ImmutableArray<(string resourceName, string? kind, string? comment)> files,
57+
ImmutableArray<string> extensions) arg2)
5158
{
52-
var ((resourceName, kind, comment), extensions) = arg2;
59+
var (files, extensions) = arg2;
5360

5461
var file = "CSharp.sbntxt";
5562
var template = Template.Parse(EmbeddedResource.GetContent(file), file);
5663

57-
var isText = kind?.Equals("text", StringComparison.OrdinalIgnoreCase) == true ||
58-
extensions.Contains(Path.GetExtension(resourceName));
59-
var root = Area.Load(new Resource(resourceName, comment, isText));
60-
var model = new Model(root);
64+
var groupsWithoutExtensions = files
65+
.GroupBy(f => Path.Combine(
66+
Path.GetDirectoryName(f.resourceName),
67+
Path.GetFileNameWithoutExtension(f.resourceName)));
68+
foreach (var group in groupsWithoutExtensions)
69+
{
70+
var basePath = group.Key;
71+
var resources = group
72+
.Select(f =>
73+
{
74+
var isText = f.kind?.Equals("text", StringComparison.OrdinalIgnoreCase) == true ||
75+
extensions.Contains(Path.GetExtension(f.resourceName));
76+
var name = group.Count() == 1
77+
? Path.GetFileNameWithoutExtension(f.resourceName)
78+
: Path.GetExtension(f.resourceName)[1..];
79+
return new Resource(name, f.comment, isText)
80+
{
81+
Path = f.resourceName,
82+
};
83+
})
84+
.ToList();
85+
86+
var root = Area.Load(basePath, resources);
87+
var model = new Model(root);
6188

62-
var output = template.Render(model, member => member.Name);
89+
var output = template.Render(model, member => member.Name);
6390

64-
// Apply formatting since indenting isn't that nice in Scriban when rendering nested
65-
// structures via functions.
66-
output = Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseCompilationUnit(output)
67-
.NormalizeWhitespace()
68-
.GetText()
69-
.ToString();
91+
// Apply formatting since indenting isn't that nice in Scriban when rendering nested
92+
// structures via functions.
93+
output = Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseCompilationUnit(output)
94+
.NormalizeWhitespace()
95+
.GetText()
96+
.ToString();
7097

71-
spc.AddSource(
72-
$"{resourceName.Replace('\\', '.').Replace('/', '.')}.g.cs",
73-
SourceText.From(output, Encoding.UTF8));
98+
spc.AddSource(
99+
$"{basePath.Replace('\\', '.').Replace('/', '.')}.g.cs",
100+
SourceText.From(output, Encoding.UTF8));
101+
}
74102
}
75103
}
76104
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
body {
2+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(function () { })();

src/ThisAssembly.Tests/Tests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,8 @@ public void CanUseTextResource()
5353
public void CanUseByteResource()
5454
=> Assert.NotNull(ThisAssembly.Resources.Content.Styles.Main.GetBytes());
5555

56+
[Fact]
57+
public void CanUseSameNameDifferentExtensions()
58+
=> Assert.NotNull(ThisAssembly.Resources.Content.Swagger.swagger_ui.css.GetBytes());
5659
}
5760
}

src/ThisAssembly.Tests/ThisAssembly.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
<EmbeddedResource Include="Content/Styles/Custom.css" Kind="Text" Comment="Secondary CSS" />
4444
<EmbeddedResource Include="Content/Styles/Main.css" Comment="Primary CSS" />
4545
<EmbeddedResource Include="Content/Docs/*" />
46+
<EmbeddedResource Include="Content/Swagger/*" />
47+
<None Remove="Content/Swagger/*" />
4648
</ItemGroup>
4749

4850
<PropertyGroup>

0 commit comments

Comments
 (0)