Mike Slinn

Nugem: Custom Rails & Jekyll Plugins

Published 2022-03-28. Last modified 2024-10-27.
Time to read: 9 minutes.

This page is part of the jekyll collection.
Early Access
The material on this web page and the GitHub project it links to are still in the development and heavy editing stages. Do not rely on anything working yet. If you are interested in watching this project evolve, please revisit this page at a later time.

This article builds upon Explanations and Examples of Jekyll Plugins and discusses a code generator that you can use to start writing your next Jekyll plugin or Ruby on Rails plugin.

Nugem

Igor Jancev originally wrote Creategem to generate Ruby on Rails gems. The project uses thor for code generation, which I wrote about in Ruby Gem Scaffold Generation With Thor. After the project had no updates for 7 years, I forked and updated it, then added the capability to generate Jekyll gems. Igor did not respond when I asked if he was interested in including my work in his gem, so I republished the new version under the name nugem.

Rails support includes Rails engine plugins, including mountable engines. Jekyll plugin support includes filters, generators, tags and block tags.

All generated plugins:

  • Are structured and implemented as Ruby gems. It is surprisingly easy to publish Ruby gems.
  • Gems are set up for publishing to RubyGems.org or to a private geminabox repository.
  • Have Rubocop and Visual Studio Code support baked in.

Generated Jekyll plugins also:

  • Set up their own custom loggers, as described in Custom Logging in Jekyll Plugins. Separate loggers are predefined for the tag plugins, as well as five loggers for the five major categories of Jekyll hooks: clean, documents, pages, posts, and site.

Generated Jekyll tags and block tags also:

  • Use jekyll_plugin_support to accept parameters, using standardized and convenient parameter parsing.
  • Provide Jekyll site, page and mode variables to scopes that need those variables. The generated code is structured to help you avoid wasting time on undocumented or under-documented details.

Installing Nugem

You could type along with the remainder of this article if you install nugem:

Shell
$ gem install nugem
Successfully installed nugem-0.8.0
Parsing documentation for nugem-0.8.0
Done installing documentation for nugem after 0 seconds
1 gem installed 

If you are using rbenv to manage Ruby instances, type:

Shell
$ rbenv rehash

Syntax

Here is the top-level help message for nugem:

Shell
$ nugem help
  nugem help [COMMAND]  # Describe available commands or one specific command
  nugem jekyll NAME     # Creates a new Jekyll plugin scaffold.
  nugem plain NAME      # Creates a new plain gem scaffold.
  nugem rails NAME      # Creates a new Rails scaffold.
  -o, [--out-dir=OUT_DIR]                                     # Output directory for the gem.
                                                              # Default: generated
  -e, [--executable], [--no-executable], [--skip-executable]  # Include an executable for the gem.
                                                              # Default: false
  -h, [--host=HOST]                                           # Repository host.
                                                              # Default: github
                                                              # Possible values: bitbucket, github
      [--private], [--no-private], [--skip-private]           # Publish the gem on a private repository.
                                                              # Default: false
  -y, [--yes], [--no-yes], [--skip-yes]                       # Answer yes to all questions.
                                                              # Default: false
  -q, [--quiet], [--no-quiet], [--skip-quiet]  # Suppress detailed messages.
                                               # Default: true
  -t, [--todos], [--no-todos], [--skip-todos]  # Generate TODO: messages in generated code.
                                               # Default: true 

Here is the help message for creating plain gems using nugem:

Shell
$ nugem help plain
    [--host=HOST]                      # Repository host.
  # Default: github
  # Possible values: bitbucket, github
[--private], [--no-private]        # Publish the gem in a private repository.
[--executable], [--no-executable]  # Include an executable for the gem.
[--host=HOST]                      # Repository host.
  # Default: github
  # Possible values: bitbucket, github
[--private], [--no-private]        # Publish the gem on a private repository.
[--quiet], [--no-quiet]  # Suppress detailed messages.
# Default: true
[--todos], [--no-todos]  # Generate TODO: messages in generated code.
# Default: true
Creates a new plain gem scaffold with the given NAME, by default hosted by GitHub and published on RubyGems.

Following is the help message for creating Ruby on Rails plugin gems using nugem. This article will not mention Rails again.

