﻿<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text" xml:lang="en">Gallium</title>
  <link type="application/atom+xml" href="https://d.moonfire.us/tags/gallium/atom.xml" rel="self" />
  <link type="text/html" href="https://d.moonfire.us/tags/gallium/" rel="alternate" />
  <updated>2026-03-09T17:42:47Z</updated>
  <id>https://d.moonfire.us/tags/gallium/</id>
  <author>
    <name>D. Moonfire</name>
  </author>
  <rights>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</rights>
  <entry>
    <title>Nitride v0.3.4</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2022/06/06/nitride-0.3.4/" />
    <updated>2022-06-06T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2022/06/06/nitride-0.3.4/</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="cobblestonejs" scheme="https://d.moonfire.us/tags/" label="CobblestoneJS" />
    <category term="mfgames-writing" scheme="https://d.moonfire.us/tags/" label="MfGames Writing" />
    <category term="gallium" scheme="https://d.moonfire.us/tags/" label="Gallium" />
    <summary type="html">A minor milestone for Nitride, my static site generator, and a sweeping refactoring to make things consistent and easier to learn.
</summary>
    <content type="html">&lt;p&gt;Last week, I decided to work on &lt;a href="/tags/nitride/"&gt;Nitride&lt;/a&gt;, my static site generator written in C#. One of the outstanding tasks after I wrote it for &lt;a href="//d.moonfire.us/"&gt;this&lt;/a&gt; site to handle both HTML and Gemtext versions was to look at the various patterns that I had established and then simplify them.&lt;/p&gt;
