diff --git a/.github/workflows/cd-nuget.yml b/.github/workflows/cd-nuget.yml
new file mode 100644
index 0000000..3c26d3f
--- /dev/null
+++ b/.github/workflows/cd-nuget.yml
@@ -0,0 +1,13 @@
+on: { push: { tags: ["v[0-9]+.[0-9]+.[0-9]+*"] } }
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-dotnet@v3
+ - run:
+ dotnet build src -c debug
+ dotnet test
+ - run: dotnet pack -p:Version=$(echo ${{ github.ref_name }} | sed 's/^v//') -p:RepositoryCommit=${{ github.sha }}
+ - run: dotnet nuget push ./artifacts/package/release/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..34fc7c7
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+ 12.0
+ enable
+ enable
+ true
+ false
+
+
\ No newline at end of file
diff --git a/PodNet.EmbeddedTexts.sln b/PodNet.EmbeddedTexts.sln
new file mode 100644
index 0000000..91dd921
--- /dev/null
+++ b/PodNet.EmbeddedTexts.sln
@@ -0,0 +1,55 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{3ED2BD42-60E2-4576-90EF-B70F1FADCEC1}"
+ ProjectSection(SolutionItems) = preProject
+ .gitignore = .gitignore
+ .github\workflows\cd-nuget.yml = .github\workflows\cd-nuget.yml
+ Directory.Build.props = Directory.Build.props
+ LICENSE = LICENSE
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{52B4873F-9533-4C80-A473-C7B3B4510C13}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PodNet.EmbeddedTexts", "src\EmbeddedTexts\PodNet.EmbeddedTexts.csproj", "{AB59FA90-615E-4BEC-8986-B8087EE570CF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{58E83F42-E7F1-4A20-9E52-D856C9AD933E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PodNet.EmbeddedTexts.Tests", "tests\EmbeddedTexts.Tests\PodNet.EmbeddedTexts.Tests.csproj", "{E70BE6A5-0A94-41BD-86B8-E217793342C8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PodNet.EmbeddedTexts.IntegrationTests", "tests\EmbeddedTexts.IntegrationTests\PodNet.EmbeddedTexts.IntegrationTests.csproj", "{16360558-324A-4BD1-A565-8EF9ACB090A3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {AB59FA90-615E-4BEC-8986-B8087EE570CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB59FA90-615E-4BEC-8986-B8087EE570CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB59FA90-615E-4BEC-8986-B8087EE570CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB59FA90-615E-4BEC-8986-B8087EE570CF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E70BE6A5-0A94-41BD-86B8-E217793342C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E70BE6A5-0A94-41BD-86B8-E217793342C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E70BE6A5-0A94-41BD-86B8-E217793342C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E70BE6A5-0A94-41BD-86B8-E217793342C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {16360558-324A-4BD1-A565-8EF9ACB090A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {16360558-324A-4BD1-A565-8EF9ACB090A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {16360558-324A-4BD1-A565-8EF9ACB090A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {16360558-324A-4BD1-A565-8EF9ACB090A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {AB59FA90-615E-4BEC-8986-B8087EE570CF} = {52B4873F-9533-4C80-A473-C7B3B4510C13}
+ {E70BE6A5-0A94-41BD-86B8-E217793342C8} = {58E83F42-E7F1-4A20-9E52-D856C9AD933E}
+ {16360558-324A-4BD1-A565-8EF9ACB090A3} = {58E83F42-E7F1-4A20-9E52-D856C9AD933E}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {099F2425-924C-4626-91B8-3025DE1D4B3D}
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
index d861a49..90b8551 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,104 @@
-# PodNet.EmbeddedTexts
-A simple C# code generator that allows for embedding text file content into your code.
+# PodNet.EmbeddedTexts [![Nuget](https://img.shields.io/nuget/v/PodNet.EmbeddedTexts)](https://www.nuget.org/packages/PodNet.EmbeddedTexts/)
+A simple C# incremental code generator that allows for efficiently embedding text file content into your code.
+
+## Usage
+1. Add the [`PodNet.EmbeddedTexts`](https://www.nuget.org/packages/PodNet.EmbeddedTexts/) NuGet package to your .NET project.
+2. Add some text files (can be code files, can be markdown, plain text etc.) to your project you want the contents of to be available to you **at compile time**. Mark the files as having a build action of `AdditionalFile`. The most straightforward way to do this is by editing the `.csproj` project file and adding a pattern in an ``:
+ ```csproj
+
+
+
+ ```
+ You can do the above for individual files as well if you so choose. In this case you can even use the Visual Studio Properties Window (F4 by default, while a file is being selected in Solution Explorer) and set the `Build action` property of the file(s) to *"C# analyzer additional file"*. Once you do, Visual Studio will edit your `.csproj` file accordingly.
+3. Once you set this up, you'll immediately find a `public static string Content { get; }` property generated on a class in a namespace according to the structure of your project, which returns the string content of the file. So, if your root namespace for the project is `MyProject`, and you have an `AdditionalFiles` element in your project's `Files/MyFile.txt` file with content `file contents`, you'll be able to call:
+ ```csharp
+ Console.WriteLine(Files.MyFile_txt.Content); // Writes: "file contents"
+ ```
+
+## Additional configuration
+
+The generator is **on by default** for every `AdditionalFiles` text file you have in your project, and will include the file contents in your compilation. If this is not your desired use-case, you have a few options.
+
+Additionally, you can configure the name of the generated namespace and class for every piece of content.
+
+> [!NOTE]
+> It's imporant to mention that the below is standard configuration practice for MSBuild items. The MSBuild project files (like .csproj or Directory.build.props) define items in the children of `ItemGroup` elements. The `AdditionalFiles` element can be used by all modern source generators and are not specific to `PodNet.EmbeddedTexts`. One quirk of MSBuild items is that they can be `Include`d individually or by globbing patterns, but the execution and parsing of these files is (mostly) sequential, so if you have any files `Include`d by any patterns, you then have to `Update` them instead of `Include`-ing again.
+
+
+### Make the generator opt-in
+
+Set the MSBuild property `PodNetAutoEmbedAdditionalTexts` to `false` to disable automatic generation for all `AdditionalFiles`. Then, for each file you wish to embed in the compilation, add a `PodNet_EmbedText="true"` attribute its `AdditionalFiles` item.
+
+```csproj
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+This is useful if you have any source generators enabled that work on text files you wish to not include in the compilation.
+
+> [!WARNING]
+> Remember that including all text files in the compilation will essentially make your assemblies/executables larger by about the size of the file. The generated `static` class and property won't be loaded into memory until first being referenced, but this can incur a performance hit when embedding larger files.
+
+### Opt-out of generation for files or folders
+
+Whether you have the generator automatically generating or not, you can explicitly opt-out of generation by setting `PodNet_EmbedText="false"`.
+
+```csproj
+
+
+
+
+
+
+
+```
+
+### Configuring the generated class name and namespace
+
+You can set the `PodNet_EmbedTextNamespace` and `PodNet_EmbedTextClassName` properties on the items to override the default namespace and class name.
+
+```csproj
+
+
+
+
+
+
+
+```
+
+The above results in: `OtherNamespace.MyFileTXT.Content` being the property that holds the file content.
+
+### Advanced parameterization
+
+Don't be shy to use MSBuild properties, well-known metadata and such to configure the generator.
+
+```
+
+```
+
+The above includes all `.cs` files (and other files that are at that point included in the compilation) into the source itself, in the `MyProject.CompiledFiles` namespace, with the class name being that of the filename without the extension.
+
+
+## Contributing and Support
+
+This project is intended to be widely usable, but no warranties are provided. If you want to contact us, feel free to do so in the repo's [[Discussions](https://github.com/podNET-Hungary/PodNet.EnumValues/discussions)], at our website at [podnet.hu](https://podnet.hu), or find us anywhere from [LinkedIn](https://www.linkedin.com/company/podnet-hungary/) and [Patreon](https://www.patreon.com/podNETHungary) to [Meetup](https://www.meetup.com/budapest-net-meetup/), [YouTube](https://www.youtube.com/@podNET) or [X](https://twitter.com/podNET_Hungary).
+
+Any kinds of contributions from issues to PRs and open discussions are welcome!
+
+Don't forget to give us a ⭐ if you like this repo (it's free to give kudos!) or share it on socials, but we're not averse to offering you some benefits at our [🍻 Patreon 🍻](https://www.patreon.com/podNETHungary) either, if you're so inclined!
\ No newline at end of file
diff --git a/src/EmbeddedTexts/EmbeddedTextsGenerator.cs b/src/EmbeddedTexts/EmbeddedTextsGenerator.cs
new file mode 100644
index 0000000..8aac830
--- /dev/null
+++ b/src/EmbeddedTexts/EmbeddedTextsGenerator.cs
@@ -0,0 +1,103 @@
+using Microsoft.CodeAnalysis;
+using PodNet.Analyzers.CodeAnalysis;
+using System.Text;
+
+namespace PodNet.EmbeddedTexts;
+
+[Generator(LanguageNames.CSharp)]
+public sealed class EmbeddedTextsGenerator : IIncrementalGenerator
+{
+ public const string EmbedAdditionalTextsConfigProperty = "PodNetAutoEmbedAdditionalTexts";
+ public const string EmbedTextMetadataProperty = "PodNet_EmbedText";
+ public const string EmbedTextNamespaceMetadataProperty = "PodNet_EmbedTextNamespace";
+ public const string EmbedTextClassNameMetadataProperty = "PodNet_EmbedTextClassName";
+
+ public record EmbeddedTextItemOptions(
+ string? RootNamespace,
+ string? ProjectDirectory,
+ string? ItemNamespace,
+ string? ItemClassName,
+ bool Enabled,
+ AdditionalText Text);
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var allTexts = context.AdditionalTextsProvider.Combine(context.AnalyzerConfigOptionsProvider)
+ .Select((item, _) =>
+ {
+ var (text, options) = item;
+ var itemOptions = options.GetOptions(text);
+ var globalEnabled = string.Equals(options.GlobalOptions.GetBuildProperty(EmbedAdditionalTextsConfigProperty) ?? "true", "true", StringComparison.OrdinalIgnoreCase);
+ var itemSwitch = itemOptions.GetAdditionalTextMetadata(EmbedTextMetadataProperty);
+ var itemEnabled = string.Equals(itemSwitch, "true", StringComparison.OrdinalIgnoreCase);
+ var itemDisabled = string.Equals(itemSwitch, "false", StringComparison.OrdinalIgnoreCase);
+ return new EmbeddedTextItemOptions(
+ RootNamespace: options.GlobalOptions.GetRootNamespace(),
+ ProjectDirectory: options.GlobalOptions.GetProjectDirectory(),
+ ItemNamespace: itemOptions.GetAdditionalTextMetadata(EmbedTextNamespaceMetadataProperty),
+ ItemClassName: itemOptions.GetAdditionalTextMetadata(EmbedTextClassNameMetadataProperty),
+ Enabled: (globalEnabled || itemEnabled) && !itemDisabled,
+ Text: text);
+ });
+
+ var enabledTexts = allTexts.Where(e => e.Enabled);
+
+ context.RegisterSourceOutput(enabledTexts, static (context, item) =>
+ {
+ if (item.Text.GetText() is not { Lines: var lines } text)
+ return;
+
+ if (item.ProjectDirectory is not { Length: > 0 })
+ throw new InvalidOperationException("No project directory.");
+ if (item.Text.Path is not { Length: > 0 })
+ throw new InvalidOperationException("Path not found for file.");
+
+ var className = TextProcessing.GetClassName(item.ItemClassName is { Length: > 0 }
+ ? item.ItemClassName
+ : Path.GetFileName(item.Text.Path));
+
+ var relativeFolderPath = PathProcessing.GetRelativePath(item.ProjectDirectory, Path.GetDirectoryName(item.Text.Path));
+ var relativeFilePath = PathProcessing.GetRelativePath(item.ProjectDirectory, item.Text.Path);
+
+ var @namespace = TextProcessing.GetNamespace(item.ItemNamespace is { Length: > 0 }
+ ? item.ItemNamespace
+ : $"{item.RootNamespace}.{relativeFolderPath}");
+
+ var separator = new string('"', 3);
+ while (lines.Any(l => l.Text?.ToString().Contains(separator) == true))
+ separator += '\"';
+
+ var sourceBuilder = new StringBuilder(text.Length * 2 + lines.Count * 8 + 300);
+
+ sourceBuilder.AppendLine($$"""
+ //
+
+ namespace {{@namespace}};
+
+ public static partial class {{className}}
+ {
+ ///
+ /// Contents of the file at '{{relativeFilePath}}':
+ ///
+ """);
+
+ foreach (var line in lines)
+ {
+ sourceBuilder.AppendLine($$"""
+ /// {{line.ToString().Replace("<", "<").Replace(">", ">")}}
+ """);
+ }
+
+ sourceBuilder.AppendLine($$"""
+ ///
+ ///
+ public static string Content { get; } = {{separator}}
+ {{text}}
+ {{separator}};
+ }
+ """);
+
+ context.AddSource($"{@namespace}/{className}.g.cs", sourceBuilder.ToString());
+ });
+ }
+}
diff --git a/src/EmbeddedTexts/PodNet.EmbeddedTexts.csproj b/src/EmbeddedTexts/PodNet.EmbeddedTexts.csproj
new file mode 100644
index 0000000..c9a2eeb
--- /dev/null
+++ b/src/EmbeddedTexts/PodNet.EmbeddedTexts.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netstandard2.0
+ PodNet.EmbeddedTexts
+ A simple C# code generator that allows for embedding text file content into your code.
+ EmbeddedTexts, PodNet, generator, embedded, texts, resx, resource, awesome
+ true
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EmbeddedTexts/build/PodNet.EmbeddedTexts.props b/src/EmbeddedTexts/build/PodNet.EmbeddedTexts.props
new file mode 100644
index 0000000..e287d49
--- /dev/null
+++ b/src/EmbeddedTexts/build/PodNet.EmbeddedTexts.props
@@ -0,0 +1,12 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/EmbeddedTextsGeneratorIntegrationTest.cs b/tests/EmbeddedTexts.IntegrationTests/EmbeddedTextsGeneratorIntegrationTest.cs
new file mode 100644
index 0000000..4f5e582
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/EmbeddedTextsGeneratorIntegrationTest.cs
@@ -0,0 +1,55 @@
+namespace PodNet.EmbeddedTexts.IntegrationTests;
+
+///
+/// This tests that, given the provided files are correctly added to AdditionaFiles, the correct
+/// classes are generated in the correct namespaces with the correct API surface (property), and it returns
+/// the file content as the result (unless ignored). These "tests" won't even compile if the correct
+/// APIs are not generated.
+///
+[TestClass]
+public class EmbeddedTextsGeneratorIntegrationTest
+{
+ [TestMethod]
+ public void TestContentIsEmbedded()
+ {
+ Assert.AreEqual("""
+ Text Content
+ Is Embedded
+ """, Files.Text_txt.Content);
+ }
+
+ [TestMethod]
+ public void IgnoredContentIsNotEmbedded()
+ {
+ // Given that the Files\Ignored\** pattern is excluded by setting "PodNet_EmbedText" to "false", the following shouldn't compile:
+ // Files.Ignored.Ignored_txt.Content;
+ var undefined = "PodNet.EmbeddedTexts.IntegrationTests.Files.Ignored.Ignored_txt";
+ var compilation = Analyzers.Testing.CSharp.PodCSharpCompilation.Create([$$"""
+ class ShouldError
+ {
+ string WontCompile() => {{undefined}}.Content;
+ };
+ """]);
+ var diagnostics = compilation.CurrentCompilation.GetDiagnostics();
+ Assert.IsTrue(diagnostics.Any(d => d is
+ {
+ Severity: Microsoft.CodeAnalysis.DiagnosticSeverity.Error,
+ Id: "CS0234", // {The type or namespace name '{0}' does not exist in the namespace '{1}' (are you missing an assembly reference?)}
+ Location: { SourceSpan: var span, SourceTree: var tree }
+ } && tree?.GetText().GetSubText(span).ToString() == undefined));
+ }
+
+ [TestMethod]
+ public void UnignoredContentIsEmbedded()
+ {
+ Assert.AreEqual("Unignored", Files.Ignored.Unignored_txt.Content);
+ }
+
+ [TestMethod]
+ public void CustomClassAndNamespaceNamesCanBeSupplied()
+ {
+ Assert.IsNotNull(Files.TestClass.Content);
+ Assert.IsNotNull(TestNamespace.TestSubNamespace.CustomNamespace_txt.Content);
+ Assert.IsNotNull(TestNamespace.TestSubNamespace.TestClass.Content);
+ }
+}
diff --git a/tests/EmbeddedTexts.IntegrationTests/Files/CustomClass.txt b/tests/EmbeddedTexts.IntegrationTests/Files/CustomClass.txt
new file mode 100644
index 0000000..a9bed00
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/Files/CustomClass.txt
@@ -0,0 +1 @@
+CustomClass
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/Files/CustomClassAndNamespace.txt b/tests/EmbeddedTexts.IntegrationTests/Files/CustomClassAndNamespace.txt
new file mode 100644
index 0000000..d1711a3
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/Files/CustomClassAndNamespace.txt
@@ -0,0 +1 @@
+CustomClassAndNamespace
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/Files/CustomNamespace.txt b/tests/EmbeddedTexts.IntegrationTests/Files/CustomNamespace.txt
new file mode 100644
index 0000000..a7e0d81
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/Files/CustomNamespace.txt
@@ -0,0 +1 @@
+CustomNamespace
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/Files/Ignored/Ignored.txt b/tests/EmbeddedTexts.IntegrationTests/Files/Ignored/Ignored.txt
new file mode 100644
index 0000000..bf65270
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/Files/Ignored/Ignored.txt
@@ -0,0 +1 @@
+Ignored
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/Files/Ignored/Unignored.txt b/tests/EmbeddedTexts.IntegrationTests/Files/Ignored/Unignored.txt
new file mode 100644
index 0000000..991a4b6
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/Files/Ignored/Unignored.txt
@@ -0,0 +1 @@
+Unignored
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/Files/Text.txt b/tests/EmbeddedTexts.IntegrationTests/Files/Text.txt
new file mode 100644
index 0000000..0706e54
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/Files/Text.txt
@@ -0,0 +1,2 @@
+Text Content
+Is Embedded
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.IntegrationTests/PodNet.EmbeddedTexts.IntegrationTests.csproj b/tests/EmbeddedTexts.IntegrationTests/PodNet.EmbeddedTexts.IntegrationTests.csproj
new file mode 100644
index 0000000..9c73a46
--- /dev/null
+++ b/tests/EmbeddedTexts.IntegrationTests/PodNet.EmbeddedTexts.IntegrationTests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ net8.0
+ $(ArtifactsPath)/package
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/EmbeddedTexts.Tests/EmbeddedTextGeneratorTests.cs b/tests/EmbeddedTexts.Tests/EmbeddedTextGeneratorTests.cs
new file mode 100644
index 0000000..27c7685
--- /dev/null
+++ b/tests/EmbeddedTexts.Tests/EmbeddedTextGeneratorTests.cs
@@ -0,0 +1,119 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using PodNet.Analyzers.Testing.CSharp;
+using System.Collections.Immutable;
+using Fakes = PodNet.Analyzers.Testing.CodeAnalysis.Fakes;
+
+namespace PodNet.EmbeddedTexts.Tests;
+
+[TestClass]
+public class EmbeddedTextGeneratorTests
+{
+ [TestMethod]
+ public void DoesntGenerateWhenDisabled()
+ {
+ var result = RunGeneration(false, false);
+ Assert.AreEqual(1, result.Results.Length);
+ Assert.AreEqual(0, result.Results[0].GeneratedSources.Length);
+ }
+
+ [TestMethod]
+ public void GeneratesForOptInOnly()
+ {
+ var result = RunGeneration(false, true);
+ // This is a complex assertion that makes sure there is a single result that contains a single property declaration with the expected content in its initializer. Doesn't check for other structural correctness.
+ Assert.AreEqual("Test File 2 Content", ((LiteralExpressionSyntax)result.Results.Single().GeneratedSources.Single().SyntaxTree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value).ToString().Trim('"').Trim());
+ }
+
+ [TestMethod]
+ public void DoesntGenerateForOptOut()
+ {
+ var result = RunGeneration(true, false);
+ Assert.AreEqual(1, result.Results.Length);
+ Assert.AreEqual(5, result.Results[0].GeneratedSources.Length);
+ }
+
+ [TestMethod]
+ public void GenerateAllWhenGloballyEnabledAndItemIsOptIn()
+ {
+ var result = RunGeneration(true, true);
+ Assert.AreEqual(1, result.Results.Length);
+ Assert.AreEqual(6, result.Results[0].GeneratedSources.Length);
+ }
+
+ [TestMethod]
+ public void GeneratesStructurallyEquivalentResult()
+ {
+ // Generates a single source as per GeneratesForOptInOnly
+ var result = RunGeneration(false, true);
+ var source = result.Results.Single().GeneratedSources.Single();
+
+ var expected = CSharpSyntaxTree.ParseText(""""
+ namespace Project;
+
+ public static partial class Parameterized_Enabled_cs
+ {
+ public static string Content { get; } = """
+ Test File 2 Content
+ """;
+ }
+ """").GetRoot();
+ var actual = source.SyntaxTree.GetRoot();
+ Assert.IsTrue(SyntaxFactory.AreEquivalent(expected, actual, ignoreChildNode: SyntaxFacts.IsTrivia));
+ }
+
+ [DataTestMethod]
+ [DataRow("Default_txt", "Project")]
+ [DataRow("Parameterized_Enabled_cs", "Project")]
+ [DataRow("CustomNamespace_n", "TestNamespace")]
+ [DataRow("TestClassName", "Project")]
+ [DataRow("Empty", "Project")]
+ [DataRow("Empty_ini", "Project.Subdirectory._2_Another____Subdirectory")]
+ public void GeneratesCorrectClassNamesAndNamespaces(string expectedClassName, string expectedNamespace)
+ {
+ var result = RunGeneration(true, true);
+ var source = result.Results.Single().GeneratedSources.SingleOrDefault(s => s.HintName.EndsWith($"{expectedClassName}.g.cs"));
+ Assert.IsNotNull(source);
+ var @namespace = source.SyntaxTree.GetRoot().DescendantNodes().OfType().FirstOrDefault()?.Name.ToString();
+ var className = source.SyntaxTree.GetRoot().DescendantNodes().OfType().FirstOrDefault()?.Identifier.ToString();
+ Assert.AreEqual(expectedNamespace, @namespace);
+ Assert.AreEqual(expectedClassName, className);
+ }
+
+ public static GeneratorDriverRunResult RunGeneration(bool globalEnabled, bool oneItemEnabled)
+ {
+ var additionalTextsLookup = GetOptionsForTexts(oneItemEnabled)
+ .ToDictionary(f => (AdditionalText)f.Key, f => f.Value);
+ var globalOptions = GetGlobalOptions(globalEnabled);
+ var optionsProvider = new Fakes.AnalyzerConfigOptionsProvider(globalOptions, [], additionalTextsLookup);
+ var compilation = PodCSharpCompilation.Create([]);
+ var generator = new EmbeddedTextsGenerator();
+ return compilation.RunGenerators(
+ [generator],
+ driver => (CSharpGeneratorDriver)driver
+ .AddAdditionalTexts(additionalTextsLookup.Select(a => a.Key).ToImmutableArray())
+ .WithUpdatedAnalyzerConfigOptions(optionsProvider));
+ }
+
+ private static Fakes.AnalyzerConfigOptions GetGlobalOptions(bool globalEnabled) => new()
+ {
+ ["build_property.rootnamespace"] = "Project",
+ ["build_property.projectdir"] = @"C:\Users\source\Project",
+ [$"build_property.{EmbeddedTextsGenerator.EmbedAdditionalTextsConfigProperty}"] = globalEnabled.ToString()
+ };
+
+ private static Dictionary GetOptionsForTexts(bool oneItemEnabled) => new()
+ {
+ [new(@"C:\Users\source\Project\Default.txt", "Test File 1 Content")]
+ = [],
+ [new(@"C:\Users\source\Project\Parameterized Enabled.cs", "Test File 2 Content")]
+ = new() { [$"build_metadata.additionalfiles.{EmbeddedTextsGenerator.EmbedTextMetadataProperty}"] = oneItemEnabled.ToString() },
+ [new(@"C:\Users\source\Project\CustomNamespace.n", "Test File 3 Content")]
+ = new() { [$"build_metadata.additionalfiles.{EmbeddedTextsGenerator.EmbedTextNamespaceMetadataProperty}"] = "TestNamespace" },
+ [new(@"C:\Users\source\Project\CustomClassName.n", "Test File 4 Content")]
+ = new() { [$"build_metadata.additionalfiles.{EmbeddedTextsGenerator.EmbedTextClassNameMetadataProperty}"] = "TestClassName" },
+ [new(@"C:\Users\source\Project\Empty", "")] = [],
+ [new(@"C:\Users\source\Project\Subdirectory\2 Another & Subdirectory/Empty.ini", "")] = [],
+ };
+}
\ No newline at end of file
diff --git a/tests/EmbeddedTexts.Tests/PodNet.EmbeddedTexts.Tests.csproj b/tests/EmbeddedTexts.Tests/PodNet.EmbeddedTexts.Tests.csproj
new file mode 100644
index 0000000..5627f9d
--- /dev/null
+++ b/tests/EmbeddedTexts.Tests/PodNet.EmbeddedTexts.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+