Shell
$ nugem help rails
  [--engine], [--no-engine]          # Create a gem containing a Rails engine.
  [--test-framework=TEST_FRAMEWORK]  # Use rspec or minitest for the test framework (default is minitest).
                                     # Default: minitest
                                     # Possible values: minitest, rspec
  [--executable], [--no-executable]  # Include an executable for the gem.
  [--host=HOST]                      # Repository host.
                                     # Default: github
                                     # Possible values: bitbucket, github
  [--mountable], [--no-mountable]    # Create a gem containing a mountable Rails engine.
  [--private], [--no-private]        # Publish the gem on a private repository.
  [--quiet], [--no-quiet]  # Suppress detailed messages.
                           # Default: true
  [--todos], [--no-todos]  # Generate TODO: messages in generated code.
                           # Default: true
  Creates a new Rails scaffold with the given NAME, by default hosted by GitHub and published on RubyGems. 

Here is the help message for creating Jekyll plugin gems using nugem:

Shell
$ nugem help jekyll
  [--block=BLOCK]                    # Specifies the name of a Jekyll block tag.
  [--blockn=BLOCKN]                  # Specifies the name of a Jekyll no-arg block tag.
  [--filter=FILTER]                  # Specifies the name of a Jekyll/Liquid filter module.
  [--generator=GENERATOR]            # Specifies a Jekyll generator.
  [--hooks=HOOKS]                    # Specifies Jekyll hooks.
  [--tag=TAG]                        # Specifies the name of a Jekyll tag.
  [--tagn=TAGN]                      # Specifies the name of a Jekyll no-arg tag.
  [--test-framework=TEST_FRAMEWORK]  # Use rspec or minitest for the test framework (default is rspec).
                                     # Default: rspec
                                     # Possible values: minitest, rspec
  [--executable], [--no-executable]  # Include an executable for the gem.
  [--host=HOST]                      # Repository host.
                                     # Default: github
                                     # Possible values: bitbucket, github
  [--private], [--no-private]        # Publish the gem on a private repository.
  [--quiet], [--no-quiet]  # Suppress detailed messages.
                           # Default: true
  [--todos], [--no-todos]  # Generate TODO: messages in generated code.
                           # Default: true
  Creates a new Jekyll plugin scaffold with the given NAME, by default hosted by GitHub and published on RubyGems. 

Generated Jekyll Plugin Code

The generated code that I added to nugem was adapted from github.com/mslinn/jekyll_plugin_template. That project also has additional Jekyll plugin code examples, some of which are shown below.

Output

Jekyll plugins are easier to write when using a template, and they are easier to manage when distributed as Ruby gems. Nugem does a good job of creating working scaffolds of Jekyll plugins, customized for your needs.

Following is a quick tour through the generated files that make up your new gem. Each type of Jekyll plugin will have a few different files; those will be described under the sections for each type of plugin, below.

.bundle/config
Configures bundler to look for Ruby development executables in the binstub directory.
README.md
This markdown file includes a badge that automatically displays the most recently published version of the gem. The version will be shown as question marks on GitHub until you publish the first version of your gem on RubyGems.org. Standard installation instructions are provided, and a placeholder is provided for you to write a description of the Jekyll tag plugin.
.git/
This is the local copy of your newly created git repository, and by default, the generated version is automatically committed.
.gitignore
This is set up for Jekyll gem development. Do not delete the .jekyll-cache/ entry, or you will introduce a huge security risk.
.rspec
Configures the rspec test facility.
.rubocop.yml
Configures the Rubocop Ruby linter.
.vscode/
Defines standard debug launch configurations, suggested VSCode extensions, extension settings, and code snippets for developing Jekyll gems.
CHANGELOG.md
You should update this file each time you release a new version of the gem.
Gemfile
This is a standard setup for developing Jekyll gems; do not define runtime dependencies in this file.
your_gem_name.gemspec
Define runtime dependencies in this file. Also update the description of your new gem here. Pay close attention to the contents of this file; it defines your gem.
LICENCE.txt
Standard MIT license terms. If you want to use another license, modify this file, and update the spec.license entry in .gemspec file to match.
Rakefile
Standard setup for Ruby gem development, you probably do not need to modify this file.
bin/
This directory contain useful scripts for Jekyll gem development: attach, console, rake, and setup. Take a look at them.
demo/
This directory contains a small but complete Jekyll website, set up to exercise your Jekyll plugin. See demo/README.md for more information.
lib/
Contains the logic for your Jekyll plugin:
  • lib/your_gem_name.rb – entry point, set up to require all the other Ruby source files in the lib/ directory. You probably do not need to modify it.
  • your_gem_name/version.rb – update the version number in this file each time you publish a new release.
  • your_plugin_name.rb – the logic for your Jekyll tag, block, filter, generator, or hook goes in this file. It is well commented.
    • nugem can create Jekyll plugins that define more than one tag, and/or block tag, and/or filter, and/or generator, and/or hooks. A Ruby source file will be created for each.
    • For Jekyll tags and block tags, the contents subclass the appropriate class from the jekyll_plugin_support gem.
