﻿<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text" xml:lang="en">MfGames.Gallium</title>
  <link type="application/atom+xml" href="https://d.moonfire.us/tags/mfgames-gallium/atom.xml" rel="self" />
  <link type="text/html" href="https://d.moonfire.us/tags/mfgames-gallium/" rel="alternate" />
  <updated>2026-03-16T17:43:08Z</updated>
  <id>https://d.moonfire.us/tags/mfgames-gallium/</id>
  <author>
    <name>D. Moonfire</name>
  </author>
  <rights>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</rights>
  <entry>
    <title>Using Nitride - Front Matter</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2025/06/12/using-nitride-front-matter/" />
    <updated>2025-06-12T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2025/06/12/using-nitride-front-matter/</id>
    <category term="development" scheme="https://d.moonfire.us/categories/" label="Development" />
    <category term="mfgames-nitride" scheme="https://d.moonfire.us/tags/" label="MfGames.Nitride" />
    <category term="mfgames-gallium" scheme="https://d.moonfire.us/tags/" label="MfGames.Gallium" />
    <category term="yaml" scheme="https://d.moonfire.us/tags/" label="YAML" />
    <category term="opengraph" scheme="https://d.moonfire.us/tags/" label="OpenGraph" />
    <category term="handlebars" scheme="https://d.moonfire.us/tags/" label="Handlebars" />
    <summary type="html">How to use Nitride to take the front matter from pages and add them as a component into the Entity class.
</summary>
    <content type="html">&lt;p&gt;Being able to associated metadata for a page is a very useful thing. It can be used to group pages into categories, control how it is styled, or to simply provide internal notes. To do that, we use something called a YAML front matter to describe the details.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;---
title: Name of the Page
summary: Summary of page
image: /path/to/image.png
---

This is the front page.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a major functionality of the Markdown + YAML pages that I use in my technical, fantasy, and even blog posts. I use the categories and tags heavily on this page, not to mention giving a summary details for links. It can also be used to provide &lt;a href="/tags/opengraph/"&gt;OpenGraph&lt;/a&gt; schemas.&lt;/p&gt;
&lt;h2&gt;Series&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/07/using-nitride-introduction/"&gt;2025-06-07 Using Nitride - Introduction&lt;/a&gt; - I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/08/using-nitride-pipelines/"&gt;2025-06-08 Using Nitride - Pipelines&lt;/a&gt; - The first major concept of Nitride is the idea of a &amp;quot;pipeline&amp;quot; which are the largest units of working in the system. This shows a relatively complex setup of pipelines that will be used to generate the website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/09/using-nitride-entities/"&gt;2025-06-09 Using Nitride - Entities&lt;/a&gt; - The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/10/using-nitride-markdown/"&gt;2025-06-10 Using Nitride - Markdown&lt;/a&gt; - Examples and explanations of converting Markdown to HTML using MfGames.Nitride and MarkDig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/12/using-nitride-front-matter/"&gt;2025-06-12 Using Nitride - Front Matter&lt;/a&gt; - How to use Nitride to take the front matter from pages and add them as a component into the Entity class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Defining a Model&lt;/h2&gt;
&lt;p&gt;Because C# is a static language, we want to take advantage of a schema so we can have type-safe. Historically, I've called this &lt;code&gt;PageModel&lt;/code&gt; because I wrap that in a &lt;code&gt;TemplateModel&lt;/code&gt; when I get to the styling.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Models/PageModel.cs
namespace Generator.Models;

public class PageModel
{
    /// &amp;lt;summary&amp;gt;
    ///     Gets or sets the optional list of categories associated with the page.
    /// &amp;lt;/summary&amp;gt;
    public List&amp;lt;string&amp;gt;? Categories { get; set; }

    /// &amp;lt;summary&amp;gt;
    ///     Gets or sets the URL of the image associated with the page.
    /// &amp;lt;/summary&amp;gt;
    public string? Image { get; set; }

    /// &amp;lt;summary&amp;gt;
    ///     Gets or sets the summary for the page.
    /// &amp;lt;/summary&amp;gt;
    public string? Summary { get; set; }