&lt;p&gt;Cleaning up code is good when starting with something that potentially can get large. I had used &lt;a href="/tags/cobblestone/"&gt;Cobblestone&lt;/a&gt; for almost a decade between all my websites and I fully expected Nitride to last the name. That means I need to be comfortable with the API. Of course, &amp;ldquo;comfortable with&amp;rdquo; usually means &amp;ldquo;I like it until I stop using it for three months and then I want to rewrite it all.&amp;rdquo; However, I've managed to avoid that with Cobblestone and I'm &amp;ldquo;mostly&amp;rdquo; resisting that with &lt;span class="missing-link" data-path="/tags/mfgames-writing"&gt;MfGames Writing&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note: I'm planning on switching MfGames Writing to configuration-as-code instead of declarative files in the future, just not sure when.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The other plan I had was to write unit tests and examples for every option to basically help document the place. This was going to be injected into &lt;a href="https://mfgames.com/"&gt;https://mfgames.com/&lt;/a&gt; as a project, but I realized that I was trying to do too much and I needed to break it down.&lt;/p&gt;
&lt;p&gt;This week's effort was just pattern normalization. I've embraced using dependency injection for setting up the various pipelines, so there is no more mix cases of constructor injection and &lt;code&gt;new&lt;/code&gt; operations; it's all constructor injection. That way, I can offload the validation logic to &lt;code&gt;FluentValidators&lt;/code&gt; and change the signatures, such as to add logging, without breaking existing code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;public PagesMarkdownPipeline(
    ReadFiles readFiles,
    MoveToIndexPath moveToIndexPath,
    ParseYamlHeader&amp;lt;PageModel&amp;gt; parseYamlHeader,
    RemovePathPrefix removePathPrefix,
    IdentifyMarkdownFromPath identifyMarkdownFromPath)
{
    this.readFiles = readFiles.WithPattern(&amp;quot;/pages/**/*&amp;quot;);
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can tell from the example above, I'm also using the fluent pattern a lot more to handle configuration. I think this works since the initial object has mostly sane defaults and validations for the rest of them while the rest can be customized in the constructor.&lt;/p&gt;
&lt;p&gt;I also decided to keep the configuration in the constructor of the pipeline. That makes the actual operation of the pipeline much easier to understand.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;/// &amp;lt;inheritdoc /&amp;gt;
public override Task&amp;lt;IEnumerable&amp;lt;Entity&amp;gt;&amp;gt; RunAsync(
    IEnumerable&amp;lt;Entity&amp;gt; entities)
{
    IEnumerable&amp;lt;Entity&amp;gt; files = this.readFiles.Run()
        .Run(this.removePathPrefix)
        .Run(this.moveToIndexPath)
        .Run(this.identifyMarkdownFromPath)
        .Run(this.parseYamlHeader)
        .ForEachEntity&amp;lt;PageModel&amp;gt;(this.SetInstantFromPageModel);

    return Task.FromResult(files);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can tell, it also pulls from my history with &lt;code&gt;gulp&lt;/code&gt; a lot since I think the chained &lt;code&gt;.Run()&lt;/code&gt; methods works. The &lt;code&gt;ForEachEntity&lt;/code&gt; is from &lt;a href="/tags/gallium/"&gt;Gallium&lt;/a&gt;, my simply entity component system that I wrote for this (but also want to use in other places).&lt;/p&gt;
&lt;p&gt;The other thing in the above example is that the pipelines (but not necessarily the individual operations) are async. I figured this gives a good mix of allowing async operations at the pipeline level but in most cases, it ends up being fairly synchronous.&lt;/p&gt;
&lt;p&gt;Thanks to the C#'s enumerations, the actual value sometimes isn't run until after the pipeline &amp;ldquo;runs&amp;rdquo;. There are exceptions, like when I need to create a category pages which requires knowing every other page in the website. Those are resolved immediately in the pipeline to process that, and then returned.&lt;/p&gt;
&lt;p&gt;Overall, it is moving forward nicely. I still see a lot of shuffling of method names and I want to break up the index creation pages (the annual, monthly, and daily blog listings) into separate components. But, once I get those, I should be ready to start converting the next site, &lt;a href="https://moonfire.us/"&gt;https://moonfire.us/&lt;/a&gt; and then into the next &amp;ldquo;big&amp;rdquo; one, &lt;a href="https://mfgames.com/"&gt;https://mfgames.com/&lt;/a&gt;. The &amp;ldquo;end goal,&amp;rdquo; as it were, is converting &lt;a href="https://fedran.com/"&gt;https://fedran.com/&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gallium Nitride and Gemini</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2021/07/10/gallium-nitride-and-gemini/" />
    <updated>2021-07-10T05:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2021/07/10/gallium-nitride-and-gemini/</id>
    <category term="programming" scheme="https://d.moonfire.us/categories/" label="Programming" />
    <category term="statiq" scheme="https://d.moonfire.us/tags/" label="Statiq" />
    <category term="gallium" scheme="https://d.moonfire.us/tags/" label="Gallium" />
    <category term="mfgames-nitride" scheme="https://d.moonfire.us/tags/" label="MfGames.Nitride" />
    <category term="gitlab" scheme="https://d.moonfire.us/tags/" label="Gitlab" />
    <category term="gemini" scheme="https://d.moonfire.us/tags/" label="Gemini" />
    <summary type="html">Last November, I switched my static site generator from CobblestoneJS (my homebrew Gulp-based one) to Statiq. There were a number of reasons, all of them still good but mainly to support what I want to do with fedran.com and my other sites. This weekend, I changed it to a new static site generator that I have written and hosted the site also on Gemini.
</summary>
    <content type="html">&lt;p&gt;Last &lt;a href="/blog/2020/11/20/website-update/"&gt;November&lt;/a&gt;, I switched my static site generator from CobblestoneJS (my homebrew Gulp-based one) to &lt;a href="https://statiq.dev/"&gt;Statiq&lt;/a&gt;. There were a number of reasons, all of them still good but mainly to support what I want to do with &lt;a href="https://fedran.com/"&gt;fedran.com&lt;/a&gt; and my other sites.&lt;/p&gt;
&lt;p&gt;This holiday weekend, I ended up ripping out all of the Statiq and wrote another static site generator, this time in C# and using an &lt;a href="https://en.m.wikipedia.org/wiki/Entity_component_system"&gt;Entity-Component-System&lt;/a&gt; (ECS). I also migrated my site over to &lt;a href="gemini://d.moonfire.us/"&gt;Gemini&lt;/a&gt; as part of the effort.&lt;/p&gt;
&lt;h1&gt;Why?&lt;/h1&gt;
&lt;p&gt;I liked many of the ideas that Statiq provided:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Treating the website generation as code.&lt;/li&gt;
&lt;li&gt;Having pipelines with dependencies for generation.&lt;/li&gt;
&lt;li&gt;Immutable objects on the pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, I found myself struggling with concepts. It just didn't sit well with me and I would spend two weeks trying to implement a feature and getting stuck. When I realized I had spent a month not fixing something that was bothering me because I didn't want to delve into the code, I knew it was time to change.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;That isn't to say Statiq isn't bad. It just isn't for me. That's it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;About once a year, I get 4-7 days of &amp;ldquo;alone time&amp;rdquo; to do what I want. This year, I decided to work on a new static site generator that did work the way I work today and that I hoped would carry me over for the next five years or so.&lt;/p&gt;
&lt;h1&gt;Entity-Component-Systems (ECS)&lt;/h1&gt;
&lt;p&gt;One thing that Statiq did (but differently) was implemented the system as an ECS. Basically, you have a lightweight object (the &amp;ldquo;entity&amp;rdquo;) and add various components into it. Those components are what provide the features: the text content, flags to say if it is HTML or Markdown, or the path.&lt;/p&gt;
&lt;p&gt;While Statiq had a number of these elements built-into the &lt;code&gt;Document&lt;/code&gt; call (basically their entity), there were a lot of assumptions that didn't always fit. Likewise, &lt;code&gt;Statiq.Web&lt;/code&gt; had some nice opinionated ways of handling it, including a document type (binary, text, etc), but I couldn't find an easy way to extend it.&lt;/p&gt;
&lt;h1&gt;Gallium&lt;/h1&gt;
&lt;p&gt;With my ECS, &lt;code&gt;Entity&lt;/code&gt; is only a collection plus an integer identifier. Components can be added, removed, and replaced easily using generics to determine the type. Methods are chained together but not pseudo-English fluent (which I'm also not fond of). Entities are immutable, so all the operations return a clone of that entity with the new components added, removed, or otherwise changed. (Thanks to functional programming for some of those ideas.)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;Entity entity = new();
Entity entityWithTwoComponents = entity
  .Set(new Uri(&amp;quot;https://d.moonfire.us&amp;quot;))
  .Set&amp;lt;FileSystemInfo&amp;gt;(new FileInfo(&amp;quot;/bob.txt&amp;quot;));
Entity entityWithReplacedUri = entityWithTwoComponents
  .Set(new Uri(&amp;quot;http://path-to-replace-d.moonfire.us/&amp;quot;));
Entity entityWithOnlyUri = entityWithReplacedUri
  .Remove&amp;lt;FileSystemInfo&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also really like chained operations, so most of the processing looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;IEnumerable&amp;lt;Entity&amp;gt; input;
var onlyHtmlOutputs = input
  .OrderBy(x =&amp;gt; x.Get&amp;lt;FileInfo&amp;gt;().FullPath)
  .ForComponents&amp;lt;Uri&amp;gt;((entity, uri) =&amp;gt; this.Uri(entity, uri))
  .WhereComponents&amp;lt;IsHtml&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole idea is &lt;code&gt;ForComponents&amp;lt;T1, T2, T3&amp;gt;&lt;/code&gt; will go through the list and for all entities that have &lt;code&gt;T1&lt;/code&gt;, &lt;code&gt;T2&lt;/code&gt;, and &lt;code&gt;T3&lt;/code&gt;, it will do the callback, otherwise it will passs it on. Likewise &lt;code&gt;WhereComponents&amp;lt;T1&amp;gt;&lt;/code&gt; is basically a &lt;code&gt;Where&lt;/code&gt; that says only the entities with the given components.&lt;/p&gt;
&lt;p&gt;Those ideas really simplified a lot of the difficulties I had with CobblestoneJS. Overall, most of the logic &amp;ldquo;felt&amp;rdquo; right for me, so I'm really happy with the results. Plus, it is based on a far more stable package ecosystem (NuGet) and in a language I enjoy greatly (C#).&lt;/p&gt;
&lt;p&gt;Also, the language uses &lt;a href="https://autofac.org/"&gt;Autofac&lt;/a&gt; as my preferred dependency injection of choice. I really like the library plus &lt;a href="https://nodatime.org/"&gt;NodaTime&lt;/a&gt; when coding, so I went with these. It's a bit opinionated, but&amp;hellip; only a few people ever used Cobblestone, so I'm going to assume very few are going to use this.&lt;/p&gt;
&lt;p&gt;Once I get it cleaned up, I'll probably call the ECS &amp;ldquo;Gallium&amp;rdquo; because my original name was &amp;ldquo;Gallium Nitride&amp;rdquo; (GaN, because it's a cool name and I like what the molecule does). The static site generator would be named Nitride.&lt;/p&gt;
&lt;h1&gt;Nitride&lt;/h1&gt;
&lt;p&gt;Nitride is just a multi-threaded pipeline static generator. It uses pipelines much like Statiq but based on C#'s thread control (&lt;code&gt;ManualReset&lt;/code&gt;, &lt;code&gt;ReaderWriterLockSlim&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The rough code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;List&amp;lt;Entity&amp;gt; list = entities
    .Run(new IdentifyMarkdown())
    .Run(new ParseYamlHeader&amp;lt;PageModel&amp;gt;())
    .ForComponents&amp;lt;PageModel&amp;gt;(this.SetHandlebarsFromPageModel)
    .Run(this.setInstantFromComponent)
    .Run(this.filterOutFutureInstants)
    .Run(this.createCategoryIndexes)
    .Run(this.createTagIndexes)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, DI or direct instantiate of modules, it all works the same and really ties into using Linq and C# generics. All of the pipelines are &lt;code&gt;async&lt;/code&gt; but most of the operations (&lt;code&gt;createTagIndexes&lt;/code&gt;, &lt;code&gt;ParseYamlHeader&lt;/code&gt;) are not. But since the pipelines are, it is easy to make something &lt;code&gt;await&lt;/code&gt; without changing signatures.&lt;/p&gt;
&lt;p&gt;I really like the pipelines. For my site, I have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PostsPipeline&lt;/code&gt;: Load posts and create blog archives&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PagesPipeline&lt;/code&gt;: Load static pages.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ContentPipeline&lt;/code&gt;: Combines &lt;code&gt;PostsPipeline&lt;/code&gt; and &lt;code&gt;PagesPipeline&lt;/code&gt;, creates categories/tag indexes, handle filtering out future pages.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BareHtmlPipeline&lt;/code&gt;: Takes &lt;code&gt;ContentPipeline&lt;/code&gt; and converts into bare, unstyled HTML. Also creates the RSS and Atom feeds from the bare HTML.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebpackPipeline&lt;/code&gt;: Runs Webpack.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StyledHtmlPipeline&lt;/code&gt;: Takes &lt;code&gt;BareHtmlPipeline&lt;/code&gt; and &lt;code&gt;WebpackPipeline&lt;/code&gt; and makes it styled using handlebars.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GeminiPipeline&lt;/code&gt;: Takes &lt;code&gt;ContentPipeline&lt;/code&gt; and turns the Markdown into Gemini, applies some simple styling, and generates Gemtext pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it, but I'm happy with the result because I've taken lessons learned from my previous attempts to created something that will handle Fedran's massive cross-linking and project pages, MfGames's pulling in of separate Git repositories, and also some of the more complex formatting of my new sci-fi fiction website.&lt;/p&gt;
&lt;h1&gt;Gemini&lt;/h1&gt;
&lt;p&gt;I like the idea of &lt;a href="//gemini.circumlunar.space/"&gt;Gemini&lt;/a&gt;. It is a low-overhead protocol that has almost no extra features, no cookies, and basically focused on presenting content. In my case, I really want to see all of my sites on Gemini because I think it has some significant merits, more so as I want to get away from heavily styled content written by people who like tiny fonts or don't have my color contrast issues.&lt;/p&gt;
&lt;p&gt;To do that, I ended up taking inspiration from &lt;a href="https://pypi.org/project/md2gemini/"&gt;md2gemini&lt;/a&gt; and wrote a C# library that converts Markdown into Gemtext (Gemini's markup format inspired by Markdown). The end result is &lt;a href="gemini://d.moonfire.us/"&gt;pretty nice&lt;/a&gt;, I think and I'm really happy with the results.&lt;/p&gt;
&lt;p&gt;Of course, it meant I had to get a virtual machine to host a Gemini server next to a HTTP one, but that was going to happen sooner or later anyways.&lt;/p&gt;
&lt;h1&gt;Next Steps&lt;/h1&gt;
&lt;p&gt;I write a lot libraries that I think are interesting but very few people worry about. They rise up, either I stick with them or I trail off, but they always scratch my itches. On that front, the following things are left to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Split out Gallium more formally into &lt;code&gt;MfGames.Gallium&lt;/code&gt; or &lt;code&gt;Gallium&lt;/code&gt;, not sure.&lt;/li&gt;
&lt;li&gt;Clean up the API for &lt;code&gt;Nitride&lt;/code&gt; and make sure everything is consistent. Some of the names are a bit&amp;hellip; off, but the functionality is good.&lt;/li&gt;
&lt;li&gt;Break all the modules into separate Gitlab projects and set up CI for releasing.&lt;/li&gt;
&lt;li&gt;Document everything.&lt;/li&gt;
&lt;li&gt;Convert five other websites sites to use it to figure out what is missing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven't found the money to get a developer's signing certificate, so I'll probably just put everything up on my public &lt;a href="https://www.myget.org/"&gt;MyGet repository&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
</feed>