spec/
This directory contains a scaffold for rspec tests for your Jekyll gem and includes some test fixtures. Jekyll filters are easy to test because they are just Ruby module methods. Setting up tests for other types of Jekyll plugins is a black art, however, because almost nothing is documented. Tags, block tags, generators and hooks can be extremely difficult to test due to the difficulty of setting up fixtures. The demo/ is provided, so you can debug your Jekyll plugins in situ.

Jekyll Filter Plugins

Filters are the easiest type of Jekyll plugin to write. All that is required is a module with methods in it, and to register the module with Liquid::Template.register_filter. All of the methods in the module become filters. No subclassing is required.

Filters should not accept page parameters because Jekyll will recursively re-evaluate all the properties of the page, and blow up when recursing through excerpt. This means the following syntax should not be used:

Avoid attempting to process the entire page class
{{ page | my_filter }}

The workaround is to write a Jekyll inline tag instead.

Example Jekyll Filter

The following code includes a method called my_filter_template. That method becomes a filter when wrapped as shown:

require 'jekyll_plugin_logger'

# @author Copyright 2020 {https://www.mslinn.com Michael Slinn}
# Template for Jekyll filters.
module JekyllFilterTemplate
  class << self
    attr_accessor :logger
  end
  self.logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)

  # This Jekyll filter returns the URL to search Google for the contents of the input string.
  # @param input_string [String].
  # @return [String] empty string if input_string has no contents except whitespace.
  # @example Use.
  #   {{ 'joy' | my_filter_template }} => <a href='https://www.google.com/search?q=joy' target='_blank' rel='nofollow'>joy</a>
  def my_filter_template(input_string)
    # @context[Liquid::Context] is available here to look up variables defined in front matter, templates, page, etc.

    JekyllFilterTemplate.logger.debug do
      'Defined filters are: ' + self.class # rubocop:disable Style/StringConcatenation
                                    .class_variable_get('@@global_strainer')
                                    .filter_methods.instance_variable_get('@hash')
                                    .map { |k, _v| k }
                                    .sort
    end

    input_string.strip!
    JekyllFilterTemplate.logger.debug "input_string=#{input_string}"
    if input_string.empty?
      ''
    else
      "<a href='https://www.google.com/search?q=#{input_string}' target='_blank' rel='nofollow'>#{input_string}</a>"
    end
  end

  PluginMetaLogger.instance.logger.info { "Loaded JekyllFilterTemplate v#{JekyllPluginTemplateVersion::VERSION} plugin." }
end

Liquid::Template.register_filter(JekyllFilterTemplate)

Input / Output Filter Transformation

Given this markup in an HTML file:

Shell
Search for {{ "joy" | my_filter_template }}

This is what is rendered to the web page after being passed through the above filter:

Shell
Search for joy
Liquid only registers instance methods as filters

Forwarding Singleton Methods to Instance Methods

If you want to define a method that can be called as a Jekyll filter and be used by other plugins as well, then you should read this section.

Singleton methods cannot be used as Liquid filters. When you use a module_function statement, module functions can be invoked from other modules; you are actually converting those instance methods into singleton methods.

If you mention a filter method in a module_function statement, an insidious bug will be introduced into your plugin, which can be difficult to understand at first. Adding a module_function statement into a module that defines Jekyll filters does the following:

  • The filters are never called
  • The unfiltered value is returned
  • No warnings or errors are issued.

Method forwarding is a way to wrap a singleton method within an instance method. The following example of method forwarding was taken from my jekyll_draft plugin. The Jekyll::Draft::draft? singleton method is invoked from the Liquid filter method Jekyll::DraftFilter::is_draft.

Shell
module Jekyll
  module Draft
    # Define this method outside of the filter module so they can be invoked externally
    def draft?(doc)
      # blah blah
    end
    module_function :draft?
  end

  module DraftFilter
    def is_draft(doc)
      Draft::draft?(doc) # method forwarding
    end

    Liquid::Template.register_filter(DraftFilter)
  end