    /// &amp;lt;summary&amp;gt;
    ///     Gets or sets the title of the page.
    /// &amp;lt;/summary&amp;gt;
    public string? Title { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Naturally, this can and will get a lot more complicated. Because we are using YAML, we can have nested objects, tags, and references that are appropriate for our page. In the above example, we are defining an optional title, summary, an image, and a list of categories.&lt;/p&gt;
&lt;p&gt;It may come to a surprise to you, but this will eventually become a component in the page &lt;code&gt;Entity&lt;/code&gt; class that is passed through the pipelines.&lt;/p&gt;
&lt;h2&gt;Including the YAML module.&lt;/h2&gt;
&lt;p&gt;Like the others &lt;a href="/tags/mfgames-nitride/"&gt;MfGames.Nitride&lt;/a&gt; modules, we have to add it as a NuGet reference and tell the system to use it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;cd src/dotnet
dotnet add package MfGames.Nitride.Yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding it to the system is just a matter of modifying &lt;code&gt;Program.cs&lt;/code&gt; to include it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Program.cs
var builder = new NitrideBuilder(args)
    .UseIO(rootDirectory)
    .UseMarkdown()
    .UseHtml()
    .UseYaml()
    .UseModule&amp;lt;WebsiteModule&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Parsing Front Matter&lt;/h2&gt;
&lt;p&gt;Parsing the front matter uses an operation, &lt;code&gt;MfGames.Nitride.Yaml.ParseYamlHeader&lt;/code&gt; to parse the text content, pull off the front matter, wrap it in the model call, and then replace &lt;code&gt;ITextContent&lt;/code&gt; with the page without the YAML header.&lt;/p&gt;
&lt;p&gt;In effect, this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;---
title: Name of the Page
summary: Summary of page
image: /path/to/image.png
---

This is the front page.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;... becomes &amp;ldquo;This is the front page.&amp;rdquo; in the text content with a &lt;code&gt;PageModel&lt;/code&gt; component.&lt;/p&gt;
&lt;p&gt;Adding the operation is relatively simple but this operation uses a generic parameter to identify the model to parse the YAML as.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Pipelines/Inputs/PagesModel.cs
public PagesPipeline(
    ILogger&amp;lt;PagesPipeline&amp;gt; logger,
    ReadFiles readFiles,
    IdentifyMarkdownFromPath identifyMarkdownFromPath,
    MoveToIndexPath moveToIndexPath,
    ParseYamlHeader&amp;lt;PageModel&amp;gt; parseYamlHeader)
{
    _logger = logger;
    _identifyMarkdownFromPath = identifyMarkdownFromPath;
    _moveToIndexPath = moveToIndexPath;
    _parseYamlHeader = parseYamlHeader;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, like the others, it's just a matter of adding the operation into the pipeline.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// In //src/dotnet/Pipelines/Inputs/PagesModel.cs
var list = _readFiles
    .Run(cancellationToken)
    .Run(_identifyMarkdownFromPath)
    .Run(_parseYamlHeader)
    .Run(_moveToIndexPath)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we run it, we get the following output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[01:44:05 INF] &amp;lt;PagesPipeline&amp;gt; Entity: Path /contact/index.md, Components [&amp;quot;Zio.UPath&amp;quot;,&amp;quot;Generator.Models.PageModel&amp;quot;,&amp;quot;MfGames.Nitride.Contents.ITextContent&amp;quot;,&amp;quot;MfGames.Nitride.Markdown.IsMarkdown&amp;quot;]
[01:44:05 INF] &amp;lt;PagesPipeline&amp;gt; Entity: Path /index.md, Components [&amp;quot;Zio.UPath&amp;quot;,&amp;quot;Generator.Models.PageModel&amp;quot;,&amp;quot;MfGames.Nitride.Contents.ITextContent&amp;quot;,&amp;quot;MfGames.Nitride.Markdown.IsMarkdown&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we look at the HTML output, you'll notice it has a simplified version.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ cat build/typewriter/html/index.html 
&amp;lt;h1&amp;gt;Typewriter Press&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Retrieving the component is code just requires the &lt;code&gt;Get&amp;lt;PageModel&amp;gt;()&lt;/code&gt; method or to use the various LINQ-based calls from &lt;a href="/tags/mfgames-gallium/"&gt;MfGames.Gallium&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var page = entity.Get&amp;lt;PageModel&amp;gt;();

var list = entities
    .SelectEntity&amp;lt;PageModel&amp;gt;(OnlyRunOnEntitiesWithPageModel)
    .ToList();

public Entity OnlyRunOnEntitiesWithPageModel(Entity entity, PageModel page)
{
    return entity.Set(somePageSpecificVariable);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Gallium Methods&lt;/h2&gt;
&lt;p&gt;In the above case, the &lt;code&gt;SelectEntity&lt;/code&gt; call will skip calling the lambda for entities that don't have a &lt;code&gt;PageModel&lt;/code&gt;, but will still return it into the list. This is the &amp;ldquo;systems&amp;rdquo; part of the ECS system. The following will strip out the entities that don't have the appropriate models.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;.SelectEntity&amp;lt;PageModel&amp;gt;((entity, page) =&amp;gt; entity, includeEntitiesWithoutComponents: false)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far, Gallium is set up to allow up to four components. I haven't had a need to do more, but those are easy to add. This makes it useful when doing some model processing on files that have a future date.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;.WhereEntity&amp;lt;PageModel, UPage, Instant&amp;gt;(FilterOutFuturePages)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;SelectEntity&lt;/code&gt; is rather powerful because it takes an &lt;code&gt;Entity&lt;/code&gt; and returns one. It can be the same entity, or it can be one that has zero or more components added or changed inside it. I found this really useful when I'm parsing out categories or I'm trying to build up a list of some specialized functionality that the styling will need.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public Entity OnSelectEntity(
    Entity entity,
    PageModel page,
    UPath path,
    ITextContent textContent)
{
    return entity
        .Set(nextPreviousModel)
        .SetAll(parentPage, parentUrl)
        .Set(modifiedPageModel);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What's Next&lt;/h2&gt;
&lt;p&gt;We have the minimum number of components and systems needed to setup styling. Next time, I'll use &lt;a href="/tags/handlebars/"&gt;Handlebars&lt;/a&gt; to style the page and put a little chrome around the output.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/07/using-nitride-introduction/"&gt;2025-06-07 Using Nitride - Introduction&lt;/a&gt; - I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/08/using-nitride-pipelines/"&gt;2025-06-08 Using Nitride - Pipelines&lt;/a&gt; - The first major concept of Nitride is the idea of a &amp;quot;pipeline&amp;quot; which are the largest units of working in the system. This shows a relatively complex setup of pipelines that will be used to generate the website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/09/using-nitride-entities/"&gt;2025-06-09 Using Nitride - Entities&lt;/a&gt; - The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/10/using-nitride-markdown/"&gt;2025-06-10 Using Nitride - Markdown&lt;/a&gt; - Examples and explanations of converting Markdown to HTML using MfGames.Nitride and MarkDig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/12/using-nitride-front-matter/"&gt;2025-06-12 Using Nitride - Front Matter&lt;/a&gt; - How to use Nitride to take the front matter from pages and add them as a component into the Entity class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Using Nitride - Entities</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2025/06/09/using-nitride-entities/" />
    <updated>2025-06-09T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2025/06/09/using-nitride-entities/</id>
    <category term="development" scheme="https://d.moonfire.us/categories/" label="Development" />
    <category term="mfgames-nitride" scheme="https://d.moonfire.us/tags/" label="MfGames.Nitride" />
    <category term="mfgames-gallium" scheme="https://d.moonfire.us/tags/" label="MfGames.Gallium" />
    <category term="statiq" scheme="https://d.moonfire.us/tags/" label="Statiq" />
    <category term="cobblestonejs" scheme="https://d.moonfire.us/tags/" label="CobblestoneJS" />
    <category term="gatsbyjs" scheme="https://d.moonfire.us/tags/" label="GatsbyJS" />
    <category term="autofac" scheme="https://d.moonfire.us/tags/" label="Autofac" />
    <category term="zio" scheme="https://d.moonfire.us/tags/" label="Zio" />
    <category term="serilog" scheme="https://d.moonfire.us/tags/" label="Serilog" />
    <summary type="html">The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.
</summary>
    <content type="html">&lt;p&gt;From yesterday's post, there was an dangling topic, the &lt;code&gt;Entity&lt;/code&gt; class. Unlike most of the static site generators I've worked with, &lt;a href="/tags/mfgames-nitride/"&gt;MfGames.Nitride&lt;/a&gt; uses an ECS (&lt;a href="https://en.wikipedia.org/wiki/Entity_component_system"&gt;Entity Component System&lt;/a&gt;) to generate its files.&lt;/p&gt;
&lt;h2&gt;Series&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/07/using-nitride-introduction/"&gt;2025-06-07 Using Nitride - Introduction&lt;/a&gt; - I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/08/using-nitride-pipelines/"&gt;2025-06-08 Using Nitride - Pipelines&lt;/a&gt; - The first major concept of Nitride is the idea of a &amp;quot;pipeline&amp;quot; which are the largest units of working in the system. This shows a relatively complex setup of pipelines that will be used to generate the website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/09/using-nitride-entities/"&gt;2025-06-09 Using Nitride - Entities&lt;/a&gt; - The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/10/using-nitride-markdown/"&gt;2025-06-10 Using Nitride - Markdown&lt;/a&gt; - Examples and explanations of converting Markdown to HTML using MfGames.Nitride and MarkDig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/12/using-nitride-front-matter/"&gt;2025-06-12 Using Nitride - Front Matter&lt;/a&gt; - How to use Nitride to take the front matter from pages and add them as a component into the Entity class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;History&lt;/h2&gt;
&lt;p&gt;I've worked on a number of static site generators over the years, including more than a few adhoc ones until I finally got something stable with &lt;a href="/tags/cobblestonejs/"&gt;CobblestoneJS&lt;/a&gt; which carried me for almost a decade of heavy use. A lot of the patterns that Nitride was based on were on how I found I had to build my pages in Cobblestone, including having a separate &amp;ldquo;gather&amp;rdquo; step which wrote out temporary files and then the &amp;ldquo;process&amp;rdquo; step which formatted them and made everything pretty. I had to do that because I wanted to have cross-linking and references work across multiple Git projects.&lt;/p&gt;
&lt;p&gt;A good example is the &lt;a href="//fedran.com/"&gt;Fedran&lt;/a&gt; website. At the bottom of a &lt;a href="//fedran.com/sand-and-blood/chapter-001/"&gt;chapter&lt;/a&gt;, I have a list of characters and locations in the chapter. The links are built from data from the wiki project but the chapter information comes from the story Git repository. The gather step let me combine both of those together to generate those links.&lt;/p&gt;
&lt;p&gt;That also meant that I needed to have some supplementary information stored in the gather step (which was written to a &lt;code&gt;//build&lt;/code&gt; folder) to help with those linking.&lt;/p&gt;
&lt;p&gt;Near the end, once Cobblestone began to break under its abstraction and complexity, I wanted to get away from writing my own and switched to &lt;a href="/tags/gatsbyjs/"&gt;GatsbyJS&lt;/a&gt; which made a very pretty website, but it was painful to debug and develop. It only lasted a year or so before I wanted to move (though the Gatsby version of Fedran was probably the prettiest I had ever made). If anything, Gatsby ended up being an ecosystem that needed me to pay attention to update things and my cycle of coming around to update a project was too far for it so I spent days trying to get it working again to build it.&lt;/p&gt;
&lt;p&gt;So I tried out &lt;a href="/tags/statiq/"&gt;Statiq&lt;/a&gt;. I never successfully made a website out of it, but it introduced me to some really interesting concepts including pulling in data from other sources (Gatsby also did this, just never really used it). But with the C#, it had some really interesting problems. However, I quickly found that I was trying to do something that was outside of the planned usage, which involved me trying to extend an enumeration which involved basically rewriting large hunks of the system just to handle it. Plus, I really like &lt;a href="/tags/autofac/"&gt;Autofac&lt;/a&gt; and I could never get it to play well with that.&lt;/p&gt;
&lt;p&gt;Which lead me into creating Nitride.&lt;/p&gt;
&lt;p&gt;Now, I want to say, both Gatsby and Statiq are both good systems. They just aren't good systems &lt;em&gt;for me&lt;/em&gt;. These don't do what I want them to do and they are designed around patterns that I'm not comfortable with. Even with the desire not to write my own, it wasn't enough.&lt;/p&gt;
&lt;h2&gt;Entity Component Systems&lt;/h2&gt;
&lt;p&gt;Nitride is build on &lt;a href="/tags/mfgames-gallium/"&gt;MfGames.Gallium&lt;/a&gt; which basically means the formal name of the system &lt;a href="https://en.wikipedia.org/wiki/Gallium_nitride"&gt;Gallium Nitride&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Gallium nitride (GaN) is a binary III/V direct bandgap semiconductor commonly used in blue light-emitting diodes since the 1990s. The compound is a very hard material that has a Wurtzite crystal structure. Its wide band gap of 3.4 eV affords it special properties for applications in optoelectronics, high-power and high-frequency devices. For example, GaN is the substrate that makes violet (405 nm) laser diodes possible, without requiring nonlinear optical frequency doubling.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gallium is a small ECS library. It doesn't intend to be fast or efficient, but it was written to match the idiomatic patterns of &lt;code&gt;System.Linq&lt;/code&gt;. I wanted something that wasn't going to reinvent the wheel, which also meant not making custom collection classes or lookup tables. Instead, I wanted to work with BCL primitives which is why much of the system is based on &lt;code&gt;IEnumerable&amp;lt;Entity&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There is some high-level documentation for Gallium on &lt;a href="//mfgames.com/mfgames-cil/docs/gallium/"&gt;its project page&lt;/a&gt; and its &lt;a href="https://src.mfgames.com/mfgames-cil/mfgames-cil/src/branch/main/tests/MfGames.Gallium.Tests"&gt;test&lt;/a&gt;, but I'll give the basics here.&lt;/p&gt;
&lt;p&gt;The core object is &lt;code&gt;MfGames.Gallium.Entity&lt;/code&gt;. This is a &lt;a href="https://src.mfgames.com/mfgames-cil/mfgames-cil/src/branch/main/src/MfGames.Gallium/Entity.cs"&gt;small class&lt;/a&gt; that has an integer identifier and basically a &lt;code&gt;Dictionary&amp;lt;Type, object&amp;gt;&lt;/code&gt; called &lt;code&gt;Components&lt;/code&gt;. It has some methods to make it easier, but basically you can set or get components from it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// This creates an empty entity.
Entity entity1 = new();

// This creates an entity with four components:
//   &amp;quot;System.Int32&amp;quot;: 123
//   &amp;quot;System.String&amp;quot;: &amp;quot;bob&amp;quot;
//   &amp;quot;System.IO.FileInfo&amp;quot;: ...
//   &amp;quot;System.IO.DirectoryInfo&amp;quot;: ...
Entity entity2 = new()
    .Set(123)
    .Set(&amp;quot;bob&amp;quot;)
    .SetAll(
        new FileInfo(&amp;quot;/tmp/bob.txt&amp;quot;),
        new DirectoryInfo(&amp;quot;/tmp&amp;quot;)
    );

Assert.True(entity2.HasComponent&amp;lt;FileInfo&amp;gt;);
Assert.False(entity2.HasComponent&amp;lt;decimal&amp;gt;);

Assert.True(123, entity2.Get&amp;lt;int&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In effect, the class of the component is the key. There are a number of other functions, the &lt;a href="https://src.mfgames.com/mfgames-cil/mfgames-cil/src/branch/main/tests/MfGames.Gallium.Tests/EntityTests.cs"&gt;test class&lt;/a&gt; is probably the best documentation at this point.&lt;/p&gt;
&lt;p&gt;The systems part of the system is the LINQ-inspired collection classes. Most of them work off &lt;code&gt;IEnumerable&amp;lt;Entity&amp;gt;&lt;/code&gt; with the &lt;a href="https://src.mfgames.com/mfgames-cil/mfgames-cil/src/branch/main/tests/MfGames.Gallium.Tests/EnumerableEntityTests.cs"&gt;test class&lt;/a&gt; being the bulk of the documentation:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// Set up a list of entities.
List&amp;lt;Entity&amp;gt; list = ...;

// This gets all the entities that have a FileInfo.
var fileOnlyList = list.WhereEntityHas&amp;lt;FileInfo&amp;gt;().ToList();

// This gets all entities that have both a FileInfo and an int.
var fileAndIntList = list.WhereEntityHasAll&amp;lt;FileInfo, int&amp;gt;().ToList();

// Get a modified list based on components. This looks for all
// entities that have a FileInfo and then adds a marker component,
// IsHtml, into it.
IsHtml isHtml = IsHtml.Instance;

var modifiedList = list
    .SelectEntity&amp;lt;FileInfo&amp;gt;((entity, file) =&amp;gt; entity.Set(isHtml))
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a note, &lt;code&gt;Entity&lt;/code&gt; is &amp;ldquo;mostly&amp;rdquo; immutable. The &lt;code&gt;Set&lt;/code&gt; command will return a new &lt;code&gt;Entity&lt;/code&gt; instance with the same ID but with a different set of components. The &amp;ldquo;mostly&amp;rdquo; bit comes that the objects that it points to can be mutated.&lt;/p&gt;
&lt;p&gt;🛈 The lack of formal documentation for Gallium and Nitride is one of the reasons this is still alpha or beta level code.&lt;/p&gt;
&lt;h2&gt;Operations&lt;/h2&gt;
&lt;p&gt;The next concept needed for today's post is an operation which extends &lt;code&gt;MfGames.Nitride.IOperation&lt;/code&gt;. These are just easy ways of creating some functionality that works on an &lt;code&gt;IEnumerable&amp;lt;Entity&amp;gt;&lt;/code&gt; that can be combined together.&lt;/p&gt;
&lt;p&gt;Naturally, there are some extension methods that make it easier to work with operations with an &lt;code&gt;IEnumerable&amp;lt;Entity&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;IEnumerable&amp;lt;Entity&amp;gt; entityList = ...;

IEnumerable&amp;lt;Entity&amp;gt; otherList = someOperation1.Run(entityList);

IEnumerable&amp;lt;Entity&amp;gt; resultList = otherList
    .Run(someOperation2)
    .Run(someOperation3)
    .Run(someOperation4);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes it relatively easy to create easy processing through composition of one or more operations. Alternatively, we could easily just work directly off the enumerable.&lt;/p&gt;
&lt;h2&gt;Zio&lt;/h2&gt;
&lt;p&gt;There is no question, I'm fond of the &lt;a href="/tags/zio/"&gt;Zio&lt;/a&gt; library. In this case, Nitride uses two big features with it: to create an abstract view of the project that is independent of the working directory and the &lt;code&gt;UPath&lt;/code&gt; class which normalizes paths.&lt;/p&gt;
&lt;p&gt;If you remember from the previous posts, we set a root directory in the &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public static async Task&amp;lt;int&amp;gt; Main(string[] args)
{
    var rootDirectory = new DirectoryInfo(Environment.CurrentDirectory);

    var builder = new NitrideBuilder(args)
        .UseIO(rootDirectory)
        .UseModule&amp;lt;WebsiteModule&amp;gt;();

    return await builder.RunAsync();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This caused an &lt;code&gt;Zio.IFileSystem&lt;/code&gt; to be injected in the system which treats that directory as the &lt;code&gt;/&lt;/code&gt;. So, in our case where we had our index file in &lt;code&gt;//src/pages/index.md&lt;/code&gt;, it has a &lt;code&gt;UPath&lt;/code&gt; of &lt;code&gt;/src/pages/index.md&lt;/code&gt;. It doesn't seem like a lot, but it means that all paths in the system are always relative to the root of the project and there is no need to pass a root directory throughout the system which means path operations are considerably simplified.&lt;/p&gt;
&lt;p&gt;And the &lt;code&gt;UPath&lt;/code&gt; ensures a consistent pattern for accessing so this works on Windows, Mac, and Linux without a problem. Not to mention, any issues with &lt;code&gt;..&lt;/code&gt; are handled automatically.&lt;/p&gt;
&lt;p&gt;It also simplifies writing tests.&lt;/p&gt;
&lt;h2&gt;Copy File in Six Files&lt;/h2&gt;
&lt;p&gt;Put all that together, we can use two operations to create a massively over-engineered copy file operation using Nitride. Admittedly, the complexity will make sense later, but at the moment, we're going to read the file in the &lt;code&gt;PagesPipeline&lt;/code&gt;, through &lt;code&gt;SimplifiedMarkdownPipeline&lt;/code&gt;, through &lt;code&gt;StyleHtmlPipeline&lt;/code&gt;, and finally write it out in &lt;code&gt;OutputHtmlPipeline&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Making Some Noise&lt;/h3&gt;
&lt;p&gt;To start with, let's put some logging to show how everything flows from pipeline to pipeline. To do that, we're going to inject an &lt;code&gt;ILogger&amp;lt;&amp;gt;&lt;/code&gt; into the constructor into all four pipelines and then put a logging message into the last three (the &lt;code&gt;PagesPipeline&lt;/code&gt; is going to do something different).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Pipelines/Content/SimplifiedMarkdownPipeline.cs
public class SimplifiedMarkdownPipeline : PipelineBase
{
    private readonly ILogger _logger;

    public SimplifiedMarkdownPipeline(
        ILogger&amp;lt;SimplifiedMarkdownPipeline&amp;gt; logger,
        PagesPipeline pagesPipeline)
    {
        _logger = logger;

        AddDependency(pagesPipeline);
    }

    public override IAsyncEnumerable&amp;lt;Entity&amp;gt; RunAsync(
        IEnumerable&amp;lt;Entity&amp;gt; entities,
        CancellationToken cancellationToken = default)
    {
        var list = entities.ToList();

        _logger.LogInformation(&amp;quot;Reading {Count:N0} entities&amp;quot;, list.Count);

        return list.ToAsyncEnumerable();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Internally, Nitride uses &lt;a href="/tags/serilog/"&gt;Serilog&lt;/a&gt;, so using &lt;code&gt;{Count:N0}&lt;/code&gt; does the right thing even thought we are using a logging abstraction for the actual call. When we run this, we get the following output (I'm not going to show the &lt;code&gt;just build&lt;/code&gt; call for these today):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[14:00:46 DBG] &amp;lt;DirectoryService&amp;gt; Setting root directory and filesystem to /home/dmoonfire/src/typewriter-press/typewriter-press-website
[14:00:46 DBG] &amp;lt;BuildCommand&amp;gt; Processing 0 pipeline options
[14:00:46 INF] &amp;lt;BuildCommand&amp;gt; Running pipelines
[14:00:46 INF] &amp;lt;SimplifiedMarkdownPipeline&amp;gt; Reading 0 entities
[14:00:46 INF] &amp;lt;StyleHtmlPipeline&amp;gt; Reading 0 entities
[14:00:46 INF] &amp;lt;OutputHtmlPipeline&amp;gt; Reading 0 entities
[14:00:46 INF] &amp;lt;PipelineManager&amp;gt; Completed in 00:00:00.1079413
[14:00:46 INF] &amp;lt;BuildCommand&amp;gt; Command took 00:00:00.1097142
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🛈 I moved &lt;code&gt;NuGet.config&lt;/code&gt; to the root level because there is where the &lt;code&gt;Website.sln&lt;/code&gt; file was located and it felt right.&lt;/p&gt;
&lt;h3&gt;Reading Files&lt;/h3&gt;
&lt;p&gt;Now, to read a file, we are going to use the &lt;code&gt;MfGames.Nitride.IO.ReadFiles&lt;/code&gt; operation which reads file from the Zio file system and passes them through. We've already included that NuGet package in a previous step, so nothing to do there.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Inputs/PagesPipeline.cs
using MfGames.Gallium;
using MfGames.Nitride.IO.Contents;
using MfGames.Nitride.Pipelines;
using Microsoft.Extensions.Logging;

namespace Generator.Pipelines.Inputs;

public class PagesPipeline : PipelineBase
{
    private readonly ILogger _logger;
    private readonly ReadFiles _readFiles;

    public PagesPipeline(
        ILogger&amp;lt;PagesPipeline&amp;gt; logger,
        ReadFiles readFiles)
    {
        _logger = logger;
        _readFiles = readFiles.WithPattern(&amp;quot;/src/pages/**/*.md&amp;quot;);
    }

    public override IAsyncEnumerable&amp;lt;Entity&amp;gt; RunAsync(
        IEnumerable&amp;lt;Entity&amp;gt; entities,
        CancellationToken cancellationToken = default)
    {
        var list = _readFiles
            .Run(cancellationToken)
            .ToList();

        _logger.LogInformation(
            &amp;quot;Read in {Count:N0} files from /src/pages&amp;quot;,
            list.Count);

        foreach (var entity in list)
        {
            _logger.LogInformation(&amp;quot;Entity: Path {Path}, Components {ComponentList}&amp;quot;,
                entity.Get&amp;lt;UPath&amp;gt;(),
                entity.GetComponentTypes().Select(x =&amp;gt; x.FullName));
        }

        return list.ToAsyncEnumerable();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This requires a bit of explanation. We are using dependency injection to insert the &lt;code&gt;ReadFiles&lt;/code&gt; operation into the constructor. This is not a singleton, so you can have multiple &lt;code&gt;ReadFiles&lt;/code&gt; if you need to read from multiple locations (or use a globbing operator to do it in one).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;_readFiles = readFiles.WithPattern(&amp;quot;/src/pages/**/*.md&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the constructor, I configure it. I could do this in the &lt;code&gt;RunAsync&lt;/code&gt; function but I found it made it easier to parse the files when I configure in the constructor. Like most things in Nitride, this uses a fluent coding style where each object sets the properties and returns itself to make a nice chained list of calls.&lt;/p&gt;
&lt;p&gt;There are two properties being set, &lt;code&gt;Pattern&lt;/code&gt; and &lt;code&gt;RemovePathPrefix&lt;/code&gt;. I use a source generator to automatically create the &lt;code&gt;With...&lt;/code&gt; pattern for fluent coding. The first one tells which files to read, relative to the root directory. This happen to use a globbing that allows me to grab all Markdown files in the source directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;var list = _readFiles
    .Run(cancellationToken)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This run the _readFiles operation and gets a list of the files it read in. (I'm also going to put this in the &lt;code&gt;OutputHtmlPipeline&lt;/code&gt; to show how everything writes out.)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;foreach (var entity in list)
{
    _logger.LogInformation(
        &amp;quot;Entity: Path {Path}, Components {ComponentList}&amp;quot;,
        entity.Get&amp;lt;UPath&amp;gt;(),
        entity.GetComponentTypes().Select(x =&amp;gt; x.FullName));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is just some debugging to show how things are working and how Gallium works. In this case, the read files sets two components: the &lt;code&gt;UPath&lt;/code&gt; for the path of the file and a &lt;code&gt;IBinaryContent&lt;/code&gt; which is an abstraction to the binary content. Since we haven't done anything to the file, this just points to the file system itself without loading it into memory. That way, we can easily deal with gigabyte-sized files without overloading the system.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;return list.ToAsyncEnumerable();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, we return the list of files that we just read in. With this change, running the build gets us this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[14:20:03 DBG] &amp;lt;DirectoryService&amp;gt; Setting root directory and filesystem to /home/dmoonfire/src/typewriter-press/typewriter-press-website
[14:20:03 DBG] &amp;lt;BuildCommand&amp;gt; Processing 0 pipeline options
[14:20:03 INF] &amp;lt;BuildCommand&amp;gt; Running pipelines
[14:20:04 INF] &amp;lt;PagesPipeline&amp;gt; Read in 1 files from /src/pages
[14:20:04 INF] &amp;lt;PagesPipeline&amp;gt; Entity: Path /src/pages/typewriter/index.md, Components [&amp;quot;MfGames.Nitride.Contents.IBinaryContent&amp;quot;,&amp;quot;Zio.UPath&amp;quot;]
[14:20:04 INF] &amp;lt;SimplifiedMarkdownPipeline&amp;gt; Reading 1 entities
[14:20:04 INF] &amp;lt;StyleHtmlPipeline&amp;gt; Reading 1 entities
[14:20:04 INF] &amp;lt;OutputHtmlPipeline&amp;gt; Writing out 1 files
[14:20:04 INF] &amp;lt;OutputHtmlPipeline&amp;gt; Entity: Path /src/pages/typewriter/index.md, Components [&amp;quot;MfGames.Nitride.Contents.IBinaryContent&amp;quot;,&amp;quot;Zio.UPath&amp;quot;]
[14:20:04 INF] &amp;lt;PipelineManager&amp;gt; Completed in 00:00:00.9543639
[14:20:04 INF] &amp;lt;BuildCommand&amp;gt; Command took 00:00:00.9559424
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, that single file read in is then passed through the other pipelines because each of them return their input list and then the final pipeline gets those same entities and paths.&lt;/p&gt;
&lt;h3&gt;Changing Paths&lt;/h3&gt;
&lt;p&gt;Now the problem with the above example is that we are writing our output to the same location as we are reading. That isn't optional. What we want to do is strip off the &lt;code&gt;/src/pages/typewriter&lt;/code&gt; from the beginning of the read in path and add &lt;code&gt;/build/typewriter/html&lt;/code&gt; in front of it. This is easily done with two additional operators, &lt;code&gt;RemovePathPrefix&lt;/code&gt; and &lt;code&gt;AddPathPrefix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To start with, let's remove the prefix from the read methods to normalize the paths.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Inputs/PagesPipeline.cs
public PagesPipeline(
    ILogger&amp;lt;PagesPipeline&amp;gt; logger,
    ReadFiles readFiles,
    RemovePathPrefix removePathPrefix)
{
    _logger = logger;
    _removePathPrefix = removePathPrefix.WithPathPrefix(&amp;quot;/src/pages/typewriter&amp;quot;);
    _readFiles = readFiles.WithPattern(&amp;quot;/src/pages/**/*.md&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then we chain the operation using an extension method on operations:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Inputs/PagesPipeline.cs
public override IAsyncEnumerable&amp;lt;Entity&amp;gt; RunAsync(
    IEnumerable&amp;lt;Entity&amp;gt; entities,
    CancellationToken cancellationToken = default)
{
    var list = _readFiles
        .Run(cancellationToken)
        .Run(_removePathPrefix)
        .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since each operation also takes an &lt;code&gt;IEnumerable&amp;lt;Entity&amp;gt;&lt;/code&gt; and removes the same type, they can flow from one operation to another. When we run the build, the output line looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[14:23:35 INF] &amp;lt;PagesPipeline&amp;gt; Read in 1 files from /src/pages
[14:23:35 INF] &amp;lt;PagesPipeline&amp;gt; Entity: Path /index.md, Components [&amp;quot;MfGames.Nitride.Contents.IBinaryContent&amp;quot;,&amp;quot;Zio.UPath&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding the output prefix is done on the output step:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Html/OutputHtmlPipeline.cs
public OutputHtmlPipeline(
    ILogger&amp;lt;OutputHtmlPipeline&amp;gt; logger,
    AddPathPrefix addPathPrefix,
    StyleHtmlPipeline styleHtmlPipeline)
{
    _logger = logger;
    _addPathPrefix = addPathPrefix.WithPathPrefix(&amp;quot;/build/typewriter/html&amp;quot;);

    AddDependency(styleHtmlPipeline);
}

public override IAsyncEnumerable&amp;lt;Entity&amp;gt; RunAsync(
    IEnumerable&amp;lt;Entity&amp;gt; entities,
    CancellationToken cancellationToken = default)
{
    var list = entities
        .Run(_addPathPrefix)
        .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we already have a list of entities, we can just use the extension method to pipe it through the operation and produce the following output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[14:28:35 INF] &amp;lt;PagesPipeline&amp;gt; Read in 1 files from /src/pages
[14:28:35 INF] &amp;lt;PagesPipeline&amp;gt; Entity: Path /index.md, Components [&amp;quot;MfGames.Nitride.Contents.IBinaryContent&amp;quot;,&amp;quot;Zio.UPath&amp;quot;]
[14:28:35 INF] &amp;lt;SimplifiedMarkdownPipeline&amp;gt; Reading 1 entities
[14:28:35 INF] &amp;lt;StyleHtmlPipeline&amp;gt; Reading 1 entities
[14:28:35 INF] &amp;lt;OutputHtmlPipeline&amp;gt; Writing out 1 files
[14:28:35 INF] &amp;lt;OutputHtmlPipeline&amp;gt; Entity: Path /build/typewriter/html/index.md, Components [&amp;quot;MfGames.Nitride.Contents.IBinaryContent&amp;quot;,&amp;quot;Zio.UPath&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason &lt;code&gt;PagesPipeline&lt;/code&gt; doesn't use &lt;code&gt;entities.Run(...)&lt;/code&gt; is because that is the first pipeline and the &lt;code&gt;entities&lt;/code&gt; is going to be an empty list. Instead, we need to make entities, which is why we call &lt;code&gt;_readFiles.Run()&lt;/code&gt; instead.&lt;/p&gt;
&lt;h3&gt;Writing Files&lt;/h3&gt;
&lt;p&gt;And the last bit for today's post is to write out files. This, creatively enough, uses the &lt;code&gt;WriteFiles&lt;/code&gt; operation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Html/OutputHtmlPipeline.cs
public OutputHtmlPipeline(
    ILogger&amp;lt;OutputHtmlPipeline&amp;gt; logger,
    AddPathPrefix addPathPrefix,
    WriteFiles writeFiles,
    StyleHtmlPipeline styleHtmlPipeline)
{
    _logger = logger;
    _writeFiles = writeFiles;
    _addPathPrefix = addPathPrefix.WithPathPrefix(&amp;quot;/build/typewriter/html&amp;quot;);

    AddDependency(styleHtmlPipeline);
}

public override IAsyncEnumerable&amp;lt;Entity&amp;gt; RunAsync(
    IEnumerable&amp;lt;Entity&amp;gt; entities,
    CancellationToken cancellationToken = default)
{
    var list = entities
        .Run(_addPathPrefix)
        .Run(_writeFiles)
        .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, we've already done all the fancy operations to adjust the path so it writes out the files in the proper location:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ find build -type f
build/typewriter/html/index.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Convenience Methods&lt;/h3&gt;
&lt;p&gt;Now, adding and removing path prefixes is pretty common, so both the &lt;code&gt;ReadFiles&lt;/code&gt; and &lt;code&gt;WriteFiles&lt;/code&gt; have methods to do those in a single call.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Inputs/PagesPipeline.cs
public PagesPipeline(
    ILogger&amp;lt;PagesPipeline&amp;gt; logger,
    ReadFiles readFiles)
{
    _logger = logger;
    _readFiles = readFiles
        .WithPattern(&amp;quot;/src/pages/typewriter/**/*.md&amp;quot;)
        .WithRemovePathPrefix(&amp;quot;/src/pages/typewriter&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// In //src/dotnet/Html/OutputHtmlPipeline.cs
public OutputHtmlPipeline(
    ILogger&amp;lt;OutputHtmlPipeline&amp;gt; logger,
    WriteFiles writeFiles,
    StyleHtmlPipeline styleHtmlPipeline)
{
    _logger = logger;
    _writeFiles = writeFiles.WithAddPathPrefix(&amp;quot;/build/typewriter/html&amp;quot;);

    AddDependency(styleHtmlPipeline);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What's Next&lt;/h2&gt;
&lt;p&gt;And there you go, an over-engineered system for copying a single file from one directory to another. In the next post of this series, we'll turn that Markdown file into an HTML page.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/07/using-nitride-introduction/"&gt;2025-06-07 Using Nitride - Introduction&lt;/a&gt; - I'm going to start a long, erratic journey to update my publisher website. Along the way, I'm going to document how to create a MfGames.Nitride website from the ground up, along with a lot of little asides about reasoning or purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/08/using-nitride-pipelines/"&gt;2025-06-08 Using Nitride - Pipelines&lt;/a&gt; - The first major concept of Nitride is the idea of a &amp;quot;pipeline&amp;quot; which are the largest units of working in the system. This shows a relatively complex setup of pipelines that will be used to generate the website.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/09/using-nitride-entities/"&gt;2025-06-09 Using Nitride - Entities&lt;/a&gt; - The continued adventures of creating the Typewriter website using MfGames.Nitride, a C# static site generator. The topic of today is the Entity class and how Nitride uses an ECS (Entity-Component-System) to generate pages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/10/using-nitride-markdown/"&gt;2025-06-10 Using Nitride - Markdown&lt;/a&gt; - Examples and explanations of converting Markdown to HTML using MfGames.Nitride and MarkDig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="/blog/2025/06/12/using-nitride-front-matter/"&gt;2025-06-12 Using Nitride - Front Matter&lt;/a&gt; - How to use Nitride to take the front matter from pages and add them as a component into the Entity class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Reflections on Nitride</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2024/04/21/nitride/" />
    <updated>2024-04-21T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2024/04/21/nitride/</id>
    <category term="development" scheme="https://d.moonfire.us/categories/" label="Development" />
    <category term="mfgames-nitride" scheme="https://d.moonfire.us/tags/" label="MfGames.Nitride" />
    <category term="mfgames-gallium" scheme="https://d.moonfire.us/tags/" label="MfGames.Gallium" />
    <category term="gatsbyjs" scheme="https://d.moonfire.us/tags/" label="GatsbyJS" />
    <category term="statiq" scheme="https://d.moonfire.us/tags/" label="Statiq" />
    <category term="cobblestonejs" scheme="https://d.moonfire.us/tags/" label="CobblestoneJS" />
    <category term="bevy" scheme="https://d.moonfire.us/tags/" label="Bevy" />
    <summary type="html">I just got mfgames.com migrated over to Nitride, so a few thoughts about the previous three years.
</summary>
    <content type="html">&lt;p&gt;This week's goal was to get &lt;a href="https://mfgames.com"&gt;mfgames.com&lt;/a&gt; migrated over to &lt;a href="/tags/mfgames-nitride/"&gt;MfGames.Nitride&lt;/a&gt;. It was a lofty goal, mainly because I've been making small incremental changes over the last year but never got something pushed over the finish line.&lt;/p&gt;
&lt;p&gt;I succeeded.&lt;/p&gt;
&lt;h2&gt;Migration&lt;/h2&gt;
&lt;p&gt;This is my third &amp;ldquo;big&amp;rdquo; site for Nitride, one that follows the common pattern of pulling in multiple Git repositories from my &lt;a href="https://src.mfgames.com"&gt;forge&lt;/a&gt;, integrating the data, and then creating a website out of it. In other words, one of the main reasons I created Nitride.&lt;/p&gt;
&lt;p&gt;It isn't exactly a fancy site, but I find that I'm not really creating flashy sites anymore. Part of this is because I still enjoy &lt;a href="/tags/gemini/"&gt;Gemini&lt;/a&gt;, which is a text-only format, but also because the chrome is distracting to me. I want something simple but not the black on gray of Netscape Navigator.&lt;/p&gt;
&lt;p&gt;Overall, I'm pretty happy with the results. It creates landing pages for projects, pulls in the &lt;code&gt;./docs/&lt;/code&gt; folder for each project and formats them (for example, &lt;a href="//mfgames.com/mfgames-cil/docs/gallium/"&gt;MfGames.Gallium&lt;/a&gt;), and merges news posts from the projects (while decorating them) and creates a single unified feed.&lt;/p&gt;
&lt;p&gt;There are more things I want to do, such as links to issue tracking, licenses, and other stuff. I also ended up losing some features like the culture sandbox (which I needed to rewrite anyways), the slippery maps, and a few other minor things.&lt;/p&gt;
&lt;p&gt;Still happy with it.&lt;/p&gt;
&lt;h2&gt;News&lt;/h2&gt;
&lt;p&gt;I was working on debugging creating news posts in individual project repositories anews posts from individual projects into mfgames.com as my &amp;ldquo;little thing&amp;rdquo; for today and I came up with the exact period of time I decided to write my latest static site generator (SSG):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git log --pretty=&amp;quot;%ai %s&amp;quot;
...
2021-05-04 02:27:28 -0500 feat: implementing my own
2021-04-07 22:13:20 -0500 chore: updating Statiq libraries
...
2014-05-07 14:41:27 -0700 Initial commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It all started a year before, when I migrated from CobblestoneJS (my SSG from the early 2010s) to &lt;a href="https://www.gatsbyjs.com/"&gt;GatsbyJS&lt;/a&gt;. Which was pretty and gorgeous and so difficult to debug. It was also fragile for me because I don't update my libraries to follow the curve and every time I tried to add a new feature to the site, I spent a few days just trying to figure out the node packages to get everything building again first.&lt;/p&gt;
&lt;p&gt;When I got into Gemini for the first time, I also realized that GatsbyJS wasn't going to work there because that shiny site was not text friendly. It was also tied into my frustrating that going to a page the first time downloaded the content in one manner, but navigation was done via SPA and pulled it from another; and the cache was killing me there.&lt;/p&gt;
&lt;p&gt;(But it looked so pretty.)&lt;/p&gt;
&lt;p&gt;So I decided to look for a new SSG. I didn't want to write my own because&amp;hellip; I really don't like writing utility programs. But I wanted to do something Gatsby didn't do, which is generate a site that didn't require Javascript.&lt;/p&gt;
&lt;p&gt;I figured I was good at C# (this was before Rust, but I'm still best at C#), so I wanted to find a C# static site generator in hopes that the NuGet package management wasn't as excruciating as Node's.&lt;/p&gt;
&lt;p&gt;So I found &lt;a href="https://www.statiq.dev/"&gt;Statiq&lt;/a&gt; which seemed like a perfect match. But going from GatsbyJS to Statiq quickly hit a point where I realized I was, once again, forcing a program to do things it wasn't supposed to be doing. The patterns in Statiq were nice but they were too rigid for the complexity of my way of creating sites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The fragility of pulling in 80+ Git repositories into a CI pipeline&lt;/li&gt;
&lt;li&gt;The use of multiple dynamic lists on a single page&lt;/li&gt;
&lt;li&gt;The desire to make a Gemini-friendly site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After that month of thinking, I started to write my own. This came when I was looking at Statiq's document type realizing that my desire to create a new type basically meant I had to rebuild the entire framework to support a slightly different enumeration.&lt;/p&gt;
&lt;h2&gt;Gallium&lt;/h2&gt;
&lt;p&gt;Enter Entity Component Systems (ECS) which I learned about while pondering writing a game using &lt;a href="https://bevyengine.org/"&gt;Bevy&lt;/a&gt;. I had never heard of an ECS before that, but the idea was great. Why not write a static site generator that didn't need a single enum to drive processing, where I could add arbitrary components to pages such as nested lists or metadata, and then easily be able to use them.&lt;/p&gt;
&lt;p&gt;Of course, then I had to look at existing ECS systems in C#, but I eventually decided to be more idiomatic to C# (another frustration of Statiq) and focus on something that let me use LINQ-like method calls (such as &lt;code&gt;.Select()&lt;/code&gt; and &lt;code&gt;.Where()&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;List&amp;lt;Entity&amp;gt; newsList = fileList
    .WhereEntity&amp;lt;UPath&amp;gt;(this.OnlyNewsFiles)
    .Run(this.setInstantFromPath, cancellationToken)
    .SelectEntity&amp;lt;UPath, Instant&amp;gt;(this.pathService.SetPathFromInstant)
    .Select(entity =&amp;gt; entity.Set(IsPost.Instance))
    .Select(entity =&amp;gt; entity.Set(IncludeInFeed.Instance))
    .Where(entity =&amp;gt; entity.Has&amp;lt;IsMarkdown&amp;gt;())
    .OrderBy(entity =&amp;gt; entity.Get&amp;lt;PageModel&amp;gt;().Title)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm specifically not saying &lt;a href="/tags/gallium/"&gt;Gallium&lt;/a&gt; is a &amp;ldquo;high performance&amp;rdquo; because it isn't. It is an ECS for relatively small systems that builds on existing libraries for patterns. I don't need high performance for sites with only a few thousand pages, I want something easy to understand.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;There are quite a few steps going forward. I still want to make Nitride easier to use, Gallium to track LINQ patterns, and mfgames.com to present more data. But this is a big step and I'm really happy that I'm proud of (I'm sure there will be tweaks). More importantly, I have something that hasn't given me any major trouble updating since I started three years ago.&lt;/p&gt;
</content>
  </entry>
</feed>
