Genzor is an experimental library ideally suited for generating files spanning multiple folders, using Blazor component model to generating the output.
That means all the Blazor goodness such as cascading values, service injection, etc., should work, making it easy to split up complex (code) generation into different Blazor components that each produce a small bit of the total generated output.
For example, consider this simple HelloWorldGenerator.razor generator component from the sample Genzor Stand Alone Console App:
<Directory Name="HelloWorld">
<TextFile Name="hello-text.txt">HELLO TEXT</TextFile>
<Directory Name="NestedHello">
<TextFile Name="nested-hello-text.txt">NESTED HELLO TEXT</TextFile>
</Directory>
</Directory>That will generate the following (assuming standard file system behaviour):
> ls -r .\HelloWorld\
Directory: \GenzorDemo\HelloWorld
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 30-03-2021 23:36 NestedHello
-a--- 30-03-2021 23:36 10 hello-text.txt
Directory: \GenzorDemo\HelloWorld\NestedHello
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 30-03-2021 23:36 17 nested-hello-text.txt
The basic version of Genzor allows you to create components that produce files and directories into a file system abstraction that you provide to Genzor.
The files and directories are represented by two sets of types in Genzor, one in your generator component's render tree and one your file system:
| Type | Generator component's render tree |
File system |
|---|---|---|
| Directory | IDirectoryComponent |
IDirectory |
| File | IFileComponent |
IFile<TContent> |
NOTE: Genzor comes with an implemetation of the IDirectoryComponent and IFileComponent types to make it easier to get started. These are simply named Directory and TextFile. But you are free to create your own as needed.
Genzor will add files and directories to the file system abstraction you provide like this:
- Invoke (render) the generator component, pass in any regular
[Parameter]parameters to it or[Inject]services into it. - Walk through the entire render tree, and create
IDirectoryandIFile<string>whenever aIDirectoryComponentorIFileComponentis encountered.- Top level
IDirectoryComponentorIFileComponentcomponents, i.e. ones not nested inside a parentIDirectoryComponentcomponent, are added directly to the file system through itsAddItem()method. IDirectoryComponentorIFileComponentcomponents nested inside a parentIDirectoryComponentcomponent is instead added to the relatedIDirectorythrough it'sAdd()method.
- Top level
- A
IDirectoryComponentcomponent can contain other content and components, but onlyIDirectoryComponentorIFileComponentchild components will be added passed to theIDirectoryit maps to. - A
IFileComponentcomponent can contain other content and components, whose rendered output will be added to as the content of theIFile<string>that it maps to. The only exception is that aIFileComponentcomponent cannot contain aIDirectoryComponentcomponent inside it.
The following creates a console application that uses Genzor to run the HelloWorldGenerator.razor generator component.
NOTE: You can also download the sample from https://github.com/egil/genzor/tree/main/samples/GenzorStandAloneConsoleApp if you prefer.
The steps are as follows:
-
Create new console app, e.g. using
dotnet new console -o GenzorDemo. -
Change the project SDK type to
Microsoft.NET.Sdk.Razor. -
Add the Microsoft.Extensions.DependencyInjection package to the project, e.g. using
dotnet add package Microsoft.Extensions.DependencyInjection. -
Optionally, to see log output from Genzor, add the Microsoft.Extensions.Logging.Console package to the project as well, e.g. using
dotnet add package Microsoft.Extensions.Logging.Console. -
Add the Genzor package to the project. It is hosted currently here on GitHubs Package Repository, but will show up on NuGet.org if this turns out to be useful to folks. See the guide below for how to connect to it.
-
Update the Program.cs file to look as follows:
using System.IO; using System.Threading.Tasks; using Genzor; using Microsoft.Extensions.Logging; using GenzorDemo.Generators; namespace GenzorDemo { class Program { static async Task Main(string[] args) { var fileSystem = new FileSystem(new DirectoryInfo(Directory.GetCurrentDirectory())); using var host = new GenzorHost() .AddLogging(configure => configure .AddConsole() .SetMinimumLevel(LogLevel.Debug)) // if the optional logging package has beed added .AddFileSystem(fileSystem); await host.InvokeGeneratorAsync<HelloWorldGenerator>(); } } }
-
Create a new directory in the project named
Generators(not strictly a requirement from Genzor, but it groups the generators together here). -
Add the following
HelloWorldGenerator.razorfile inside aGeneratorsfolder:@using Genzor.Components <Directory Name="HelloWorld"> <TextFile Name="hello-text.txt">HELLO TEXT</TextFile> <Directory Name="NestedHello"> <TextFile Name="nested-hello-text.txt">NESTED HELLO TEXT</TextFile> </Directory> </Directory>
-
Add the following
FileSystem.csfile to the project (this is our basic implementation of Genzor'sIFileSystem):using System; using System.IO; using Genzor.FileSystem; namespace GenzorDemo { class FileSystem : IFileSystem { private readonly DirectoryInfo rootDirectory; public FileSystem(DirectoryInfo rootDirectory) => this.rootDirectory = rootDirectory ?? throw new ArgumentNullException(nameof(rootDirectory)); public void AddItem(IFileSystemItem item) => AddItem(rootDirectory, item); private void AddItem(DirectoryInfo parent, IFileSystemItem item) { switch (item) { case IDirectory directory: AddDirectory(parent, directory); break; case IFile<string> textFile: AddTextFile(parent, textFile); break; default: throw new NotImplementedException($"Unsupported file system item {item.GetType().FullName}"); } } private void AddDirectory(DirectoryInfo parent, IDirectory directory) { var createdDirectory = parent.CreateSubdirectory(directory.Name); foreach (var item in directory) AddItem(createdDirectory, item); } private void AddTextFile(DirectoryInfo parent, IFile<string> file) { var fullPath = Path.Combine(parent.FullName, file.Name); File.WriteAllText(fullPath, file.Content); } } }
-
Run the application, e.g. by typing
dotnet runin a terminal. After the app runs, you should see some files created in whatever is your "current directory" when running the app.
To be able to download packages from GitHub Package Repository, do the following:
- Go into your GitHub settings under security tokens and generate a new access token. The token should only have
read:packagesrights. - Create a
nuget.configfile and place it in your project folder. - Add the following content to the
nuget.config, replacing USERNAME with your GitHub username and TOKEN with the generated token from step 1:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="github" value="https://nuget.pkg.github.com/egil/index.json" />
</packageSources>
<packageSourceCredentials>
<github>
<add key="Username" value="USERNAME" />
<add key="ClearTextPassword" value="TOKEN" />
</github>
</packageSourceCredentials>
</configuration>
Then you should be able to do a dotnet add package or dotnet restore and pull packages from my GPR feed.