end

Jekyll Tag Plugins

Jekyll tag plugins are easier to write when using a template, and they are easier to manage when distributed as Ruby gems. Nugem does a good job of creating a working scaffold of Jekyll tags, customized for your needs.

Following is a demonstration of how to use nugem to create a new Jekyll plugin that defines one Jekyll tag. The gem that contains the tag will be called jekyll_highlight_tag, and the Jekyll tag will be called highlight. This plugin would be better implemented as a filter.

Shell
$ nugem jekyll jekyll_highlight_tag --tag highlight
Please list the names of the options for the highlight Jekyll/Liquid tag:  text fg_color bg_color
What is the type of text? (tab autocompletes) [boolean, string, numeric] (string)
What is the type of fg_color? (tab autocompletes) [boolean, string, numeric] (string)
What is the type of bg_color? (tab autocompletes) [boolean, string, numeric] (string)
Initialized empty Git repository in /mnt/c/work/ruby/nugem/generated/jekyll_highlight_tag/.git/
Do you want to create a repository on GitHub named jekyll_highlight_tag? (y/N)  y
Enumerating objects: 66, done.
Counting objects: 100% (66/66), done.
Delta compression using up to 12 threads
Compressing objects: 100% (57/57), done.
Writing objects: 100% (66/66), 461.38 KiB | 445.00 KiB/s, done.
Total 66 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:mslinn/jekyll_highlight_tag.git
  * [new branch]      master -> master
branch 'master' set up to track 'origin/master'. 

Lets see what was written to the generated/ directory. The following shows only the first 2 levels of directories, without files.

Shell
$ tree -adL 2 generated/
generated/
  └── jekyll_highlight_tag
      ├── .bundle
      ├── .git
      ├── .vscode
      ├── bin
      ├── demo
      ├── lib
      ├── spec
      └── test
9 directories

The above shows that the new Jekyll plugin, ready to be made into a gem, is stored in generated/jekyll_highlight_tag. A public git repository was created on GitHub, and the contents of the directory were committed.

I use the generated/ directory as a scratch area for experimentation. Before we go any further, you might want to move the generated/jekyll_highlight_tag directory somewhere permanent. I moved it to the directory pointed to by $work, and made that directory current.

Shell
$ mv generated/jekyll_highlight_tag $work/

$ cd $work/jekyll_highlight_tag

Regular readers of this blog will know that I use environment variables to point to directories; this allows me to address the contents of $work on every machine, even though the environment variable might point to /mnt/f/work on one machine, and /data/work on another.

Next, I launched the project using Visual Studio Code:

Shell
$ code .

Jekyll Block Tag Plugins

Jekyll block tag plugins are just like tag plugins, plus they also have a content body.

The following is a demonstration of how to use nugem to create a new Jekyll plugin that defines one Jekyll block tag. The gem that contains the tag will be called jekyll_highlight_block, and the Jekyll tag will be called highlight2. This plugin would be better implemented as a filter.

Shell
$ nugem jekyll jekyll_highlight_block --block highlight2
Please list the names of the options for the highlight2 Jekyll/Liquid tag:  color bg_color
What is the type of fg_color? (tab autocompletes) [boolean, string, numeric] (string)
What is the type of bg_color? (tab autocompletes) [boolean, string, numeric] (string)
Initialized empty Git repository in /mnt/c/work/ruby/nugem/generated/jekyll_highlight_tag/.git/
Do you want to create a repository on GitHub named jekyll_highlight_block? (y/N)  y
Enumerating objects: 66, done.
Counting objects: 100% (66/66), done.
Delta compression using up to 12 threads
Compressing objects: 100% (57/57), done.
Writing objects: 100% (66/66), 461.38 KiB | 445.00 KiB/s, done.
Total 66 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:mslinn/jekyll_highlight_block.git
  * [new branch]      master -> master
branch 'master' set up to track 'origin/master'. 

The structure of the generated code for Jekyll block tags is identical to that of regular Jekyll tags.

The new Jekyll plugin, ready to be made into a gem, is stored in generated/jekyll_highlight_block. A public git repository was created on GitHub, and the contents of the directory were committed.

As with the preceding tag plugin, you might want to move the generated/jekyll_highlight_block directory somewhere permanent. I moved it to the directory pointed to by $work, and made that directory current.

