diff --git a/src/ThisAssembly.Constants/Model.cs b/src/ThisAssembly.Constants/Model.cs index 25ec0b69..b48f7631 100644 --- a/src/ThisAssembly.Constants/Model.cs +++ b/src/ThisAssembly.Constants/Model.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp; [DebuggerDisplay("Values = {RootArea.Values.Count}")] record Model(Area RootArea) @@ -17,6 +19,27 @@ record Area(string Name, string Prefix) public List NestedAreas { get; init; } = new(); public List Values { get; init; } = new(); + static string EscapeIdentifier(string identifier) + { + if (string.IsNullOrWhiteSpace(identifier)) + { + return "_"; + } + + var replaced = identifier + .Select(c => SyntaxFacts.IsIdentifierPartCharacter(c) ? c : '_') + .ToArray(); + + var result = Regex.Replace(new string(replaced), "(_)+", "_"); + + if (!SyntaxFacts.IsIdentifierStartCharacter(result[0])) + { + result = "_" + result; + } + + return result; + } + public static Area Load(List constants, string rootArea = "Constants") { var root = new Area(rootArea, ""); @@ -24,15 +47,18 @@ public static Area Load(List constants, string rootArea = "Constants") foreach (var constant in constants) { // Splits: ([area].)*[name] - var parts = constant.Name.Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries); + var parts = constant.Name.Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries) + .Select(EscapeIdentifier) + .ToArray(); + if (parts.Length <= 1) { - root.Values.Add(new Constant(constant.Name, constant.Value, constant.Comment)); + root.Values.Add(constant with { Name = EscapeIdentifier(constant.Name) }); } else { var area = GetArea(root, parts.Take(parts.Length - 1)); - var value = new Constant(parts.Skip(parts.Length - 1).First(), constant.Value, constant.Comment); + var value = constant with { Name = parts.Skip(parts.Length - 1).First() }; area.Values.Add(value); } @@ -74,4 +100,4 @@ static Area GetArea(Area area, IEnumerable areaPath) } [DebuggerDisplay("{Name} = {Value}")] -record Constant(string Name, string? Value, string? Comment); \ No newline at end of file +record Constant(string Name, string? Value, string? Comment); diff --git a/src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt b/src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/ThisAssembly.Tests/Content/Docs/12. Readme (copy).txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ThisAssembly.Tests/Tests.cs b/src/ThisAssembly.Tests/Tests.cs index 7a11d414..d9944274 100644 --- a/src/ThisAssembly.Tests/Tests.cs +++ b/src/ThisAssembly.Tests/Tests.cs @@ -35,6 +35,10 @@ public void CanUseConstants() public void CanUseFileConstants() => Assert.Equal(ThisAssembly.Constants.Content.Docs.License, Path.Combine("Content", "Docs", "License.md")); + [Fact] + public void CanUseFileConstantInvalidIdentifier() + => Assert.Equal(ThisAssembly.Constants.Content.Docs._12._Readme_copy_, Path.Combine("Content", "Docs", "12. Readme (copy).txt")); + [Fact] public void CanUseFileConstantLinkedFile() => Assert.Equal(ThisAssembly.Constants.Included.Readme, Path.Combine("Included", "Readme.txt"));