├── .gitattributes ├── global.json ├── samples ├── GenzorStandAloneConsoleApp │ ├── Generators │ │ ├── _Imports.razor │ │ └── HelloWorldGenerator.razor │ ├── GenzorStandAloneConsoleApp.csproj │ ├── Program.cs │ └── FileSystem.cs ├── GenzorSourceGeneratorsDemoApp │ ├── Csv │ │ ├── People.csv │ │ └── Cars.csv │ ├── Program.cs │ └── GenzorSourceGeneratorsDemoApp.csproj ├── GenzorSourceGenerators │ ├── SourceGeneratorSamples │ │ ├── CsvLoadType.cs │ │ ├── NOTICE.md │ │ ├── CsvGenerator.props │ │ ├── HelloWorldGenerator.cs │ │ └── CSVGenerator.cs │ ├── Genzor.CSharp.SourceGenerators │ │ ├── _Imports.razor │ │ ├── IGeneratedTypeCollection.cs │ │ ├── IGenzorSourceGenerator.cs │ │ ├── Components │ │ │ ├── Namespace.razor │ │ │ ├── GenzorComponentBase.cs │ │ │ ├── Constructor.razor │ │ │ ├── Class.razor │ │ │ ├── Project.cs │ │ │ ├── Property.cs │ │ │ └── T.cs │ │ ├── IUsedTypesCollection.cs │ │ ├── TypeInfo.cs │ │ ├── VirtualFileSystem.cs │ │ └── GenzorSourceGeneratorBase.cs │ ├── GenzorGenerators │ │ ├── GenzorCSVGenerator │ │ │ ├── CSVGenerator.razor │ │ │ ├── CsvGeneratorOption.cs │ │ │ ├── CSVClassFileGenerator.razor │ │ │ ├── CSVGenerator.razor.cs │ │ │ └── CSVClassFileGenerator.razor.cs │ │ ├── _Imports.razor │ │ └── HelloWorldGenerator.razor │ └── GenzorSourceGenerators.csproj ├── GenzorSourceGeneratorsTests │ ├── TestDoubles │ │ └── FakeFileSystem.cs │ ├── Assertions │ │ ├── LoggingBuilderExtensions.cs │ │ ├── FileAssertionExtensions.cs │ │ ├── DirectoryAssertionsExtensions.cs │ │ └── FileSystemAssertionsExtensions.cs │ ├── GenzorGenerators │ │ └── CSVGeneratorTest.cs │ ├── GenzorTestBase.cs │ └── GenzorSourceGeneratorsTests.csproj ├── .editorconfig ├── README.md ├── Directory.Build.props └── samples.sln ├── src ├── genzor │ ├── Internal │ │ ├── IRenderTree.cs │ │ └── FileContentRenderTreeVisitor.cs │ ├── FileSystem │ │ ├── Internal │ │ │ ├── TextFile.cs │ │ │ ├── DefaultFileSystemItemFactory.cs │ │ │ └── Directory.cs │ │ ├── IDirectory.cs │ │ ├── IFileSystemItem.cs │ │ ├── IFile.cs │ │ ├── IFileSystem.cs │ │ └── IFileSystemItemFactory.cs │ ├── Components │ │ ├── IFileComponent.cs │ │ ├── IDirectoryComponent.cs │ │ ├── TextFile.cs │ │ └── Directory.cs │ ├── InvalidGeneratorComponentContentException.cs │ ├── genzor.csproj │ ├── ParameterViewBuilder.cs │ ├── GenzorHost.cs │ └── GenzorRenderer.cs ├── genzor.csharp │ └── genzor.csharp.csproj ├── .editorconfig └── Directory.Build.props ├── stylecop.json ├── tests ├── genzor.tests │ ├── TestGenerators │ │ ├── StaticDirectoryGenerator.cs │ │ ├── GenericTextComponent.cs │ │ ├── GenericParentComponent.cs │ │ ├── ThrowingGenereator.cs │ │ ├── DirectoryWithNoneFileSystemComponentGenerator.cs │ │ ├── StaticFileGenerator.cs │ │ ├── DirectoryWithFileGenerator.cs │ │ ├── FileWithDirectoryGenerator.cs │ │ ├── StaticFileWithChildComponentGenerator.cs │ │ ├── TwoFileGenerator.cs │ │ ├── TwoDirectoryGenerator.cs │ │ ├── StaticWithMultipleNestedComponentsWrappingFileGenerator.cs │ │ ├── StaticFileWithMultipleNestedChildComponentsGenerator.cs │ │ └── StaticFileWithMultipleNestedComponentsWrappingItemsGenerator.cs │ ├── TestDoubles │ │ └── FakeFileSystem.cs │ ├── genzor.tests.csproj │ ├── Assertions │ │ ├── LoggingBuilderExtensions.cs │ │ ├── FileAssertionExtensions.cs │ │ ├── DirectoryAssertionsExtensions.cs │ │ └── FileSystemAssertionsExtensions.cs │ ├── GenzorTestBase.cs │ ├── Components │ │ ├── TextFileTest.cs │ │ └── DirectoryTest.cs │ └── GenzorRendererTest.cs ├── genzor.csharp.tests │ └── genzor.csharp.tests.csproj ├── Directory.Build.props └── .editorconfig ├── version.json ├── .github ├── dependabot.yml └── workflows │ ├── pre-integration.yml │ └── post-integration.yml ├── LICENSE ├── Directory.Build.props ├── genzor.sln ├── .gitignore ├── README.md └── .editorconfig /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "rollForward": "latestMajor", 4 | "allowPrerelease": false 5 | } 6 | } -------------------------------------------------------------------------------- /samples/GenzorStandAloneConsoleApp/Generators/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components 2 | @using Genzor.Components -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsDemoApp/Csv/People.csv: -------------------------------------------------------------------------------- 1 | Name, address, 11Age 2 | "Luca Bol", "23 Bell Street", 90 3 | "john doe", "32 Carl street", 45 4 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsDemoApp/Csv/Cars.csv: -------------------------------------------------------------------------------- 1 | Brand, Model, Year, cc 2 | Fiat, Punto, 2008, 12.3 3 | Ford, Wagon, 1956, 20.3 4 | Nissan, Wagon, 2018, 40.0 5 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/SourceGeneratorSamples/CsvLoadType.cs: -------------------------------------------------------------------------------- 1 | namespace CsvGenerator 2 | { 3 | public enum CsvLoadType 4 | { 5 | Startup, 6 | OnDemand 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/SourceGeneratorSamples/NOTICE.md: -------------------------------------------------------------------------------- 1 | All samples in this folders is copied from https://github.com/dotnet/roslyn-sdk/tree/main/samples/CSharp/SourceGenerators/SourceGeneratorSamples for comparison purposes. 2 | -------------------------------------------------------------------------------- /src/genzor/Internal/IRenderTree.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.RenderTree; 2 | 3 | namespace Genzor 4 | { 5 | internal interface IRenderTree 6 | { 7 | ArrayRange GetCurrentRenderTreeFrames(int componentId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "indentation": { 5 | "useTabs": true 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/_Imports.razor: -------------------------------------------------------------------------------- 1 | @namespace Genzor.CSharp.SourceGenerators 2 | 3 | @using Microsoft.AspNetCore.Components 4 | @using Genzor.Components 5 | @using Genzor.CSharp.SourceGenerators 6 | @using Genzor.CSharp.SourceGenerators.Components 7 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/GenzorCSVGenerator/CSVGenerator.razor: -------------------------------------------------------------------------------- 1 | @attribute [Generator] 2 | @inherits GenzorSourceGeneratorBase 3 | 4 | @foreach (var option in Options) 5 | { 6 | 7 | } 8 | 9 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/IGeneratedTypeCollection.cs: -------------------------------------------------------------------------------- 1 | namespace Genzor.CSharp.SourceGenerators.Components 2 | { 3 | public interface IGeneratedTypeCollection 4 | { 5 | void Add(TypeInfo typeInfo); 6 | 7 | TypeInfo GetByName(string @namespace, string name); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Collections.Generic 2 | @using Microsoft.AspNetCore.Components 3 | @using Microsoft.CodeAnalysis 4 | @using Genzor.Components 5 | @using Genzor.CSharp.SourceGenerators 6 | @using Genzor.CSharp.SourceGenerators.Components 7 | @using CsvGenerator 8 | -------------------------------------------------------------------------------- /samples/GenzorStandAloneConsoleApp/Generators/HelloWorldGenerator.razor: -------------------------------------------------------------------------------- 1 | 2 | HELLO TEXT 3 | 4 | NESTED HELLO TEXT 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsDemoApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GenzorSourceGeneratorsDemoApp 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | Console.WriteLine("Hello World!"); 10 | GenzorHelloWorldGenerated.HelloWorld.SayHello(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/SourceGeneratorSamples/CsvGenerator.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/Internal/TextFile.cs: -------------------------------------------------------------------------------- 1 | namespace Genzor.FileSystem.Internal 2 | { 3 | internal class TextFile : IFile 4 | { 5 | public string Name { get; } 6 | 7 | public string Content { get; } 8 | 9 | public TextFile(string name, string content) 10 | { 11 | Name = name; 12 | Content = content; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/IGenzorSourceGenerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Genzor.CSharp.SourceGenerators 5 | { 6 | public interface IGenzorSourceGenerator 7 | { 8 | [Parameter] public GeneratorExecutionContext Context { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/IDirectory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Genzor.FileSystem 4 | { 5 | /// 6 | /// Represent a generated directory, which can hold 7 | /// zero or more . 8 | /// 9 | public interface IDirectory : IReadOnlyList, IFileSystemItem 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/Namespace.razor: -------------------------------------------------------------------------------- 1 | @inherits GenzorComponentBase 2 | @implements IDirectoryComponent 3 | @code 4 | { 5 | [Parameter] public RenderFragment? ChildContent { get; set; } 6 | [Parameter] public string Name { get; set; } = string.Empty; 7 | } 8 | @ChildContent 9 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/GenzorComponentBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Genzor.CSharp.SourceGenerators.Components 4 | { 5 | public abstract class GenzorComponentBase : ComponentBase 6 | { 7 | [Parameter] public bool Visible { get; set; } = true; 8 | 9 | protected override bool ShouldRender() => Visible; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/StaticDirectoryGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | 5 | namespace Genzor.TestGenerators 6 | { 7 | public class StaticDirectoryGenerator : ComponentBase, IDirectoryComponent 8 | { 9 | public static readonly string NameText = Guid.NewGuid().ToString(); 10 | 11 | public string Name { get; } = NameText; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/IUsedTypesCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Genzor.CSharp.SourceGenerators.Components; 7 | 8 | namespace Genzor.CSharp.SourceGenerators 9 | { 10 | public interface IUsedTypesCollection 11 | { 12 | void Add(TypeInfo typeInfo); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestDoubles/FakeFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Genzor.FileSystem; 3 | 4 | namespace Genzor.TestDoubles 5 | { 6 | public class FakeFileSystem : IFileSystem 7 | { 8 | private readonly List items = new(); 9 | 10 | public IReadOnlyList Root => items; 11 | 12 | public void AddItem(IFileSystemItem item) 13 | { 14 | items.Add(item); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/TestDoubles/FakeFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Genzor.FileSystem; 3 | 4 | namespace Genzor.TestDoubles 5 | { 6 | public class FakeFileSystem : IFileSystem 7 | { 8 | private readonly List items = new(); 9 | 10 | public IReadOnlyList Root => items; 11 | 12 | public void AddItem(IFileSystemItem item) 13 | { 14 | items.Add(item); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.0.0-alpha.{height}", 4 | "versionHeightOffset": "100", 5 | "publicReleaseRefSpec": [ 6 | "^refs/heads/main$", 7 | "^refs/heads/v\\d+(?:\\.\\d+)?$" 8 | ], 9 | "cloudBuild": { 10 | "setVersionVariables": true, 11 | "buildNumber": { 12 | "enabled": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/IFileSystemItem.cs: -------------------------------------------------------------------------------- 1 | namespace Genzor.FileSystem 2 | { 3 | /// 4 | /// Represents a generic file system item, i.e. either an 5 | /// or an . 6 | /// 7 | public interface IFileSystemItem 8 | { 9 | /// 10 | /// Gets the name of the file or directory in a . 11 | /// 12 | public string Name { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/genzor.csharp.tests/genzor.csharp.tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | false 6 | Genzor.CSharp 7 | Genzor.CSharp.Tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/GenericTextComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Genzor.TestGenerators 5 | { 6 | public class GenericTextComponent : ComponentBase 7 | { 8 | [Parameter] public string Text { get; set; } = string.Empty; 9 | 10 | protected override void BuildRenderTree(RenderTreeBuilder builder) 11 | { 12 | builder.AddContent(0, Text); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/GenericParentComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Genzor.TestGenerators 5 | { 6 | public class GenericParentComponent : ComponentBase 7 | { 8 | [Parameter] public RenderFragment ChildContent { get; set; } 9 | 10 | protected override void BuildRenderTree(RenderTreeBuilder builder) 11 | { 12 | builder.AddContent(0, ChildContent); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/ThrowingGenereator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | 5 | namespace Genzor.TestGenerators 6 | { 7 | public class ThrowingGenereator : ComponentBase, IFileComponent 8 | { 9 | public string Name { get; } = string.Empty; 10 | 11 | protected override void OnInitialized() => throw new ThrowingGenereatorException(); 12 | 13 | internal sealed class ThrowingGenereatorException : Exception { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/IFile.cs: -------------------------------------------------------------------------------- 1 | namespace Genzor.FileSystem 2 | { 3 | /// 4 | /// Represents a file in a 5 | /// that has content of type . 6 | /// 7 | /// The content type of the content in the file. 8 | public interface IFile : IFileSystemItem 9 | { 10 | /// 11 | /// Gets the content of the file. 12 | /// 13 | TContent Content { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /samples/GenzorStandAloneConsoleApp/GenzorStandAloneConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/genzor/Components/IFileComponent.cs: -------------------------------------------------------------------------------- 1 | using Genzor.FileSystem; 2 | using Microsoft.AspNetCore.Components; 3 | 4 | namespace Genzor.Components 5 | { 6 | /// 7 | /// Represents a component that when rendered will result in a 8 | /// being added to the , or in a containing it. 9 | /// 10 | public interface IFileComponent : IComponent 11 | { 12 | /// 13 | /// Gets the name of the file the component renders. 14 | /// 15 | string Name { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/IFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Genzor.FileSystem 4 | { 5 | /// 6 | /// Represents a abstraction for a file system, which will 7 | /// add and types to. 8 | /// 9 | public interface IFileSystem 10 | { 11 | /// 12 | /// Adds the to the root of the file system. 13 | /// 14 | /// Item to add to the file system. 15 | void AddItem(IFileSystemItem item); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/GenzorCSVGenerator/CsvGeneratorOption.cs: -------------------------------------------------------------------------------- 1 | using CsvGenerator; 2 | 3 | namespace GenzorSourceGenerators.GenzorGenerators 4 | { 5 | public class CsvGeneratorOption 6 | { 7 | public CsvLoadType LoadType { get; } 8 | 9 | public bool CacheObjects { get; } 10 | 11 | public string ClassName { get; } 12 | 13 | public string CsvText { get; } 14 | 15 | public CsvGeneratorOption(string csvText, string className, CsvLoadType loadType, bool cacheObjects) 16 | { 17 | LoadType = loadType; 18 | CsvText = csvText; 19 | ClassName = className; 20 | CacheObjects = cacheObjects; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/DirectoryWithNoneFileSystemComponentGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class DirectoryWithNoneFileSystemComponentGenerator : ComponentBase, IFileComponent 9 | { 10 | public static readonly Type ChildComponent = typeof(GenericParentComponent); 11 | 12 | public string Name { get; } = string.Empty; 13 | 14 | protected override void BuildRenderTree(RenderTreeBuilder builder) 15 | { 16 | builder.OpenComponent(0, ChildComponent); 17 | builder.CloseComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/genzor.tests/genzor.tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;netcoreapp3.1 5 | false 6 | Genzor 7 | Genzor.Tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/Internal/DefaultFileSystemItemFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Genzor.FileSystem.Internal 5 | { 6 | internal class DefaultFileSystemItemFactory : IFileSystemItemFactory 7 | { 8 | public IDirectory CreateDirectory(string name, IReadOnlyList items) 9 | => new Directory(name, items); 10 | 11 | public IFile CreateFile(string name, TContent content) 12 | => content switch 13 | { 14 | string c => (IFile)new TextFile(name, c), 15 | _ => throw new InvalidOperationException($"Support for files with content of type {typeof(TContent)} is not yet implemented.") 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/StaticFileGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class StaticFileGenerator : ComponentBase, IFileComponent 9 | { 10 | public static readonly string NameText = Guid.NewGuid().ToString(); 11 | public static readonly string ContentText = Guid.NewGuid().ToString(); 12 | 13 | public string Name { get; } = NameText; 14 | 15 | public string Content { get; } = ContentText; 16 | 17 | protected override void BuildRenderTree(RenderTreeBuilder builder) 18 | => builder.AddContent(0, Content); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/genzor.csharp/genzor.csharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;netstandard2.0 5 | Genzor.CSharp 6 | Genzor.CSharp 7 | 8 | 9 | 10 | genzor.csharp 11 | genzor.csharp 12 | 13 | Genzor C# is a library ideally suited for generating C# code spanning multiple files and folders, using Blazor component model to represent the generated output. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/DirectoryWithFileGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class DirectoryWithFileGenerator : ComponentBase, IDirectoryComponent 9 | { 10 | public static readonly string ChildFileName = Guid.NewGuid().ToString(); 11 | 12 | public string Name { get; } = nameof(DirectoryWithFileGenerator); 13 | 14 | protected override void BuildRenderTree(RenderTreeBuilder builder) 15 | { 16 | builder.OpenComponent(0); 17 | builder.AddAttribute(1, nameof(TextFile.Name), ChildFileName); 18 | builder.CloseComponent(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = tab 6 | tab_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.cs] 12 | tab_size = 4 13 | 14 | [*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] 15 | tab_size = 2 16 | 17 | [*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}] 18 | tab_size = 2 19 | 20 | [*.json] 21 | tab_size = 2 22 | 23 | [*.{ps1,psm1}] 24 | tab_size = 4 25 | 26 | [*.sh] 27 | tab_size = 4 28 | end_of_line = lf 29 | 30 | [*.{yml,yaml}] 31 | indent_style = space 32 | tab_size = 2 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | 37 | [*.{cmd,bat}] 38 | end_of_line = crlf 39 | 40 | [Makefile] 41 | indent_style = tab -------------------------------------------------------------------------------- /src/genzor/FileSystem/Internal/Directory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Genzor.FileSystem.Internal 6 | { 7 | internal class Directory : IDirectory 8 | { 9 | private readonly IReadOnlyList items; 10 | 11 | public string Name { get; } 12 | 13 | public int Count => items.Count; 14 | 15 | public IFileSystemItem this[int index] => items[index]; 16 | 17 | public Directory(string name, IReadOnlyList items) 18 | { 19 | Name = name; 20 | this.items = items; 21 | } 22 | 23 | public IEnumerator GetEnumerator() => items.GetEnumerator(); 24 | 25 | IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/Constructor.razor: -------------------------------------------------------------------------------- 1 | @inherits GenzorComponentBase 2 | @code 3 | { 4 | [CascadingParameter] public Class Class { get; set; } = default!; 5 | [Parameter] public bool IsStatic { get; set; } 6 | [Parameter] public RenderFragment? ChildContent { get; set; } 7 | 8 | private string Modifiers 9 | { 10 | get 11 | { 12 | var result = IsStatic 13 | ? "static" 14 | : "public"; 15 | 16 | return result; 17 | } 18 | } 19 | 20 | private const string Arguments = "()"; 21 | } 22 | @if (Visible) 23 | { 24 | 25 | @Modifiers @Class.Name@Arguments 26 | { 27 | @ChildContent 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/genzor/Components/IDirectoryComponent.cs: -------------------------------------------------------------------------------- 1 | using Genzor.FileSystem; 2 | using Microsoft.AspNetCore.Components; 3 | 4 | namespace Genzor.Components 5 | { 6 | /// 7 | /// Represents a component that when rendered will result in a 8 | /// being added to the . Any child components of 9 | /// type or will 10 | /// be added to the as 11 | /// or respectively. 12 | /// 13 | public interface IDirectoryComponent : IComponent 14 | { 15 | /// 16 | /// Gets the name of the directory the component renders. 17 | /// 18 | string Name { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/FileWithDirectoryGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class FileWithDirectoryGenerator : ComponentBase, IFileComponent 9 | { 10 | public static readonly string FileName = Guid.NewGuid().ToString(); 11 | public static readonly string DirectoryName = Guid.NewGuid().ToString(); 12 | 13 | public string Name { get; } = FileName; 14 | 15 | protected override void BuildRenderTree(RenderTreeBuilder builder) 16 | { 17 | builder.OpenComponent(0); 18 | builder.AddAttribute(1, nameof(Directory.Name), DirectoryName); 19 | builder.CloseComponent(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/GenzorStandAloneConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Genzor; 4 | using GenzorStandAloneConsoleApp.Generators; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace GenzorDemo 8 | { 9 | class Program 10 | { 11 | static async Task Main(string[] args) 12 | { 13 | var fileSystem = new FileSystem(new DirectoryInfo(Directory.GetCurrentDirectory())); 14 | 15 | using var host = new GenzorHost() 16 | .AddLogging(configure => configure 17 | .AddConsole() 18 | .SetMinimumLevel(LogLevel.Debug)) 19 | .AddFileSystem(fileSystem); 20 | 21 | await host.InvokeGeneratorAsync(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/StaticFileWithChildComponentGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class StaticFileWithChildComponentGenerator : ComponentBase, IFileComponent 9 | { 10 | public static readonly string ChildComponentText = Guid.NewGuid().ToString(); 11 | 12 | public string Name { get; } = nameof(StaticFileWithChildComponentGenerator); 13 | 14 | protected override void BuildRenderTree(RenderTreeBuilder builder) 15 | { 16 | builder.OpenComponent(0); 17 | builder.AddAttribute(1, nameof(GenericTextComponent.Text), ChildComponentText); 18 | builder.CloseComponent(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/HelloWorldGenerator.razor: -------------------------------------------------------------------------------- 1 | @attribute [Generator] 2 | @inherits GenzorSourceGeneratorBase 3 | 4 | 5 | using System; 6 | namespace GenzorHelloWorldGenerated 7 | { 8 | public static class HelloWorld 9 | { 10 | public static void SayHello() 11 | { 12 | Console.WriteLine("Hello from razor generated code!"); 13 | Console.WriteLine("The following syntax trees existed in the compilation that created this program:"); 14 | 15 | @foreach (SyntaxTree tree in Context.Compilation.SyntaxTrees) 16 | { 17 | 18 | Console.WriteLine(@@" - @tree.FilePath"); 19 | 20 | } 21 | } 22 | } 23 | } 24 | 25 | 26 | @code { 27 | [Parameter] public override GeneratorExecutionContext Context { get; set; } 28 | } 29 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # Genzor Samples 2 | 3 | The following samples are available: 4 | 5 | 1. **GenzorStandAloneConsoleApp** is a console app that uses Genzor directly to generate files and folders to the file system. 6 | 2. **GenzorSourceGenerators** is a source generators library that uses Genzor to implement C# source generators. 7 | 3. **GenzorSourceGeneratorsDemoApp** is an console app that uses the **GenzorSourceGeneratorsDemo** library. 8 | 9 | NOTE: All samples currently reference the Genzor package hosted on GitHub Package Repository. To build and try the samples locally, you need to configure your machine to download the Genzor packages. See the section [Getting the Genzor package from GitHub Package Repository](https://github.com/egil/genzor#getting-the-genzor-package-from-github-package-repository) for details on how to do this. 10 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsDemoApp/GenzorSourceGeneratorsDemoApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/TwoFileGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class TwoFileGenerator : ComponentBase 9 | { 10 | public static readonly string FirstFilesName = nameof(FirstFilesName) + Guid.NewGuid().ToString(); 11 | public static readonly string SecondFilesName = nameof(SecondFilesName) + Guid.NewGuid().ToString(); 12 | 13 | protected override void BuildRenderTree(RenderTreeBuilder builder) 14 | { 15 | builder.OpenComponent(0); 16 | builder.AddAttribute(1, nameof(TextFile.Name), FirstFilesName); 17 | builder.CloseComponent(); 18 | builder.OpenComponent(10); 19 | builder.AddAttribute(11, nameof(TextFile.Name), SecondFilesName); 20 | builder.CloseComponent(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/TypeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Genzor.CSharp.SourceGenerators 4 | { 5 | public class TypeInfo : IEquatable 6 | { 7 | public string Name { get; } = string.Empty; 8 | 9 | public string FullName => $"{Namespace}.{Name}"; 10 | 11 | public string Namespace { get; } = string.Empty; 12 | 13 | public bool Exists { get; internal set; } = false; 14 | 15 | public TypeInfo(string @namespace, string name, bool exists) 16 | { 17 | Namespace = @namespace; 18 | Name = name; 19 | Exists = exists; 20 | } 21 | 22 | public override bool Equals(object obj) 23 | => obj is TypeInfo other && Equals(other); 24 | 25 | public bool Equals(TypeInfo other) 26 | => FullName.Equals(other.FullName, StringComparison.Ordinal); 27 | 28 | public override int GetHashCode() 29 | => FullName.GetHashCode(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0-alpha-* 5 | 5.0.0 6 | 3.1.0 7 | 8 | 9 | 10 | enable 11 | 9.0 12 | full 13 | true 14 | true 15 | 16 | 17 | 18 | Default 19 | true 20 | latest 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/TwoDirectoryGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class TwoDirectoryGenerator : ComponentBase 9 | { 10 | public static readonly string FirstDirectoryName = nameof(FirstDirectoryName) + Guid.NewGuid().ToString(); 11 | public static readonly string SecondDirectoryName = nameof(SecondDirectoryName) + Guid.NewGuid().ToString(); 12 | 13 | protected override void BuildRenderTree(RenderTreeBuilder builder) 14 | { 15 | builder.OpenComponent(0); 16 | builder.AddAttribute(1, nameof(Directory.Name), FirstDirectoryName); 17 | builder.CloseComponent(); 18 | builder.OpenComponent(10); 19 | builder.AddAttribute(11, nameof(Directory.Name), SecondDirectoryName); 20 | builder.CloseComponent(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/genzor.tests/Assertions/LoggingBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using Serilog; 5 | using Serilog.Events; 6 | using Xunit.Abstractions; 7 | 8 | namespace Genzor.Assertions 9 | { 10 | public static class LoggingBuilderExtensions 11 | { 12 | [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Serilog should dispose of its logger itself")] 13 | public static ILoggingBuilder AddXunitLogger(this ILoggingBuilder loggingBuilder, ITestOutputHelper outputHelper) 14 | { 15 | var serilogLogger = new LoggerConfiguration() 16 | .MinimumLevel.Verbose() 17 | .WriteTo.TestOutput(outputHelper, LogEventLevel.Verbose) 18 | .CreateLogger(); 19 | loggingBuilder.Services.AddSingleton(new LoggerFactory().AddSerilog(serilogLogger, dispose: true)); 20 | loggingBuilder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); 21 | return loggingBuilder; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/Assertions/LoggingBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using Serilog; 5 | using Serilog.Events; 6 | using Xunit.Abstractions; 7 | 8 | namespace Genzor.Assertions 9 | { 10 | public static class LoggingBuilderExtensions 11 | { 12 | [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Serilog should dispose of its logger itself")] 13 | public static ILoggingBuilder AddXunitLogger(this ILoggingBuilder loggingBuilder, ITestOutputHelper outputHelper) 14 | { 15 | var serilogLogger = new LoggerConfiguration() 16 | .MinimumLevel.Verbose() 17 | .WriteTo.TestOutput(outputHelper, LogEventLevel.Verbose) 18 | .CreateLogger(); 19 | loggingBuilder.Services.AddSingleton(new LoggerFactory().AddSerilog(serilogLogger, dispose: true)); 20 | loggingBuilder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); 21 | return loggingBuilder; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/GenzorGenerators/CSVGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CsvGenerator; 7 | using Genzor; 8 | using GenzorSourceGenerators.GenzorGenerators; 9 | using GenzorSourceGenerators.GenzorGenerators.GenzorCSVGenerator; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace GenzorSourceGeneratorsTests.GenzorGenerators 14 | { 15 | public class CSVGeneratorTest : GenzorTestBase 16 | { 17 | private const string CarsCsv = @"Brand, Model, Year, cc 18 | Fiat, Punto, 2008, 12.3 19 | Ford, Wagon, 1956, 20.3"; 20 | 21 | public CSVGeneratorTest(ITestOutputHelper outputHelper) : base(outputHelper) 22 | { 23 | } 24 | 25 | [Fact] 26 | public async Task MyTestMethod() 27 | { 28 | var option = new CsvGeneratorOption( 29 | CarsCsv, 30 | "Cars", 31 | CsvLoadType.OnDemand, 32 | false); 33 | 34 | await Host.InvokeGeneratorAsync(ps => ps.Add(p => p.Option, option)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/genzor.tests/GenzorTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Assertions; 3 | using Genzor.TestDoubles; 4 | using Xunit.Abstractions; 5 | 6 | namespace Genzor 7 | { 8 | public abstract class GenzorTestBase : IDisposable 9 | { 10 | private bool disposedValue; 11 | 12 | protected GenzorHost Host { get; } 13 | 14 | protected FakeFileSystem FileSystem { get; } 15 | 16 | protected GenzorTestBase(ITestOutputHelper outputHelper) 17 | { 18 | FileSystem = new FakeFileSystem(); 19 | Host = new GenzorHost(); 20 | Host.AddFileSystem(FileSystem); 21 | Host.AddLogging((builder) => builder.AddXunitLogger(outputHelper)); 22 | } 23 | 24 | protected virtual void Dispose(bool disposing) 25 | { 26 | if (!disposedValue) 27 | { 28 | if (disposing) 29 | { 30 | Host?.Dispose(); 31 | } 32 | 33 | disposedValue = true; 34 | } 35 | } 36 | 37 | public void Dispose() 38 | { 39 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 40 | Dispose(disposing: true); 41 | GC.SuppressFinalize(this); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/genzor/Components/TextFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.FileSystem; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.Components 7 | { 8 | /// 9 | /// Represents a text file that should be added to the . 10 | /// 11 | public class TextFile : ComponentBase, IFileComponent 12 | { 13 | /// 14 | [Parameter] public string Name { get; set; } = string.Empty; 15 | 16 | /// 17 | /// Gets or sets the child content to add to the file. 18 | /// 19 | [Parameter] public RenderFragment? ChildContent { get; set; } 20 | 21 | /// 22 | protected override void OnParametersSet() 23 | { 24 | if (string.IsNullOrWhiteSpace(Name)) 25 | { 26 | throw new ArgumentException("The Name parameter cannot be null or whitespace."); 27 | } 28 | } 29 | 30 | /// 31 | protected override void BuildRenderTree(RenderTreeBuilder builder) 32 | { 33 | builder?.AddContent(0, ChildContent); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/GenzorTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Assertions; 3 | using Genzor.TestDoubles; 4 | using Xunit.Abstractions; 5 | 6 | namespace Genzor 7 | { 8 | public abstract class GenzorTestBase : IDisposable 9 | { 10 | private bool disposedValue; 11 | 12 | protected GenzorHost Host { get; } 13 | 14 | protected FakeFileSystem FileSystem { get; } 15 | 16 | protected GenzorTestBase(ITestOutputHelper outputHelper) 17 | { 18 | FileSystem = new FakeFileSystem(); 19 | Host = new GenzorHost(); 20 | Host.AddFileSystem(FileSystem); 21 | Host.AddLogging((builder) => builder.AddXunitLogger(outputHelper)); 22 | } 23 | 24 | protected virtual void Dispose(bool disposing) 25 | { 26 | if (!disposedValue) 27 | { 28 | if (disposing) 29 | { 30 | Host?.Dispose(); 31 | } 32 | 33 | disposedValue = true; 34 | } 35 | } 36 | 37 | public void Dispose() 38 | { 39 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 40 | Dispose(disposing: true); 41 | GC.SuppressFinalize(this); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/genzor/InvalidGeneratorComponentContentException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Genzor.Components; 4 | 5 | namespace Genzor 6 | { 7 | /// 8 | /// Represents an exception that is thrown when an invalid state is detected in render tree generated 9 | /// by the when it invokes/renders generator components. 10 | /// 11 | [Serializable] 12 | public sealed class InvalidGeneratorComponentContentException : Exception 13 | { 14 | private InvalidGeneratorComponentContentException(string? message) : base(message) 15 | { } 16 | 17 | private InvalidGeneratorComponentContentException(SerializationInfo info, StreamingContext context) 18 | : base(info, context) 19 | { } 20 | 21 | internal static InvalidGeneratorComponentContentException CreateUnexpectedDirectoryException(string directoryName) 22 | => new InvalidGeneratorComponentContentException( 23 | $"A directory component ({nameof(IDirectoryComponent)}) cannot be the child of a file component ({nameof(IFileComponent)}). Name of misplaced directory: {directoryName}"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/genzor/genzor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;netstandard2.0 5 | Genzor 6 | Genzor 7 | 8 | 9 | 10 | genzor 11 | genzor 12 | 13 | Genzor is a library ideally suited for generating many files spanning many folders, using Blazor component model to represent the generated output. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Egil Hansen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # ATC coding rules - https://github.com/atc-net/atc-coding-rules 2 | # Version: 1.0.5 3 | # Updated: 02-05-2021 4 | # Location: Src 5 | # Inspired by: https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options 6 | 7 | ########################################## 8 | # Code Analyzers Rules 9 | ########################################## 10 | [*.{cs,csx,cake}] 11 | 12 | # AsyncFixer 13 | # http://www.asyncfixer.com 14 | 15 | 16 | # Asyncify 17 | # https://github.com/hvanbakel/Asyncify-CSharp 18 | 19 | 20 | # Meziantou 21 | # https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm 22 | 23 | 24 | # Microsoft - Code Analysis 25 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ 26 | 27 | 28 | # SecurityCodeScan 29 | # https://security-code-scan.github.io/ 30 | 31 | 32 | # StyleCop 33 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers 34 | 35 | 36 | # SonarAnalyzer.CSharp 37 | # https://rules.sonarsource.com/csharp 38 | 39 | 40 | ########################################## 41 | # Custom - Code Analyzers Rules 42 | ########################################## -------------------------------------------------------------------------------- /src/genzor/Components/Directory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.FileSystem; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.Components 7 | { 8 | /// 9 | /// Represents a directory that should be added to the . 10 | /// 11 | public class Directory : ComponentBase, IDirectoryComponent 12 | { 13 | /// 14 | [Parameter] public string Name { get; set; } = string.Empty; 15 | 16 | /// 17 | /// Gets or sets the child content to add to the directory. 18 | /// 19 | /// 20 | /// Only components of type or 21 | /// can be added to a directory. 22 | /// All other content will be ignored. 23 | /// 24 | [Parameter] public RenderFragment? ChildContent { get; set; } 25 | 26 | /// 27 | protected override void OnParametersSet() 28 | { 29 | if (string.IsNullOrWhiteSpace(Name)) 30 | { 31 | throw new ArgumentException("The Name parameter cannot be null or whitespace."); 32 | } 33 | } 34 | 35 | /// 36 | protected override void BuildRenderTree(RenderTreeBuilder builder) 37 | { 38 | builder?.AddContent(0, ChildContent); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/genzor.tests/Assertions/FileAssertionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using FluentAssertions.Primitives; 8 | using Genzor.FileSystem; 9 | 10 | namespace Genzor.Assertions 11 | { 12 | public static class FileAssertionsExtensions 13 | { 14 | public static FileAssertions Should(this IFile file) 15 | { 16 | return new FileAssertions(file); 17 | } 18 | } 19 | 20 | public class FileAssertions : ReferenceTypeAssertions, FileAssertions> 21 | { 22 | protected override string Identifier { get; } = "file"; 23 | 24 | public FileAssertions(IFile subject) : base(subject) 25 | { 26 | } 27 | 28 | public AndConstraint> WithName(string expectedName, string because = "", params object[] becauseArgs) 29 | { 30 | Subject.Name.Should().Be(expectedName, because, becauseArgs); 31 | return new AndConstraint>(this); 32 | } 33 | 34 | public AndConstraint> WithContent(T expectedContent, string because = "", params object[] becauseArgs) 35 | { 36 | Subject.Content.Should().Be(expectedContent, because, becauseArgs); 37 | return new AndConstraint>(this); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/Assertions/FileAssertionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using FluentAssertions.Primitives; 8 | using Genzor.FileSystem; 9 | 10 | namespace Genzor.Assertions 11 | { 12 | public static class FileAssertionsExtensions 13 | { 14 | public static FileAssertions Should(this IFile file) 15 | { 16 | return new FileAssertions(file); 17 | } 18 | } 19 | 20 | public class FileAssertions : ReferenceTypeAssertions, FileAssertions> 21 | { 22 | protected override string Identifier { get; } = "file"; 23 | 24 | public FileAssertions(IFile subject) : base(subject) 25 | { 26 | } 27 | 28 | public AndConstraint> WithName(string expectedName, string because = "", params object[] becauseArgs) 29 | { 30 | Subject.Name.Should().Be(expectedName, because, becauseArgs); 31 | return new AndConstraint>(this); 32 | } 33 | 34 | public AndConstraint> WithContent(T expectedContent, string because = "", params object[] becauseArgs) 35 | { 36 | Subject.Content.Should().Be(expectedContent, because, becauseArgs); 37 | return new AndConstraint>(this); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/Class.razor: -------------------------------------------------------------------------------- 1 | @inherits GenzorComponentBase 2 | @implements IUsedTypesCollection 3 | @code 4 | { 5 | [CascadingParameter] public IGeneratedTypeCollection GeneratedTypes { get; set; } 6 | [CascadingParameter] public Namespace Namespace { get; set; } 7 | [Parameter] public RenderFragment? ChildContent { get; set; } 8 | [Parameter] public string Name { get; set; } = string.Empty; 9 | 10 | private HashSet usedTypes = new(); 11 | 12 | void IUsedTypesCollection.Add(TypeInfo type) 13 | { 14 | usedTypes.Add(type); 15 | } 16 | 17 | protected override void OnInitialized() 18 | { 19 | GeneratedTypes.Add(new TypeInfo(Namespace.Name, Name, true)); 20 | } 21 | 22 | protected override void OnAfterRender(bool firstRender) 23 | { 24 | if (firstRender && usedTypes.Count > 0) 25 | { 26 | StateHasChanged(); 27 | } 28 | } 29 | } 30 | 31 | #nullable enable 32 | namespace @Namespace.Name 33 | { 34 | @foreach (var u in usedTypes.OrderBy(x => x.Namespace)) 35 | { 36 | 37 | using @u.Namespace; 38 | 39 | } 40 | 41 | public class @Name 42 | { 43 | 44 | @ChildContent 45 | 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/genzor/FileSystem/IFileSystemItemFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Genzor.FileSystem 4 | { 5 | /// 6 | /// Represents an factory for types, 7 | /// that is used by the to create the 8 | /// file system items during generation/rendering. 9 | /// 10 | public interface IFileSystemItemFactory 11 | { 12 | /// 13 | /// Creates an . 14 | /// 15 | /// The type of content in the file. 16 | /// The name of the file (relative, should not include the parent directory's paths.). 17 | /// The content to add to the file. 18 | /// The created . 19 | IFile CreateFile(string name, TContent content); 20 | 21 | /// 22 | /// Creates an . 23 | /// 24 | /// The name of the directory (relative, should not include the parent directory's paths.). 25 | /// The items in the directory. 26 | /// The created . 27 | IDirectory CreateDirectory(string name, IReadOnlyList items); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | annotations 11 | false 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/StaticWithMultipleNestedComponentsWrappingFileGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class StaticWithMultipleNestedComponentsWrappingFileGenerator : ComponentBase 9 | { 10 | public static readonly string NestedFileName = Guid.NewGuid().ToString(); 11 | 12 | public string Name { get; } = string.Empty; 13 | 14 | protected override void BuildRenderTree(RenderTreeBuilder builder) 15 | { 16 | builder.AddContent(0, (RenderFragment)FirstLevelGenericParentComponent); 17 | } 18 | 19 | private void FirstLevelGenericParentComponent(RenderTreeBuilder builder) 20 | { 21 | builder.OpenComponent(0); 22 | builder.AddAttribute(1, nameof(GenericParentComponent.ChildContent), (RenderFragment)SecondLevelGenericParentComponent); 23 | builder.CloseComponent(); 24 | } 25 | 26 | private void SecondLevelGenericParentComponent(RenderTreeBuilder builder) 27 | { 28 | builder.OpenComponent(0); 29 | builder.AddAttribute(1, nameof(GenericParentComponent.ChildContent), (RenderFragment)FileComponent); 30 | builder.CloseComponent(); 31 | } 32 | 33 | private void FileComponent(RenderTreeBuilder builder) 34 | { 35 | builder.OpenComponent(0); 36 | builder.AddAttribute(1, nameof(TextFile.Name), NestedFileName); 37 | builder.CloseComponent(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/GenzorSourceGeneratorsTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | annotations 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/pre-integration.yml: -------------------------------------------------------------------------------- 1 | name: "Pre-Integration" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - reopened 9 | 10 | jobs: 11 | verify-code: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | framework: [netcoreapp3.1, net5.0] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: 🛒 Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: ⚙️ Setup dotnet 3.1.x 24 | uses: actions/setup-dotnet@v1 25 | with: 26 | dotnet-version: '3.1.x' 27 | 28 | - name: ⚙️ Setup dotnet 5.0.x 29 | uses: actions/setup-dotnet@v1 30 | with: 31 | dotnet-version: '5.0.x' 32 | 33 | - name: 🔁 Restore packages 34 | run: dotnet restore 35 | 36 | - name: 🛠️ Building library in release mode 37 | run: dotnet build -c Release --no-restore --framework ${{ matrix.framework }} 38 | 39 | - name: 🧪 Run unit tests 40 | run: dotnet test -c Debug --framework ${{ matrix.framework }} /p:ContinuousIntegrationBuild=false /p:CollectCoverage=true /p:CoverletOutput=./coverage/ /p:CoverletOutputFormat=opencover /p:ExcludeByAttribute=\"Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute\" 41 | 42 | - name: 🧪 Collect code coverage 43 | uses: codecov/codecov-action@v1 44 | with: 45 | flags: unittests 46 | name: genzor # optional 47 | fail_ci_if_error: true # optional (default = false) 48 | verbose: true # optional (default = false) 49 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/Project.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | 5 | namespace Genzor.CSharp.SourceGenerators.Components 6 | { 7 | public class Project : ComponentBase, IGeneratedTypeCollection 8 | { 9 | private readonly Dictionary generatedTypes = new(); 10 | 11 | [Parameter] public RenderFragment? ChildContent { get; set; } 12 | 13 | public void Add(TypeInfo typeInfo) 14 | { 15 | if (generatedTypes.TryGetValue(typeInfo.FullName, out var existing)) 16 | { 17 | existing.Exists = true; 18 | } 19 | else 20 | { 21 | generatedTypes.Add(typeInfo.FullName, typeInfo); 22 | } 23 | } 24 | 25 | public TypeInfo GetByName(string @namespace, string name) 26 | { 27 | var result = new TypeInfo(@namespace, name, false); 28 | if (generatedTypes.TryGetValue(result.FullName, out var existing)) 29 | { 30 | result = existing; 31 | } 32 | else 33 | { 34 | generatedTypes.Add(result.FullName, result); 35 | } 36 | return result; 37 | } 38 | 39 | protected override void BuildRenderTree(RenderTreeBuilder builder) 40 | { 41 | builder.OpenComponent>(0); 42 | builder.AddAttribute(1, nameof(CascadingValue.IsFixed), true); 43 | builder.AddAttribute(2, nameof(CascadingValue.Value), this); 44 | builder.AddAttribute(3, nameof(CascadingValue.ChildContent), ChildContent); 45 | builder.CloseComponent(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples/GenzorStandAloneConsoleApp/FileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Genzor.FileSystem; 4 | 5 | namespace GenzorDemo 6 | { 7 | class FileSystem : IFileSystem 8 | { 9 | private readonly DirectoryInfo rootDirectory; 10 | 11 | public FileSystem(DirectoryInfo rootDirectory) 12 | => this.rootDirectory = rootDirectory ?? throw new ArgumentNullException(nameof(rootDirectory)); 13 | 14 | public void AddItem(IFileSystemItem item) 15 | => AddItem(rootDirectory, item); 16 | 17 | private void AddItem(DirectoryInfo parent, IFileSystemItem item) 18 | { 19 | switch (item) 20 | { 21 | case IDirectory directory: 22 | AddDirectory(parent, directory); 23 | break; 24 | case IFile textFile: 25 | AddTextFile(parent, textFile); 26 | break; 27 | default: 28 | throw new NotImplementedException($"Unsupported file system item {item.GetType().FullName}"); 29 | } 30 | } 31 | 32 | private void AddDirectory(DirectoryInfo parent, IDirectory directory) 33 | { 34 | var createdDirectory = parent.CreateSubdirectory(directory.Name); 35 | 36 | foreach (var item in directory) 37 | AddItem(createdDirectory, item); 38 | } 39 | 40 | private void AddTextFile(DirectoryInfo parent, IFile file) 41 | { 42 | var fullPath = Path.Combine(parent.FullName, file.Name); 43 | File.WriteAllText(fullPath, file.Content); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/GenzorCSVGenerator/CSVClassFileGenerator.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | static _all = null; 5 | 6 | 7 | _ = All; 8 | 9 | 10 | @foreach(var prop in Properties) 11 | { 12 | 13 | } 14 | 15 | public static All 16 | { 17 | get 18 | { 19 | if(_all is not null) 20 | { 21 | return _all; 22 | } 23 | 24 | List@($"<{Option.ClassName}>") items = new(); 25 | @Option.ClassName i; 26 | @foreach(var row in Rows) 27 | { 28 | 29 | i = new(); 30 | 31 | 32 | @foreach(var prop in Properties) 33 | { 34 | 35 | i.@prop.Name = @GetCsvValue(@prop.TypeName, @row[@prop.ColumnIndex]); 36 | 37 | } 38 | 39 | 40 | items.Add(i); 41 | 42 | } 43 | 44 | _all = items; 45 | return _all; 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.Text; 6 | 7 | namespace SourceGeneratorSamples 8 | { 9 | [Generator] 10 | public class HelloWorldGenerator : ISourceGenerator 11 | { 12 | public void Execute(GeneratorExecutionContext context) 13 | { 14 | // begin creating the source we'll inject into the users compilation 15 | StringBuilder sourceBuilder = new StringBuilder(@" 16 | using System; 17 | namespace HelloWorldGenerated 18 | { 19 | public static class HelloWorld 20 | { 21 | public static void SayHello() 22 | { 23 | Console.WriteLine(""Hello from generated code!""); 24 | Console.WriteLine(""The following syntax trees existed in the compilation that created this program:""); 25 | "); 26 | 27 | // using the context, get a list of syntax trees in the users compilation 28 | IEnumerable syntaxTrees = context.Compilation.SyntaxTrees; 29 | 30 | // add the filepath of each tree to the class we're building 31 | foreach (SyntaxTree tree in syntaxTrees) 32 | { 33 | sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");"); 34 | } 35 | 36 | // finish creating the source to inject 37 | sourceBuilder.Append(@" 38 | } 39 | } 40 | }"); 41 | 42 | // inject the created source into the users compilation 43 | context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); 44 | } 45 | 46 | public void Initialize(GeneratorInitializationContext context) 47 | { 48 | // No initialization required 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/StaticFileWithMultipleNestedChildComponentsGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class StaticFileWithMultipleNestedChildComponentsGenerator : ComponentBase, IFileComponent 9 | { 10 | public static readonly string Child1ComponentText = nameof(Child1ComponentText) + Guid.NewGuid().ToString(); 11 | public static readonly string Child2ComponentText = nameof(Child2ComponentText) + Guid.NewGuid().ToString(); 12 | 13 | public string Name { get; } = nameof(StaticFileWithChildComponentGenerator); 14 | 15 | protected override void BuildRenderTree(RenderTreeBuilder builder) 16 | { 17 | builder.OpenComponent(0); 18 | builder.AddAttribute(1, nameof(GenericParentComponent.ChildContent), (RenderFragment)Child1ComponentContent); 19 | builder.CloseComponent(); 20 | builder.OpenComponent(10); 21 | builder.AddAttribute(11, nameof(GenericParentComponent.ChildContent), (RenderFragment)Child2ComponentContent); 22 | builder.CloseComponent(); 23 | } 24 | 25 | private void Child1ComponentContent(RenderTreeBuilder builder) 26 | { 27 | builder.OpenComponent(0); 28 | builder.AddAttribute(1, nameof(GenericTextComponent.Text), Child1ComponentText); 29 | builder.CloseComponent(); 30 | } 31 | 32 | private void Child2ComponentContent(RenderTreeBuilder builder) 33 | { 34 | builder.OpenComponent(0); 35 | builder.AddAttribute(1, nameof(GenericTextComponent.Text), Child2ComponentText); 36 | builder.CloseComponent(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/GenzorCSVGenerator/CSVGenerator.razor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using CsvGenerator; 6 | using Genzor.CSharp.SourceGenerators; 7 | using Microsoft.AspNetCore.Components; 8 | using Microsoft.CodeAnalysis; 9 | 10 | namespace GenzorSourceGenerators.GenzorGenerators.GenzorCSVGenerator 11 | { 12 | public partial class CSVGenerator : GenzorSourceGeneratorBase 13 | { 14 | IEnumerable Options { get; set; } = Enumerable.Empty(); 15 | 16 | [Parameter] public override GeneratorExecutionContext Context { get; set; } 17 | 18 | protected override void OnInitialized() 19 | { 20 | // get options 21 | Options = GetLoadOptions(Context); 22 | } 23 | 24 | static IEnumerable GetLoadOptions(GeneratorExecutionContext context) 25 | { 26 | foreach (AdditionalText file in context.AdditionalFiles) 27 | { 28 | if (Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase)) 29 | { 30 | // are there any options for it? 31 | context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString); 32 | Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType); 33 | 34 | context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString); 35 | bool.TryParse(cacheObjectsString, out bool cacheObjects); 36 | 37 | yield return new CsvGeneratorOption( 38 | file.GetText()?.ToString() ?? string.Empty, 39 | Path.GetFileNameWithoutExtension(file.Path), 40 | loadType, 41 | cacheObjects); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/Property.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Genzor.CSharp.SourceGenerators.Components 5 | { 6 | public class Property : ComponentBase 7 | { 8 | private string typeName = ""; 9 | [CascadingParameter] protected Namespace Namespace { get; set; } = default!; 10 | [CascadingParameter] protected IGeneratedTypeCollection GeneratedTypes { get; set; } = default!; 11 | [CascadingParameter] protected IUsedTypesCollection UsedTypes { get; set; } = default!; 12 | [Parameter] public string Modifiers { get; set; } = "public"; 13 | [Parameter] public string Name { get; set; } 14 | [Parameter] public Type? GenType { get; set; } 15 | [Parameter] public Type? ClrType { get; set; } 16 | [Parameter] public bool HasGet { get; set; } = true; 17 | [Parameter] public bool HasSet { get; set; } = true; 18 | 19 | protected override void OnInitialized() 20 | { 21 | if (GenType is string genType) 22 | { 23 | UsedTypes.Add(GeneratedTypes.GetByName(Namespace.Name, genType)); 24 | } 25 | 26 | if(ClrType is not System.Type type) 27 | { 28 | type = typeof(Type); 29 | } 30 | 31 | UsedTypes.Add(new TypeInfo(type.Namespace, type.Name, true)); 32 | 33 | if (type.IsGenericType && GenType is not null) 34 | { 35 | typeName = $"{type.Name.Substring(0, type.Name.IndexOf('`'))}<{GenType}>"; 36 | } 37 | else 38 | { 39 | typeName = type.Name; 40 | } 41 | } 42 | 43 | protected override void BuildRenderTree(RenderTreeBuilder builder) 44 | { 45 | var prop = $"{Modifiers} {typeName} {Name} {{ "; 46 | if (HasGet) 47 | prop += "get; "; 48 | if (HasSet) 49 | prop += "set; "; 50 | prop += "}"; 51 | 52 | builder.AddMarkupContent(0, prop); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/genzor.tests/TestGenerators/StaticFileWithMultipleNestedComponentsWrappingItemsGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Genzor.Components; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Rendering; 5 | 6 | namespace Genzor.TestGenerators 7 | { 8 | public class StaticFileWithMultipleNestedComponentsWrappingItemsGenerator : ComponentBase, IDirectoryComponent 9 | { 10 | public static readonly string DirectoryName = Guid.NewGuid().ToString(); 11 | public static readonly string NestedFileName = nameof(NestedFileName) + Guid.NewGuid().ToString(); 12 | public static readonly string NestedDirectoryName = nameof(NestedDirectoryName) + Guid.NewGuid().ToString(); 13 | 14 | public string Name { get; } = DirectoryName; 15 | 16 | protected override void BuildRenderTree(RenderTreeBuilder builder) 17 | { 18 | builder.AddContent(0, (RenderFragment)FirstLevelGenericParentComponent); 19 | } 20 | 21 | private void FirstLevelGenericParentComponent(RenderTreeBuilder builder) 22 | { 23 | builder.OpenComponent(0); 24 | builder.AddAttribute(1, nameof(GenericParentComponent.ChildContent), (RenderFragment)SecondLevelGenericParentComponent); 25 | builder.CloseComponent(); 26 | } 27 | 28 | private void SecondLevelGenericParentComponent(RenderTreeBuilder builder) 29 | { 30 | builder.OpenComponent(0); 31 | builder.AddAttribute(1, nameof(GenericParentComponent.ChildContent), (RenderFragment)FileComponent); 32 | builder.CloseComponent(); 33 | } 34 | 35 | private void FileComponent(RenderTreeBuilder builder) 36 | { 37 | builder.OpenComponent(0); 38 | builder.AddAttribute(1, nameof(TextFile.Name), NestedFileName); 39 | builder.CloseComponent(); 40 | builder.OpenComponent(10); 41 | builder.AddAttribute(11, nameof(Directory.Name), NestedDirectoryName); 42 | builder.CloseComponent(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/VirtualFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using Genzor.FileSystem; 5 | 6 | namespace Genzor.CSharp.SourceGenerators 7 | { 8 | internal class VirtualFileSystem : IFileSystem, IEnumerable<(string PathAndName, string Content)> 9 | { 10 | private List items = new(); 11 | 12 | public void AddItem(IFileSystemItem item) 13 | { 14 | items.Add(item); 15 | } 16 | 17 | public IEnumerator<(string PathAndName, string Content)> GetEnumerator() 18 | { 19 | var result = new List<(string PathAndName, string Content)>(); 20 | 21 | foreach (var item in items) 22 | { 23 | result.AddRange(AddFiles(string.Empty, item)); 24 | } 25 | 26 | return result.GetEnumerator(); 27 | } 28 | 29 | private static IReadOnlyList<(string PathAndName, string Content)> AddFiles(string path, IFileSystemItem item) 30 | { 31 | return item switch 32 | { 33 | IDirectory directory => AddDirectory(path, directory), 34 | IFile textFile => AddTextFile(path, textFile), 35 | _ => throw new NotImplementedException($"Unsupported file system item {item.GetType().FullName}"), 36 | }; 37 | } 38 | 39 | private static IReadOnlyList<(string PathAndName, string Content)> AddDirectory(string path, IDirectory directory) 40 | { 41 | var result = new List<(string PathAndName, string Content)>(); 42 | var dirPath = $"{path}_{directory.Name}"; 43 | 44 | foreach (var item in directory) 45 | { 46 | result.AddRange(AddFiles(dirPath, item)); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | private static IReadOnlyList<(string PathAndName, string Content)> AddTextFile(string path, IFile file) 53 | { 54 | var fullPath = $"{path}_{file.Name}"; 55 | return new (string PathAndName, string Content)[] { (fullPath, file.Content) }; 56 | } 57 | 58 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/genzor.tests/Components/TextFileTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using AutoFixture.Xunit2; 7 | using FluentAssertions; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | namespace Genzor.Components 13 | { 14 | public class TextFileTest : GenzorTestBase 15 | { 16 | public TextFileTest(ITestOutputHelper outputHelper) : base(outputHelper) { } 17 | 18 | [AutoData] 19 | [Theory(DisplayName = "given file name and no content, " + 20 | "when generator is invoked, " + 21 | "then a empty file with specified name is added to file system")] 22 | public async Task Test001(string fileName) 23 | { 24 | await Host.InvokeGeneratorAsync(ps => ps.Add(p => p.Name, fileName)); 25 | 26 | FileSystem 27 | .Should() 28 | .ContainSingleTextFile() 29 | .WithName(fileName) 30 | .And 31 | .WithContent(string.Empty); 32 | } 33 | 34 | [AutoData] 35 | [Theory(DisplayName = "given file name and content, " + 36 | "when generator is invoked, " + 37 | "then a file with content and specified name is added to file system")] 38 | public async Task Test002(string fileName, string content) 39 | { 40 | await Host.InvokeGeneratorAsync(ps => ps 41 | .Add(p => p.Name, fileName) 42 | .Add(p => p.ChildContent, content)); 43 | 44 | FileSystem 45 | .Should() 46 | .ContainSingleTextFile() 47 | .WithName(fileName) 48 | .And 49 | .WithContent(content); 50 | } 51 | 52 | [Fact(DisplayName = "given no file name, " + 53 | "when generator is invoked, " + 54 | "then an argument exception is throw")] 55 | public async Task Test003() 56 | { 57 | Func throwingAction = () => Host.InvokeGeneratorAsync(); 58 | 59 | await throwingAction.Should() 60 | .ThrowAsync() 61 | .WithMessage("The Name parameter cannot be null or whitespace."); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/.editorconfig: -------------------------------------------------------------------------------- 1 | # ATC coding rules - https://github.com/atc-net/atc-coding-rules 2 | # Version: 1.0.5 3 | # Updated: 02-05-2021 4 | # Location: Test 5 | # Inspired by: https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options 6 | 7 | ########################################## 8 | # Code Analyzers Rules 9 | ########################################## 10 | [*.{cs,csx,cake}] 11 | 12 | # AsyncFixer 13 | # http://www.asyncfixer.com 14 | 15 | 16 | # Asyncify 17 | # https://github.com/hvanbakel/Asyncify-CSharp 18 | 19 | 20 | # Meziantou 21 | # https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm 22 | dotnet_diagnostic.MA0004.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0004.md 23 | 24 | # Microsoft - Code Analysis 25 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ 26 | dotnet_diagnostic.CA1064.severity = suggestion # CA1064: Exceptions should be public 27 | dotnet_diagnostic.CA1707.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1707.md 28 | dotnet_diagnostic.CA2007.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA2007.md 29 | 30 | # SecurityCodeScan 31 | # https://security-code-scan.github.io/ 32 | 33 | 34 | # StyleCop 35 | # https://github.com/DotNetAnalyzers/StyleCopAnalyzers 36 | dotnet_diagnostic.SA1122.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1122.md 37 | dotnet_diagnostic.SA1133.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1133.md 38 | 39 | 40 | # SonarAnalyzer.CSharp 41 | # https://rules.sonarsource.com/csharp 42 | 43 | 44 | ########################################## 45 | # Custom - Code Analyzers Rules 46 | ########################################## -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/Components/T.cs: -------------------------------------------------------------------------------- 1 | using Genzor.CSharp.SourceGenerators; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | 5 | namespace Genzor.CSharp.SourceGenerators.Components 6 | { 7 | public class UseGenType : ComponentBase 8 | { 9 | private TypeInfo usedType; 10 | 11 | [CascadingParameter] public Namespace Namespace { get; set; } 12 | [CascadingParameter] protected IUsedTypesCollection UsedTypes { get; set; } 13 | [CascadingParameter] public IGeneratedTypeCollection GeneratedTypes { get; set; } 14 | 15 | [Parameter] public string Name { get; set; } 16 | 17 | protected override void OnInitialized() 18 | { 19 | usedType = GeneratedTypes.GetByName(Namespace.Name, Name); 20 | UsedTypes.Add(usedType); 21 | } 22 | 23 | protected override void BuildRenderTree(RenderTreeBuilder builder) 24 | { 25 | builder.AddContent(0, usedType.Name); 26 | } 27 | } 28 | 29 | public class UseType : ComponentBase 30 | { 31 | private string typeName = ""; 32 | 33 | [CascadingParameter] protected Namespace Namespace { get; set; } 34 | 35 | [CascadingParameter] protected IGeneratedTypeCollection GeneratedTypes { get; set; } 36 | 37 | [CascadingParameter] protected IUsedTypesCollection UsedTypes { get; set; } 38 | 39 | [Parameter] public string? GenType { get; set; } 40 | 41 | protected override void OnInitialized() 42 | { 43 | if (GenType is not null) 44 | { 45 | UsedTypes.Add(GeneratedTypes.GetByName(Namespace.Name, GenType)); 46 | } 47 | 48 | var type = typeof(Type); 49 | UsedTypes.Add(new TypeInfo(type.Namespace, type.Name, true)); 50 | 51 | if (type.IsGenericType && GenType is not null) 52 | { 53 | typeName = $"{type.Name.Substring(0, type.Name.IndexOf('`'))}<{GenType}>"; 54 | } 55 | else 56 | { 57 | typeName = type.Name; 58 | } 59 | } 60 | 61 | protected override void BuildRenderTree(RenderTreeBuilder builder) 62 | { 63 | builder.AddMarkupContent(0, typeName); 64 | } 65 | } 66 | 67 | public sealed class GenType { } 68 | } 69 | -------------------------------------------------------------------------------- /tests/genzor.tests/Assertions/DirectoryAssertionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using FluentAssertions.Execution; 4 | using FluentAssertions.Primitives; 5 | using Genzor.Assertions; 6 | using Genzor.FileSystem; 7 | 8 | namespace FluentAssertions 9 | { 10 | public static class DirectoryAssertionsExtensions 11 | { 12 | public static DirectoryAssertions Should(this IDirectory subject) 13 | { 14 | return new DirectoryAssertions(subject); 15 | } 16 | } 17 | 18 | public class DirectoryAssertions : ReferenceTypeAssertions 19 | { 20 | protected override string Identifier { get; } = "directory"; 21 | 22 | public IDirectory Which => Subject; 23 | 24 | public DirectoryAssertions(IDirectory subject) : base(subject) 25 | { 26 | } 27 | 28 | public AndConstraint WithName(string expectedName, string because = "", params object[] becauseArgs) 29 | { 30 | Subject.Name.Should().Be(expectedName, because, becauseArgs); 31 | return new AndConstraint(this); 32 | } 33 | 34 | public AndConstraint WithoutItems(string because = "", params object[] becauseArgs) 35 | { 36 | ((IReadOnlyList)Subject).Should().BeEmpty(because, becauseArgs); 37 | return new AndConstraint(this); 38 | } 39 | 40 | public AndConstraint WithItemsEquivalentTo(IEnumerable expectation, string because = "", params object[] becauseArgs) 41 | { 42 | ((IReadOnlyList)Subject).Should().BeEquivalentTo(expectation, because, becauseArgs); 43 | return new AndConstraint(this); 44 | } 45 | 46 | public FileAssertions ContainSingleTextFile(string because = "", params object[] becauseArgs) 47 | { 48 | var file = ((IReadOnlyList)Subject) 49 | .Should() 50 | .ContainSingle(because, becauseArgs) 51 | .Subject 52 | .Should() 53 | .BeAssignableTo>(because, becauseArgs) 54 | .Subject; 55 | 56 | return new FileAssertions(file); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/Assertions/DirectoryAssertionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using FluentAssertions.Execution; 4 | using FluentAssertions.Primitives; 5 | using Genzor.Assertions; 6 | using Genzor.FileSystem; 7 | 8 | namespace FluentAssertions 9 | { 10 | public static class DirectoryAssertionsExtensions 11 | { 12 | public static DirectoryAssertions Should(this IDirectory subject) 13 | { 14 | return new DirectoryAssertions(subject); 15 | } 16 | } 17 | 18 | public class DirectoryAssertions : ReferenceTypeAssertions 19 | { 20 | protected override string Identifier { get; } = "directory"; 21 | 22 | public IDirectory Which => Subject; 23 | 24 | public DirectoryAssertions(IDirectory subject) : base(subject) 25 | { 26 | } 27 | 28 | public AndConstraint WithName(string expectedName, string because = "", params object[] becauseArgs) 29 | { 30 | Subject.Name.Should().Be(expectedName, because, becauseArgs); 31 | return new AndConstraint(this); 32 | } 33 | 34 | public AndConstraint WithoutItems(string because = "", params object[] becauseArgs) 35 | { 36 | ((IReadOnlyList)Subject).Should().BeEmpty(because, becauseArgs); 37 | return new AndConstraint(this); 38 | } 39 | 40 | public AndConstraint WithItemsEquivalentTo(IEnumerable expectation, string because = "", params object[] becauseArgs) 41 | { 42 | ((IReadOnlyList)Subject).Should().BeEquivalentTo(expectation, because, becauseArgs); 43 | return new AndConstraint(this); 44 | } 45 | 46 | public FileAssertions ContainSingleTextFile(string because = "", params object[] becauseArgs) 47 | { 48 | var file = ((IReadOnlyList)Subject) 49 | .Should() 50 | .ContainSingle(because, becauseArgs) 51 | .Subject 52 | .Should() 53 | .BeAssignableTo>(because, becauseArgs) 54 | .Subject; 55 | 56 | return new FileAssertions(file); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | true 11 | Library 12 | 13 | true 14 | 15 | snupkg 16 | 20 | true 21 | 22 | embedded 23 | true 24 | 25 | 26 | 27 | MIT 28 | https://github.com/egil/genzor 29 | git 30 | https://github.com/egil/genzor 31 | code-generator file-generator generator blazor 32 | Egil Hansen 33 | Egil Hansen 34 | Egil Hansen 35 | Genzor 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /tests/genzor.tests/Components/DirectoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AutoFixture.Xunit2; 4 | using FluentAssertions; 5 | using Microsoft.AspNetCore.Components; 6 | using Microsoft.AspNetCore.Components.Rendering; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Genzor.Components 11 | { 12 | public class DirectoryTest : GenzorTestBase 13 | { 14 | public DirectoryTest(ITestOutputHelper outputHelper) : base(outputHelper) { } 15 | 16 | [AutoData] 17 | [Theory(DisplayName = "given directory name and no content, " + 18 | "when generator is invoked, " + 19 | "then a empty directory with specified name is added to file system")] 20 | public async Task Test001(string directoryName) 21 | { 22 | await Host.InvokeGeneratorAsync(ps => ps.Add(p => p.Name, directoryName)); 23 | 24 | FileSystem 25 | .Should() 26 | .ContainSingleDirectory() 27 | .WithName(directoryName) 28 | .And 29 | .WithoutItems(); 30 | } 31 | 32 | [AutoData] 33 | [Theory(DisplayName = "given directory name and text file as content, " + 34 | "when generator is invoked, " + 35 | "then a directory with text file and specified name is added to file system")] 36 | public async Task Test002(string directoryName, string fileName) 37 | { 38 | await Host.InvokeGeneratorAsync(ps => ps 39 | .Add(p => p.Name, directoryName) 40 | .Add(p => p.ChildContent, RenderTextFile)); 41 | 42 | FileSystem 43 | .Should() 44 | .ContainSingleDirectory() 45 | .WithName(directoryName) 46 | .And 47 | .WithItemsEquivalentTo(new[] 48 | { 49 | new { Name = fileName }, 50 | }); 51 | 52 | void RenderTextFile(RenderTreeBuilder builder) 53 | { 54 | builder.OpenComponent(0); 55 | builder.AddAttribute(1, nameof(TextFile.Name), fileName); 56 | builder.CloseComponent(); 57 | } 58 | } 59 | 60 | [Fact(DisplayName = "given no directory name, " + 61 | "when generator is invoked, " + 62 | "then an argument exception is throw")] 63 | public async Task Test003() 64 | { 65 | Func throwingAction = () => Host.InvokeGeneratorAsync(); 66 | 67 | await throwingAction.Should() 68 | .ThrowAsync() 69 | .WithMessage("The Name parameter cannot be null or whitespace."); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/workflows/post-integration.yml: -------------------------------------------------------------------------------- 1 | name: "Post-Integration" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - '!release' 8 | paths: 9 | - '**' 10 | - '!samples/**' 11 | 12 | env: 13 | NUGET_REPO_URL: 'https://nuget.pkg.github.com/egil/index.json' 14 | 15 | jobs: 16 | create-pre-release: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: 🛒 Checkout repository 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: ⚛️ Sets environment variables - branch-name 25 | uses: nelonoel/branch-name@v1.0.1 26 | 27 | - name: ⚛️ Sets environment variables - Nerdbank.GitVersioning 28 | uses: dotnet/nbgv@master 29 | with: 30 | setAllVars: true 31 | 32 | - name: ⚙️ Setup dotnet 3.1.x 33 | uses: actions/setup-dotnet@v1 34 | with: 35 | dotnet-version: '3.1.x' 36 | 37 | - name: ⚙️ Setup dotnet 5.0.x 38 | uses: actions/setup-dotnet@v1 39 | with: 40 | dotnet-version: '5.0.x' 41 | 42 | - name: 🧹 Clean 43 | run: dotnet clean -c Release && dotnet nuget locals all --clear 44 | 45 | - name: 🔁 Restore packages 46 | run: dotnet restore 47 | 48 | - name: 🧪 Run unit tests 49 | run: dotnet test -c Debug /p:ContinuousIntegrationBuild=false /p:CollectCoverage=true /p:CoverletOutput=./coverage/ /p:CoverletOutputFormat=opencover /p:ExcludeByAttribute=\"Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute\" 50 | 51 | - name: 🧪 Collect code coverage 52 | uses: codecov/codecov-action@v1 53 | with: 54 | flags: unittests 55 | name: genzor # optional 56 | fail_ci_if_error: false # optional (default = false) 57 | verbose: true # optional (default = false) 58 | 59 | - name: 🛠️ Build 60 | run: dotnet build -c Release --no-restore -p:RepositoryBranch=$BRANCH_NAME -p:ContinousIntegrationBuild=true -p:PublicRelease=false -p:UseSourceLink=true 61 | 62 | - name: 🗳️ Creating library package for pre-release 63 | run: dotnet pack -c Release --no-restore -o ${GITHUB_WORKSPACE}/packages -p:RepositoryBranch=$BRANCH_NAME -p:ContinousIntegrationBuild=true -p:PublicRelease=false -p:UseSourceLink=true 64 | 65 | - name: 📦 Push packages to GitHub Package Registry 66 | run: dotnet nuget push ${GITHUB_WORKSPACE}/packages/*.nupkg -k ${{ secrets.GITHUB_TOKEN }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate 67 | -------------------------------------------------------------------------------- /tests/genzor.tests/Assertions/FileSystemAssertionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using FluentAssertions.Execution; 4 | using FluentAssertions.Primitives; 5 | using Genzor.Assertions; 6 | using Genzor.FileSystem; 7 | using Genzor.TestDoubles; 8 | 9 | namespace FluentAssertions 10 | { 11 | public static class FileSystemAssertionsExtensions 12 | { 13 | public static FileSystemAssertions Should(this FakeFileSystem fileSystem) 14 | { 15 | return new FileSystemAssertions(fileSystem); 16 | } 17 | } 18 | 19 | public class FileSystemAssertions : ReferenceTypeAssertions 20 | { 21 | protected override string Identifier { get; } = "file system"; 22 | 23 | public FileSystemAssertions(FakeFileSystem subject) : base(subject) 24 | { 25 | } 26 | 27 | public DirectoryAssertions ContainSingleDirectory(string because = "", params object[] becauseArgs) 28 | { 29 | using var scope = new AssertionScope("directory"); 30 | 31 | var directory = Subject.Root 32 | .Should() 33 | .ContainSingle(because, becauseArgs) 34 | .Subject 35 | .Should() 36 | .BeAssignableTo(because, becauseArgs) 37 | .Subject; 38 | 39 | return new DirectoryAssertions(directory); 40 | } 41 | 42 | public AndWhichConstraint> HaveDirectories(int count, string because = "", params object[] becauseArgs) 43 | { 44 | using var scope = new AssertionScope("directories"); 45 | 46 | AndConstraint> files = Subject.Root.OfType() 47 | .Should() 48 | .HaveCount(count, because, becauseArgs); 49 | 50 | return new AndWhichConstraint>(this, files.And.Subject); 51 | } 52 | 53 | public FileAssertions ContainSingleTextFile(string because = "", params object[] becauseArgs) 54 | { 55 | var file = Subject.Root 56 | .Should() 57 | .ContainSingle(because, becauseArgs) 58 | .Subject 59 | .Should() 60 | .BeAssignableTo>(because, becauseArgs) 61 | .Subject; 62 | 63 | return new FileAssertions(file); 64 | } 65 | 66 | public AndWhichConstraint>> HaveTextFiles(int count, string because = "", params object[] becauseArgs) 67 | { 68 | using var scope = new AssertionScope("text files"); 69 | 70 | AndConstraint>> files = Subject.Root.OfType>() 71 | .Should() 72 | .HaveCount(count, because, becauseArgs); 73 | 74 | return new AndWhichConstraint>>(this, files.And.Subject); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples/GenzorSourceGeneratorsTests/Assertions/FileSystemAssertionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using FluentAssertions.Execution; 4 | using FluentAssertions.Primitives; 5 | using Genzor.Assertions; 6 | using Genzor.FileSystem; 7 | using Genzor.TestDoubles; 8 | 9 | namespace FluentAssertions 10 | { 11 | public static class FileSystemAssertionsExtensions 12 | { 13 | public static FileSystemAssertions Should(this FakeFileSystem fileSystem) 14 | { 15 | return new FileSystemAssertions(fileSystem); 16 | } 17 | } 18 | 19 | public class FileSystemAssertions : ReferenceTypeAssertions 20 | { 21 | protected override string Identifier { get; } = "file system"; 22 | 23 | public FileSystemAssertions(FakeFileSystem subject) : base(subject) 24 | { 25 | } 26 | 27 | public DirectoryAssertions ContainSingleDirectory(string because = "", params object[] becauseArgs) 28 | { 29 | using var scope = new AssertionScope("directory"); 30 | 31 | var directory = Subject.Root 32 | .Should() 33 | .ContainSingle(because, becauseArgs) 34 | .Subject 35 | .Should() 36 | .BeAssignableTo(because, becauseArgs) 37 | .Subject; 38 | 39 | return new DirectoryAssertions(directory); 40 | } 41 | 42 | public AndWhichConstraint> HaveDirectories(int count, string because = "", params object[] becauseArgs) 43 | { 44 | using var scope = new AssertionScope("directories"); 45 | 46 | AndConstraint> files = Subject.Root.OfType() 47 | .Should() 48 | .HaveCount(count, because, becauseArgs); 49 | 50 | return new AndWhichConstraint>(this, files.And.Subject); 51 | } 52 | 53 | public FileAssertions ContainSingleTextFile(string because = "", params object[] becauseArgs) 54 | { 55 | var file = Subject.Root 56 | .Should() 57 | .ContainSingle(because, becauseArgs) 58 | .Subject 59 | .Should() 60 | .BeAssignableTo>(because, becauseArgs) 61 | .Subject; 62 | 63 | return new FileAssertions(file); 64 | } 65 | 66 | public AndWhichConstraint>> HaveTextFiles(int count, string because = "", params object[] becauseArgs) 67 | { 68 | using var scope = new AssertionScope("text files"); 69 | 70 | AndConstraint>> files = Subject.Root.OfType>() 71 | .Should() 72 | .HaveCount(count, because, becauseArgs); 73 | 74 | return new AndWhichConstraint>>(this, files.And.Subject); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 5.0.0 6 | 3.1.0 7 | 8 | 9 | 10 | 11 | Egil Hansen 12 | Egil Hansen 13 | en 14 | en-US 15 | 16 | 17 | 18 | enable 19 | 9.0 20 | CA1014 21 | 22 | 23 | full 24 | true 25 | 26 | 27 | 28 | AllEnabledByDefault 29 | true 30 | latest 31 | true 32 | 33 | 34 | 35 | 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | true 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/Genzor.CSharp.SourceGenerators/GenzorSourceGeneratorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Text; 6 | using Microsoft.AspNetCore.Components; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.Text; 9 | 10 | namespace Genzor.CSharp.SourceGenerators 11 | { 12 | public abstract class GenzorSourceGeneratorBase : ComponentBase, IGenzorSourceGenerator, ISourceGenerator 13 | { 14 | [SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:Enable analyzer release tracking", Justification = "Just prototyping for now.")] 15 | private static readonly DiagnosticDescriptor GeneratorRuntimeInfo = 16 | new(id: "GSG0001", 17 | title: "Generator runtime", 18 | messageFormat: "The generator '{0}' completed in '{1}' milliseconds", 19 | category: "GenzorSourceGenerator", 20 | DiagnosticSeverity.Info, 21 | isEnabledByDefault: true); 22 | 23 | [Parameter] 24 | public abstract GeneratorExecutionContext Context { get; set; } 25 | 26 | public void Initialize(GeneratorInitializationContext context) 27 | { 28 | // No initialization required 29 | } 30 | 31 | public void Execute(GeneratorExecutionContext context) 32 | { 33 | var generatorType = GetType(); 34 | var runtimeMilliseconds = InvokeGenerator(generatorType, context); 35 | ReportingRuntime(generatorType, context, runtimeMilliseconds); 36 | } 37 | 38 | private static long InvokeGenerator(Type generatorType, GeneratorExecutionContext context) 39 | { 40 | var stopWatch = Stopwatch.StartNew(); 41 | var fileSystem = new VirtualFileSystem(); 42 | using (var host = new GenzorHost().AddFileSystem(fileSystem)) 43 | { 44 | var dict = new Dictionary { { "Context", context } }; 45 | var generatorTask = host.Renderer.InvokeGeneratorAsync(generatorType, ParameterView.FromDictionary(dict)); 46 | 47 | // Task should be completed already, unless the generator component 48 | // is doing async stuff in its async life cycle methods. 49 | generatorTask.Wait(); 50 | 51 | // inject the created source into the users compilation 52 | foreach (var (PathAndName, Content) in fileSystem) 53 | { 54 | context.AddSource(PathAndName, SourceText.From(Content, Encoding.UTF8)); 55 | } 56 | } 57 | stopWatch.Stop(); 58 | return stopWatch.ElapsedMilliseconds; 59 | } 60 | 61 | private static void ReportingRuntime(Type generatorType, GeneratorExecutionContext context, long runtimeMilliseconds) 62 | { 63 | var runtimeDiag = Diagnostic.Create( 64 | GeneratorRuntimeInfo, 65 | Location.None, 66 | generatorType.Name, 67 | runtimeMilliseconds); 68 | 69 | context.ReportDiagnostic(runtimeDiag); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorGenerators/GenzorCSVGenerator/CSVClassFileGenerator.razor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using Microsoft.AspNetCore.Components; 7 | using NotVisualBasic.FileIO; 8 | 9 | namespace GenzorSourceGenerators.GenzorGenerators.GenzorCSVGenerator 10 | { 11 | public partial class CSVClassFileGenerator : ComponentBase 12 | { 13 | private List<(Type TypeName, string Name, int ColumnIndex)> Properties { get; } = new(); 14 | 15 | private List Rows { get; } = new List(); 16 | 17 | [Parameter] public CsvGeneratorOption Option { get; set; } = default!; 18 | 19 | protected override void OnInitialized() 20 | { 21 | using var parser = new CsvTextFieldParser(new StringReader(Option.CsvText)); 22 | var (types, names, fields) = ExtractProperties(parser); 23 | 24 | for (int i = 0; i < types.Length; i++) 25 | { 26 | Properties.Add((types[i], names[i], i)); 27 | } 28 | 29 | if (fields is not null) 30 | { 31 | Rows.Add(fields); 32 | 33 | while (!parser.EndOfData) 34 | { 35 | Rows.Add(parser.ReadFields()); 36 | } 37 | } 38 | } 39 | 40 | private static string GetCsvValue(Type type, string rawValue) 41 | { 42 | return type == typeof(string) 43 | ? $"\"{rawValue.Trim().Trim(new char[] { '"' })}\"" 44 | : rawValue; 45 | } 46 | 47 | static (Type[] types, string[] names, string[]? firstLineFields) ExtractProperties(CsvTextFieldParser parser) 48 | { 49 | string[]? headerFields = parser.ReadFields(); 50 | if (headerFields == null) throw new Exception("Empty csv file!"); 51 | 52 | string[]? firstLineFields = parser.ReadFields(); 53 | 54 | if (firstLineFields == null) 55 | { 56 | var types = Enumerable.Repeat(typeof(string), headerFields.Length).ToArray(); 57 | return (types, headerFields, firstLineFields); 58 | } 59 | else 60 | { 61 | var types = firstLineFields.Select(GetCsvFieldType).ToArray(); 62 | var names = headerFields.Select(StringToValidPropertyName).ToArray(); 63 | return (types, names, firstLineFields); 64 | } 65 | } 66 | 67 | static string StringToValidPropertyName(string s) 68 | { 69 | s = s.Trim(); 70 | s = char.IsLetter(s[0]) ? char.ToUpper(s[0]) + s.Substring(1) : s; 71 | s = char.IsDigit(s.Trim()[0]) ? "_" + s : s; 72 | s = new string(s.Select(ch => char.IsDigit(ch) || char.IsLetter(ch) ? ch : '_').ToArray()); 73 | return s; 74 | } 75 | 76 | // Guesses type of property for the object from the value of a csv field 77 | static Type GetCsvFieldType(string exemplar) => exemplar switch 78 | { 79 | _ when bool.TryParse(exemplar, out _) => typeof(bool), 80 | _ when int.TryParse(exemplar, out _) => typeof(int), 81 | _ when double.TryParse(exemplar, out _) => typeof(double), 82 | _ => typeof(string) 83 | }; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /samples/GenzorSourceGenerators/GenzorSourceGenerators.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 3.0 6 | GenzorSourceGenerators 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | $(GetTargetPathDependsOn);GetDependencyTargetPaths 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /samples/samples.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31025.218 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8F261220-29F0-470A-8FBF-70F179A6EE43}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | Directory.Build.props = Directory.Build.props 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenzorStandAloneConsoleApp", "GenzorStandAloneConsoleApp\GenzorStandAloneConsoleApp.csproj", "{F06878D4-5A34-4AAF-8E63-2D6D0BDD307C}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenzorSourceGenerators", "GenzorSourceGenerators\GenzorSourceGenerators.csproj", "{FE8388E3-E9AA-44EC-8923-C2F4E6624A37}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenzorSourceGeneratorsDemoApp", "GenzorSourceGeneratorsDemoApp\GenzorSourceGeneratorsDemoApp.csproj", "{BA6110F3-BDB4-426E-960D-B017ABE842D7}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenzorSourceGeneratorsTests", "GenzorSourceGeneratorsTests\GenzorSourceGeneratorsTests.csproj", "{BD632358-E77F-4B96-B6E4-32F5884AC0BA}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {F06878D4-5A34-4AAF-8E63-2D6D0BDD307C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {F06878D4-5A34-4AAF-8E63-2D6D0BDD307C}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {F06878D4-5A34-4AAF-8E63-2D6D0BDD307C}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {F06878D4-5A34-4AAF-8E63-2D6D0BDD307C}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {FE8388E3-E9AA-44EC-8923-C2F4E6624A37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {FE8388E3-E9AA-44EC-8923-C2F4E6624A37}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {FE8388E3-E9AA-44EC-8923-C2F4E6624A37}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {FE8388E3-E9AA-44EC-8923-C2F4E6624A37}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {BA6110F3-BDB4-426E-960D-B017ABE842D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {BA6110F3-BDB4-426E-960D-B017ABE842D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {BA6110F3-BDB4-426E-960D-B017ABE842D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {BA6110F3-BDB4-426E-960D-B017ABE842D7}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {BD632358-E77F-4B96-B6E4-32F5884AC0BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {BD632358-E77F-4B96-B6E4-32F5884AC0BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {BD632358-E77F-4B96-B6E4-32F5884AC0BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {BD632358-E77F-4B96-B6E4-32F5884AC0BA}.Release|Any CPU.Build.0 = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(SolutionProperties) = preSolution 45 | HideSolutionNode = FALSE 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {EF4925F3-E3CB-42C1-BAA9-47ED9B58952B} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /src/genzor/ParameterViewBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using Microsoft.AspNetCore.Components; 6 | 7 | namespace Genzor 8 | { 9 | /// 10 | /// Represents a builder for a which can be 11 | /// safely passed to a component of type . 12 | /// 13 | /// The component type to build parameters for. 14 | public class ParameterViewBuilder where TComponent : IComponent 15 | { 16 | private const string ChildContent = nameof(ChildContent); 17 | private static readonly Type TComponentType = typeof(TComponent); 18 | 19 | private readonly Dictionary parameters = new(StringComparer.Ordinal); 20 | 21 | /// 22 | /// Adds the to the parameter selected with the . 23 | /// 24 | /// Type of . 25 | /// A lambda function that selects the parameter. 26 | /// The value to pass to . 27 | /// This so that additional calls can be chained. 28 | public ParameterViewBuilder Add(Expression> parameterSelector, TValue value) 29 | { 30 | if (value is null) 31 | throw new ArgumentNullException(nameof(value)); 32 | 33 | parameters.Add(GetParameterName(parameterSelector), value); 34 | return this; 35 | } 36 | 37 | /// 38 | /// Adds the to the parameter selected with . 39 | /// 40 | /// A lambda function that selects the parameter. 41 | /// The content string to pass to the . 42 | /// This so that additional calls can be chained. 43 | public ParameterViewBuilder Add(Expression> parameterSelector, string content) 44 | => Add(parameterSelector, b => b.AddContent(0, content)); 45 | 46 | /// 47 | /// Builds the with the parameters added to the builder. 48 | /// 49 | /// The created . 50 | public ParameterView Build() => ParameterView.FromDictionary(parameters); 51 | 52 | private static string GetParameterName(Expression> parameterSelector) 53 | { 54 | if (parameterSelector is null) 55 | throw new ArgumentNullException(nameof(parameterSelector)); 56 | 57 | if (!(parameterSelector.Body is MemberExpression memberExpression) || !(memberExpression.Member is PropertyInfo propInfoCandidate)) 58 | throw new ArgumentException($"The parameter selector '{parameterSelector}' does not resolve to a public property on the component '{typeof(TComponent)}'.", nameof(parameterSelector)); 59 | 60 | var propertyInfo = propInfoCandidate.DeclaringType != TComponentType 61 | ? TComponentType.GetProperty(propInfoCandidate.Name, propInfoCandidate.PropertyType) 62 | : propInfoCandidate; 63 | 64 | var paramAttr = propertyInfo?.GetCustomAttribute(inherit: true); 65 | 66 | if (propertyInfo is null || paramAttr is null) 67 | throw new ArgumentException($"The parameter selector '{parameterSelector}' does not resolve to a public property on the component '{typeof(TComponent)}' with a [Parameter] or [CascadingParameter] attribute.", nameof(parameterSelector)); 68 | 69 | return propertyInfo.Name; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /genzor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.6.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2EDCD546-86D2-42CC-B193-4D9047A39622}" 7 | ProjectSection(SolutionItems) = preProject 8 | src\.editorconfig = src\.editorconfig 9 | src\Directory.Build.props = src\Directory.Build.props 10 | EndProjectSection 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "genzor", "src\genzor\genzor.csproj", "{4A7D9BC7-6CAA-4349-8450-876074077A22}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{64C66AEB-7F16-47CD-AAE3-D0F0D4C54554}" 15 | ProjectSection(SolutionItems) = preProject 16 | tests\.editorconfig = tests\.editorconfig 17 | tests\Directory.Build.props = tests\Directory.Build.props 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "genzor.tests", "tests\genzor.tests\genzor.tests.csproj", "{3196B2EC-C5FB-49B4-A29F-ECE139548B9D}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".git", ".git", "{A113856C-3C66-4460-9B95-0B0128CF0AA1}" 23 | ProjectSection(SolutionItems) = preProject 24 | .gitattributes = .gitattributes 25 | .gitignore = .gitignore 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{6FFA6E66-84BF-4AD1-AC24-BE30DC9FF7B3}" 29 | ProjectSection(SolutionItems) = preProject 30 | .editorconfig = .editorconfig 31 | Directory.Build.props = Directory.Build.props 32 | stylecop.json = stylecop.json 33 | version.json = version.json 34 | EndProjectSection 35 | EndProject 36 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".md", ".md", "{ED0F0395-CE8A-45DB-8A63-79D4E31E21AA}" 37 | ProjectSection(SolutionItems) = preProject 38 | README.md = README.md 39 | EndProjectSection 40 | EndProject 41 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "genzor.csharp", "src\genzor.csharp\genzor.csharp.csproj", "{C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}" 42 | EndProject 43 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "genzor.csharp.tests", "tests\genzor.csharp.tests\genzor.csharp.tests.csproj", "{39A85101-8C45-4908-A9F2-E91387DADCD2}" 44 | EndProject 45 | Global 46 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 47 | Debug|Any CPU = Debug|Any CPU 48 | Debug|x64 = Debug|x64 49 | Debug|x86 = Debug|x86 50 | Release|Any CPU = Release|Any CPU 51 | Release|x64 = Release|x64 52 | Release|x86 = Release|x86 53 | EndGlobalSection 54 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 55 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Debug|x64.ActiveCfg = Debug|Any CPU 58 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Debug|x64.Build.0 = Debug|Any CPU 59 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Debug|x86.ActiveCfg = Debug|Any CPU 60 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Debug|x86.Build.0 = Debug|Any CPU 61 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Release|x64.ActiveCfg = Release|Any CPU 64 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Release|x64.Build.0 = Release|Any CPU 65 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Release|x86.ActiveCfg = Release|Any CPU 66 | {4A7D9BC7-6CAA-4349-8450-876074077A22}.Release|x86.Build.0 = Release|Any CPU 67 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Debug|x64.ActiveCfg = Debug|Any CPU 70 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Debug|x64.Build.0 = Debug|Any CPU 71 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Debug|x86.ActiveCfg = Debug|Any CPU 72 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Debug|x86.Build.0 = Debug|Any CPU 73 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Release|Any CPU.Build.0 = Release|Any CPU 75 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Release|x64.ActiveCfg = Release|Any CPU 76 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Release|x64.Build.0 = Release|Any CPU 77 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Release|x86.ActiveCfg = Release|Any CPU 78 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D}.Release|x86.Build.0 = Release|Any CPU 79 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 80 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Debug|Any CPU.Build.0 = Debug|Any CPU 81 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Debug|x64.ActiveCfg = Debug|Any CPU 82 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Debug|x64.Build.0 = Debug|Any CPU 83 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Debug|x86.ActiveCfg = Debug|Any CPU 84 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Debug|x86.Build.0 = Debug|Any CPU 85 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Release|Any CPU.Build.0 = Release|Any CPU 87 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Release|x64.ActiveCfg = Release|Any CPU 88 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Release|x64.Build.0 = Release|Any CPU 89 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Release|x86.ActiveCfg = Release|Any CPU 90 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F}.Release|x86.Build.0 = Release|Any CPU 91 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 92 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Debug|Any CPU.Build.0 = Debug|Any CPU 93 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Debug|x64.ActiveCfg = Debug|Any CPU 94 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Debug|x64.Build.0 = Debug|Any CPU 95 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Debug|x86.ActiveCfg = Debug|Any CPU 96 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Debug|x86.Build.0 = Debug|Any CPU 97 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Release|Any CPU.ActiveCfg = Release|Any CPU 98 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Release|Any CPU.Build.0 = Release|Any CPU 99 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Release|x64.ActiveCfg = Release|Any CPU 100 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Release|x64.Build.0 = Release|Any CPU 101 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Release|x86.ActiveCfg = Release|Any CPU 102 | {39A85101-8C45-4908-A9F2-E91387DADCD2}.Release|x86.Build.0 = Release|Any CPU 103 | EndGlobalSection 104 | GlobalSection(SolutionProperties) = preSolution 105 | HideSolutionNode = FALSE 106 | EndGlobalSection 107 | GlobalSection(NestedProjects) = preSolution 108 | {4A7D9BC7-6CAA-4349-8450-876074077A22} = {2EDCD546-86D2-42CC-B193-4D9047A39622} 109 | {3196B2EC-C5FB-49B4-A29F-ECE139548B9D} = {64C66AEB-7F16-47CD-AAE3-D0F0D4C54554} 110 | {C10CE6B4-473F-4FC0-B479-DBBE5A0C259F} = {2EDCD546-86D2-42CC-B193-4D9047A39622} 111 | {39A85101-8C45-4908-A9F2-E91387DADCD2} = {64C66AEB-7F16-47CD-AAE3-D0F0D4C54554} 112 | EndGlobalSection 113 | GlobalSection(ExtensibilityGlobals) = postSolution 114 | SolutionGuid = {427116E5-89A4-4E41-8703-670BB341706C} 115 | EndGlobalSection 116 | EndGlobal 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | .vscode/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | **/Properties/launchSettings.json 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | *.playlist 333 | bunit.docs/log.txt 334 | 335 | .store 336 | *coverage*.info 337 | .sonarqube 338 | tests/*/coverage 339 | 340 | bunit.v3.ncrunchsolution.user -------------------------------------------------------------------------------- /tests/genzor.tests/GenzorRendererTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AutoFixture.Xunit2; 4 | using FluentAssertions; 5 | using Genzor.Components; 6 | using Genzor.TestGenerators; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Genzor 11 | { 12 | public class GenzorRendererTest : GenzorTestBase 13 | { 14 | public GenzorRendererTest(ITestOutputHelper outputHelper) : base(outputHelper) { } 15 | 16 | [Fact(DisplayName = "when invoking a generator which throws an exception, " + 17 | "then the exception is re-thrown to caller")] 18 | public void Test102() 19 | { 20 | Func throwingAction = () => Host.InvokeGeneratorAsync(); 21 | 22 | throwingAction 23 | .Should() 24 | .Throw(); 25 | } 26 | 27 | [Fact(DisplayName = "given generator that creates a file, " + 28 | "when invoking generator, " + 29 | "then a generated file is added to file system")] 30 | public async Task Test001() 31 | { 32 | await Host.InvokeGeneratorAsync(); 33 | 34 | FileSystem 35 | .Should() 36 | .ContainSingleTextFile() 37 | .WithName(StaticFileGenerator.NameText) 38 | .And 39 | .WithContent(StaticFileGenerator.ContentText); 40 | } 41 | 42 | [AutoData] 43 | [Theory(DisplayName = "given generator that takes parameters, " + 44 | "when invoking generator with parameters, " + 45 | "then parameters are passed to generator")] 46 | public async Task Test011(string filename, string content) 47 | { 48 | await Host.InvokeGeneratorAsync(ps => ps 49 | .Add(p => p.Name, filename) 50 | .Add(p => p.ChildContent, content)); 51 | 52 | FileSystem 53 | .Should() 54 | .ContainSingleTextFile() 55 | .WithName(filename) 56 | .And 57 | .WithContent(content); 58 | } 59 | 60 | [Fact(DisplayName = "given file generator which has a directory as its content, " + 61 | "when invoking generator, " + 62 | "then an InvalidGeneratorComponentContentException is thrown")] 63 | public async Task Test012() 64 | { 65 | Func throwingAction = () => Host.InvokeGeneratorAsync(); 66 | 67 | await throwingAction 68 | .Should() 69 | .ThrowAsync() 70 | .WithMessage($"A directory component ({nameof(IDirectoryComponent)}) cannot be the child of a file component ({nameof(IFileComponent)}). Name of misplaced directory: {FileWithDirectoryGenerator.DirectoryName}"); 71 | } 72 | 73 | [Fact(DisplayName = "given file generator with child components as its content, " + 74 | "when invoking generator, " + 75 | "then content from child components are part of generated files content")] 76 | public async Task Test013() 77 | { 78 | await Host.InvokeGeneratorAsync(); 79 | 80 | FileSystem 81 | .Should() 82 | .ContainSingleTextFile() 83 | .WithContent(StaticFileWithChildComponentGenerator.ChildComponentText); 84 | } 85 | 86 | [Fact(DisplayName = "given file generator with multiple nested child components as its content, " + 87 | "when invoking generator, " + 88 | "then content from all child components are part of generated files content")] 89 | public async Task Test014() 90 | { 91 | var expectedContent = $"{StaticFileWithMultipleNestedChildComponentsGenerator.Child1ComponentText}" + 92 | $"{StaticFileWithMultipleNestedChildComponentsGenerator.Child2ComponentText}"; 93 | 94 | await Host.InvokeGeneratorAsync(); 95 | 96 | FileSystem 97 | .Should() 98 | .ContainSingleTextFile() 99 | .WithContent(expectedContent); 100 | } 101 | 102 | [Fact(DisplayName = "given generator with multiple levels of generic component wrapping a file component, " + 103 | "when invoking generator, " + 104 | "then wrapped file component is added to file system")] 105 | public async Task Test015() 106 | { 107 | await Host.InvokeGeneratorAsync(); 108 | 109 | FileSystem 110 | .Should() 111 | .ContainSingleTextFile() 112 | .WithName(StaticWithMultipleNestedComponentsWrappingFileGenerator.NestedFileName); 113 | } 114 | 115 | [Fact(DisplayName = "given generator that creates multiple file, " + 116 | "when invoking generator, " + 117 | "then generated files is added to file system in generated order")] 118 | public async Task Test021() 119 | { 120 | await Host.InvokeGeneratorAsync(); 121 | 122 | FileSystem 123 | .Should() 124 | .HaveTextFiles(2) 125 | .Which 126 | .Should() 127 | .BeEquivalentTo(new[] 128 | { 129 | new { Name = TwoFileGenerator.FirstFilesName }, 130 | new { Name = TwoFileGenerator.SecondFilesName }, 131 | }); 132 | } 133 | 134 | [Fact(DisplayName = "given generator that creates a directory, " + 135 | "when invoking generator, " + 136 | "then generated directory is added to file system")] 137 | public async Task Test031() 138 | { 139 | await Host.InvokeGeneratorAsync(); 140 | 141 | FileSystem 142 | .Should() 143 | .ContainSingleDirectory() 144 | .WithName(StaticDirectoryGenerator.NameText); 145 | } 146 | 147 | [Fact(DisplayName = "given generator that creates multiple directories, " + 148 | "when invoking generator, " + 149 | "then generated directories is added to file system")] 150 | public async Task Test032() 151 | { 152 | await Host.InvokeGeneratorAsync(); 153 | 154 | FileSystem 155 | .Should() 156 | .HaveDirectories(2) 157 | .Which 158 | .Should() 159 | .BeEquivalentTo(new[] 160 | { 161 | new { Name = TwoDirectoryGenerator.FirstDirectoryName }, 162 | new { Name = TwoDirectoryGenerator.SecondDirectoryName }, 163 | }); 164 | } 165 | 166 | [Fact(DisplayName = "given directory generator that has file generator as its child, " + 167 | "when invoking generator, " + 168 | "then the generated file is added to the generated directory")] 169 | public async Task Test033() 170 | { 171 | await Host.InvokeGeneratorAsync(); 172 | 173 | FileSystem 174 | .Should() 175 | .ContainSingleDirectory() 176 | .Which 177 | .Should() 178 | .ContainSingleTextFile() 179 | .WithName(DirectoryWithFileGenerator.ChildFileName); 180 | } 181 | 182 | [Fact(DisplayName = "given directory generator with multiple nested components that wraps another generator as its child, " + 183 | "when invoking generator, " + 184 | "then the generated file is added to the generated directory")] 185 | public async Task Test034() 186 | { 187 | await Host.InvokeGeneratorAsync(); 188 | 189 | FileSystem 190 | .Should() 191 | .ContainSingleDirectory() 192 | .WithName(StaticFileWithMultipleNestedComponentsWrappingItemsGenerator.DirectoryName) 193 | .And 194 | .WithItemsEquivalentTo(new[] 195 | { 196 | new { Name = StaticFileWithMultipleNestedComponentsWrappingItemsGenerator.NestedDirectoryName }, 197 | new { Name = StaticFileWithMultipleNestedComponentsWrappingItemsGenerator.NestedFileName }, 198 | }); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/genzor/GenzorHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Genzor.Components; 4 | using Genzor.FileSystem; 5 | using Genzor.FileSystem.Internal; 6 | using Microsoft.AspNetCore.Components; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Logging.Abstractions; 10 | 11 | namespace Genzor 12 | { 13 | /// 14 | /// Represents a host for a that 15 | /// handles setting up a default service provider. 16 | /// 17 | public sealed class GenzorHost : IDisposable 18 | { 19 | private readonly IServiceCollection collection; 20 | private ServiceProvider? serviceProvider; 21 | private GenzorRenderer? renderer; 22 | 23 | /// 24 | /// Gets the . 25 | /// 26 | public GenzorRenderer Renderer 27 | { 28 | get 29 | { 30 | if (renderer is null) 31 | { 32 | renderer = CreateRenderer(); 33 | } 34 | 35 | return renderer; 36 | } 37 | } 38 | 39 | private GenzorRenderer CreateRenderer() 40 | { 41 | serviceProvider = collection.BuildServiceProvider(); 42 | var fileSystem = serviceProvider.GetRequiredService(); 43 | var itemFactory = serviceProvider.GetService() ?? new DefaultFileSystemItemFactory(); 44 | var loggerFactory = serviceProvider.GetService() ?? NullLoggerFactory.Instance; 45 | return new GenzorRenderer(fileSystem, itemFactory, serviceProvider, loggerFactory); 46 | } 47 | 48 | /// 49 | /// Initializes a new instance of the class. 50 | /// 51 | public GenzorHost() 52 | { 53 | collection = new ServiceCollection(); 54 | collection.AddSingleton(); 55 | } 56 | 57 | /// 58 | /// Adds an implementation to the for use when generating. 59 | /// 60 | /// The file system instance to use. 61 | /// The type of to use. 62 | /// The so that additional calls can be chained. 63 | public GenzorHost AddFileSystem(TImplementation instance) 64 | where TImplementation : class, IFileSystem 65 | { 66 | collection.AddSingleton(instance); 67 | collection.AddSingleton(instance); 68 | return this; 69 | } 70 | 71 | /// 72 | /// Adds an implementation to the for use when generating. 73 | /// 74 | /// The type of to use. 75 | /// The so that additional calls can be chained. 76 | public GenzorHost AddFileSystem() 77 | where TImplementation : class, IFileSystem 78 | { 79 | collection.AddSingleton(); 80 | collection.AddSingleton(s => s.GetRequiredService()); 81 | return this; 82 | } 83 | 84 | /// 85 | /// Adds logging services to the specified in the . 86 | /// 87 | /// The configuration delegate. 88 | /// The so that additional calls can be chained. 89 | public GenzorHost AddLogging(Action configure) 90 | { 91 | collection.AddLogging(configure); 92 | return this; 93 | } 94 | 95 | /// 96 | /// Adds a singleton service of the type specified in with an implementation 97 | /// type specified in to the . 98 | /// 99 | /// The type of the service to add. 100 | /// The type of the implementation to use. 101 | /// The so that additional calls can be chained. 102 | public GenzorHost AddService() 103 | where TService : class 104 | where TImplementation : class, TService 105 | { 106 | collection.AddSingleton(); 107 | return this; 108 | } 109 | 110 | /// 111 | /// Adds a singleton service of the type specified in with an instance specified 112 | /// in to the . 113 | /// 114 | /// The type of the service to add. 115 | /// The instance of the service. 116 | /// The so that additional calls can be chained. 117 | public GenzorHost AddService(TService implementationInstance) 118 | where TService : class 119 | { 120 | collection.AddSingleton(implementationInstance); 121 | return this; 122 | } 123 | 124 | /// 125 | /// Adds a singleton service of the type specified in with a factory specified 126 | /// in to the specified . 127 | /// 128 | /// The type of the service to add. 129 | /// The factory that creates the service. 130 | /// The so that additional calls can be chained. 131 | public GenzorHost AddService(Func implementationFactory) 132 | where TService : class 133 | { 134 | collection.AddSingleton(implementationFactory); 135 | return this; 136 | } 137 | 138 | /// 139 | /// Invoke (render) the generator and add 140 | /// any or components 141 | /// to the registered with the . 142 | /// 143 | /// The generator component to invoke. 144 | /// Optional parameters to pass to the generator. 145 | /// A that completes when the generator finishes. 146 | public Task InvokeGeneratorAsync(ParameterView? initialParameters) 147 | where TComponent : IComponent 148 | => Renderer.InvokeGeneratorAsync(initialParameters); 149 | 150 | /// 151 | /// Invoke (render) the generator and add 152 | /// any or components 153 | /// to the registered with the . 154 | /// 155 | /// The generator component to invoke. 156 | /// A optional parameter builder action. 157 | /// A that completes when the generator finishes. 158 | public Task InvokeGeneratorAsync(Action>? parametersBuilder = null) 159 | where TComponent : IComponent 160 | { 161 | var pvb = new ParameterViewBuilder(); 162 | parametersBuilder?.Invoke(pvb); 163 | return Renderer.InvokeGeneratorAsync(pvb.Build()); 164 | } 165 | 166 | /// 167 | public void Dispose() 168 | { 169 | renderer?.Dispose(); 170 | serviceProvider?.Dispose(); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/genzor/Internal/FileContentRenderTreeVisitor.cs: -------------------------------------------------------------------------------- 1 | // Some of the code in this class is copied from: 2 | // - https://source.dot.net/#Microsoft.AspNetCore.Mvc.ViewFeatures/RazorComponents/HtmlRenderer.cs 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using Genzor.Components; 7 | using Microsoft.AspNetCore.Components.RenderTree; 8 | 9 | namespace Genzor.Internal 10 | { 11 | internal class FileContentRenderTreeVisitor 12 | { 13 | private readonly IRenderTree renderTree; 14 | 15 | public FileContentRenderTreeVisitor(IRenderTree renderTree) 16 | { 17 | this.renderTree = renderTree; 18 | } 19 | 20 | public string GetTextContent(int componentId) 21 | { 22 | var frames = renderTree.GetCurrentRenderTreeFrames(componentId); 23 | var context = new HtmlRenderingContext(); 24 | var newPosition = RenderFrames(context, frames, 0, frames.Count); 25 | Debug.Assert(newPosition == frames.Count, "All render frames for component was not processes."); 26 | return string.Join(separator: null, context.Result); 27 | } 28 | 29 | private int RenderFrames(HtmlRenderingContext context, ArrayRange frames, int position, int maxElements) 30 | { 31 | var nextPosition = position; 32 | var endPosition = position + maxElements; 33 | while (position < endPosition) 34 | { 35 | nextPosition = RenderCore(context, frames, position); 36 | if (position == nextPosition) 37 | { 38 | throw new InvalidOperationException("We didn't consume any input."); 39 | } 40 | 41 | position = nextPosition; 42 | } 43 | 44 | return nextPosition; 45 | } 46 | 47 | private int RenderCore( 48 | HtmlRenderingContext context, 49 | ArrayRange frames, 50 | int position) 51 | { 52 | ref var frame = ref frames.Array[position]; 53 | switch (frame.FrameType) 54 | { 55 | case RenderTreeFrameType.Element: 56 | return RenderElement(context, frames, position); 57 | case RenderTreeFrameType.Attribute: 58 | throw new InvalidOperationException($"Attributes should only be encountered within {nameof(RenderElement)}"); 59 | case RenderTreeFrameType.Text: 60 | context.Result.Add(frame.TextContent); 61 | return ++position; 62 | case RenderTreeFrameType.Markup: 63 | context.Result.Add(frame.MarkupContent); 64 | return ++position; 65 | case RenderTreeFrameType.Component when frame.Component is IDirectoryComponent dc: 66 | throw InvalidGeneratorComponentContentException.CreateUnexpectedDirectoryException(dc.Name); 67 | case RenderTreeFrameType.Component when frame.Component is not IDirectoryComponent: 68 | return RenderChildComponent(context, frames, position); 69 | case RenderTreeFrameType.Region: 70 | return RenderFrames(context, frames, position + 1, frame.RegionSubtreeLength - 1); 71 | case RenderTreeFrameType.ElementReferenceCapture: 72 | case RenderTreeFrameType.ComponentReferenceCapture: 73 | return ++position; 74 | default: 75 | throw new InvalidOperationException($"Invalid element frame type '{frame.FrameType}'."); 76 | } 77 | } 78 | 79 | private int RenderChildComponent( 80 | HtmlRenderingContext context, 81 | ArrayRange frames, 82 | int position) 83 | { 84 | ref var frame = ref frames.Array[position]; 85 | var childFrames = renderTree.GetCurrentRenderTreeFrames(frame.ComponentId); 86 | RenderFrames(context, childFrames, 0, childFrames.Count); 87 | return position + frame.ComponentSubtreeLength; 88 | } 89 | 90 | private int RenderElement( 91 | HtmlRenderingContext context, 92 | ArrayRange frames, 93 | int position) 94 | { 95 | ref var frame = ref frames.Array[position]; 96 | var result = context.Result; 97 | result.Add("<"); 98 | result.Add(frame.ElementName); 99 | var afterAttributes = RenderAttributes(context, frames, position + 1, frame.ElementSubtreeLength - 1, out var capturedValueAttribute); 100 | 101 | // When we see an