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
IDirectory
andIFile<string>
whenever aIDirectoryComponent
orIFileComponent
is encountered.- Top level
IDirectoryComponent
orIFileComponent
components, i.e. ones not nested inside a parentIDirectoryComponent
component, are added directly to the file system through itsAddItem()
method. IDirectoryComponent
orIFileComponent
components nested inside a parentIDirectoryComponent
component is instead added to the relatedIDirectory
through it'sAdd()
method.
- Top level
- A
IDirectoryComponent
component can contain other content and components, but onlyIDirectoryComponent
orIFileComponent
child components will be added passed to theIDirectory
it maps to. - A
IFileComponent
component 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 aIFileComponent
component cannot contain aIDirectoryComponent
component 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.razor
file inside aGenerators
folder:@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.cs
file 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 run
in 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:packages
rights. - Create a
nuget.config
file 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.