Shell
$ mv generated/jekyll_highlight_block $work/

$ cd $work/jekyll_highlight_block

Next, I launched the project using Visual Studio Code:

Shell
$ code .

Usage

Given this markup in an HTML file:

HTML page
{ % highlight2 %}
Hello, world!
{ % endhighlight2 %}

The generated HTML from the block tag looks like this:

Generated HTML
<span style='color: black; background: yellow; padding: 2px;'>Hello, world!</span>

Here is another example:

HTML page
{% highlight2 fg_color="yellow" bg_color="green" %}
Hello, world!
{% endhighlight2 %}

The generated HTML from the block tag is as follows:

Generated HTML
<span style='color: yellow; background: green; padding: 2px;'>Hello, world!</span>

Jekyll Hook Plugins

Modifying Pages Across the Entire Jekyll Site

You can modify the generated HTML for the entire Jekyll website. This is easy to do.

The very last hook that gets called before writing posts to disk is :post_render. We can modify the output property of the document at the :documents :post_render hook to make edits to rendered web pages in collections, regardless of whether they were originally written in Markdown or HTML:

Ruby code
module JekyllPluginHookExamples
  Jekyll::Hooks.register(:documents, :post_render) do |doc|
    doc.output.gsub!('Jekyll', 'Awesome')
  end
end

To also modify web pages that are not in a collection (for example, /index.html), add the following into the above module JekyllPluginHooks:

Ruby code
Jekyll::Hooks.register(:pages, :post_render) do |page|
  page.output.gsub!('Jekyll', 'Awesome')
end

Notice that both of the hook invocations have duplicate code. If we want all web pages to be modified, we can rewrite the above and extract the common code to a new method called modify_output:

Ruby code
module JekyllPluginHookExamples
  def modify_output
    Proc.new do |webpage|
      webpage.output.gsub!('Jekyll', 'Awesome')
    end
  end

  module_function :modify_output

  Jekyll::Hooks.register(:documents, :post_render, &modify_output)
  Jekyll::Hooks.register(:pages, :post_render, &modify_output)
end

The demo/index.html web page now looks like the following:

If you want to translate web pages into other languages or dialects, for example, Pig Latin or Pirate Talk, or even spelling and grammar autocorrection, just rewrite modify_output to suit.

Talk Like a Pirate Translator

I could not help myself, and wrote a quick Pirate Talk translator for Jekyll sites. This is an example of a Jekyll hook plugin.

Ruby code
require "active_support"
require "active_support/inflector"
require "nokogiri"
require "talk_like_a_pirate"

def pirate_translator
  proc do |webpage|
    html = Nokogiri.HTML(webpage.output)
    html.css("p").each do |node|
      node.content = TalkLikeAPirate.translate(node.content)
    end
    webpage.output = html
  end
end

module_function :pirate_translator

Jekyll::Hooks.register(:documents, :post_render, &pirate_translator)
Jekyll::Hooks.register(:pages, :post_render, &pirate_translator)

Here is the output of one of the demo web pages:

Original HTML
<h2>Don't Worry, Be Happy</h2>
<p>
If you do not worry, someone else will.
That is their problem.
Enjoy life, it comes at you fast.
</p>
<p class="copyright" id="copyright" xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
T' tha extent possible under law, Michael Slinn has waived all copyright n' related or neighborin' rights t'
Jekyll Plugin Template Collection.
This duty is published from Great North.
</p>
<p class="copyright" id="copyright" xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
<a rel="license" style="float: left; margin-right: 1em; padding-top: 9px; padding-bottom: 2em;"
href="http://creativecommons.org/publicdomain/zero/1.0/">
<img src="http://i.creativecommons.org/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0" />
</a>
To the extent possible under law,
<a rel="dct:publisher"
href="https://www.mslinn.com/blog/2022/03/28/jekyll-plugin-template-collection.html">
<span property="dct:title">Michael Slinn</span></a>
has waived all copyright and related or neighboring rights to
<span property="dct:title">Jekyll Plugin Template Collection</span>.
This work is published from <span property="vcard:Country" datatype="dct:ISO3166" content="CA"
about="https://www.mslinn.com/blog/2022/03/28/jekyll-plugin-template-collection.html"> Canada</span>.

Notice that the copyright has had all the inner HTML removed by my simple translator. With more work (and more code), some of the inner HTML could be retained.

HTML translated to Pirate Talk
<h2>Don't Worry, Be Happy</h2>
<p>
If ye d' not worry, someone else will.
That is their problem.
Enjoy life, it comes at ye fast.
</p>
<p class="copyright" id="copyright" xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
T' tha extent possible under law, Michael Slinn has waived all copyright n' related or neighborin' rights t'
Jekyll Plugin Template Collection.
This duty is published from Great North.
</p>

The translated HTML renders in a web browser like this:

Selecting Pages to Translate

The above pirate_translator plugin modifies every page on the website. If you want to only translate certain pages, you could take advantage of the fact that page data, including front matter variables, is available to all the hooks for :documents, :pages, and :posts.

Let's modify the hook, so it checks for the existence of a front matter variable called pirate_talk. If present, and it has a value that is not false, that page will be translated into Pirate Talk; otherwise, it will not be modified. Here is the modified version:

lib/jekyll_hook_examples.rb
def pirate_translator
  proc do |webpage|
    return unless webpage.data['pirate_talk']

    html = Nokogiri.HTML(webpage.output)
    html.css("p").each do |node|
      node.content = TalkLikeAPirate.translate(node.content)
    end
    webpage.output = html
  end
end

demo/_posts/2022/2022-01-01-test.html looks like this:

---
categories: [Jekyll, Ruby]
description: Test post.
date: 2022-03-28
last_modified_at: 2022-04-01
layout: default
title: Test Post
pirate_talk: true
---
<h2>Don't Worry, Be Happy</h2>
<p>
  If you do not worry, someone else will.
  That is their problem.
  Enjoy life, it comes at you fast.
</p>

Jekyll Generator Plugins

Generators are only invoked once during the website build process, when all the pages have been scanned and the site structure is available for processing. It is common for generators to include code that loops through various collections of pages.

Functionally, a Jekyll generator is the same as a :site :pre_render hook. The choice of whether to write a generator class, which subclasses Jekyll::Generator, or writing a :site :pre_render hook is arbitrary. Flip a coin to decide.

Generators can create files containing web pages in any directory, and they can modify front matter and content of existing files. Generators usually log information to the console whenever a problem occurs, or progress needs to be shown. Here is the official documentation:

You can create a generator when you need Jekyll to create additional content based on your own rules.

A generator is a subclass of Jekyll::Generator that defines a generate method, which receives an instance of Jekyll::Site. The return value of generate is ignored.

Generators run after Jekyll has made an inventory of the existing content and before the site is generated. Pages with front matter are stored as instances of Jekyll::Page and are available via site.pages. Static files become instances of Jekyll::StaticFile and are available via site.static_files. See the Variables documentation page and Jekyll::Site for details.
# Inspired by the badly broken example on https://jekyllrb.com/docs/plugins/generators/, and completely redone so it works.
module CategoryIndexGenerator
  # Creates an index page for each catagory, plus a main index, all within a directory called _site/categories.
  class CategoryGenerator < Jekyll::Generator
    safe true

    # Only generates content in development mode
    # rubocop:disable Style/StringConcatenation, Metrics/AbcSize
    def generate(site)
      # This plugin is disabled unless _config.yml contains an entry for category_generator_enable and the value is not false
      return if site.config['category_generator_enable']

      return if site.config['env']['JEKYLL_ENV'] == 'production'

      index = Jekyll::PageWithoutAFile.new(site, site.source, 'categories', 'index.html')
      index.data['layout'] = 'default'
      index.data['title'] = 'Post Categories'
      index.content = '<p>'

      site.categories.each do |category, posts|
        new_page = Jekyll::PageWithoutAFile.new(site, site.source, 'categories', "#{category}.html")
        new_page.data['layout'] = 'default'
        new_page.data['title'] = "Category #{category} Posts"
        new_page.content = '<p>' + posts.map do |post|
          "<a href='#{post.url}'>#{post.data['title']}</a><br>"
        end.join("\n") + "</p>\n"
        site.pages << new_page
        index.content += "<a href='#{category}.html'>#{category}</a><br>\n"
      end
      index.content += '</p>'
      site.pages << index
    end
    # rubocop:enable Style/StringConcatenation, Metrics/AbcSize
  end

  PluginMetaLogger.instance.logger.info { "Loaded CategoryGenerator v#{JekyllPluginTemplateVersion::VERSION} plugin." }
end
* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.