Can a plugin use a layout YAML from a theme?

I was wondering if there is a way for a generator plugin to use a layout from a theme.

Almost copying the example from the documentation of Generators, I created this plugin:

# frozen_string_literal: true

require 'jekyll'
require 'liquid'

module Buckygem
  # A generator plugin creating one new page per category found in the site.
  class CategoryPageGenerator < Jekyll::Generator
    safe true

    def generate(site)
      return unless site.layouts.key? 'category_index'

      dir = site.config['category_dir'] || 'categories'
      site.categories.each_key do |category|
        # Slug from https://stackoverflow.com/questions/4308377/ruby-post-title-to-slug
        site.pages << CategoryPage.new(site, site.source, File.join(dir, Buckygem.slugify(category)), category)
      end
    end
  end

  # A Page subclass used in the `CategoryPageGenerator`
  class CategoryPage < Jekyll::Page
    # rubocop:disable Lint/MissingSuper
    def initialize(site, base, dir, category)
      @site = site
      @base = base
      @dir  = dir
      @name = 'index.html'

      process(@name)

      puts site.layouts['category_index']
H
      read_yaml(File.join(base, '_layouts'), 'category_index.html')
      data['category'] = category

      category_title_prefix = site.config['category_title_prefix'] || 'Category: '
      data['title'] = "#{category_title_prefix}#{category}"
    end
  end
  # rubocop:enable Lint/MissingSuper

  # A Liquid filter to generate the path of a category resource.
  module CategoryLinkFilter
    def category_link(input)
      "/categories/#{Buckygem.slugify(input)}"
    end
  end
end

But since I intend to use a category_index.html layout from a theme (as opposed to a file in _layout/of my project), I am getting the following error:

Error reading file /Users/.../_layouts/category_index.html: No such file or directory @ rb_sysopen - /Users/.../_layouts/category_index.html 

Surely.
When you have the liberty to write and use plugins, your limits are endless.

The reason your plugin is failing is because it is trying to read a non-existing file path.

Remember, Jekyll builds a site in distinct phases:

reset => read => generate => render => write

That means, your plugin (which runs during the generate phase) doesn’t have to read layout files again. It would already be available in the container site.layouts.

I’m purposely not walking you through to the solution and it is upto you to dissect the site.layouts container, extract the desired layout and access the extracted layout's data attribute.

Good luck :slight_smile:

2 Likes

Thanks @ashmaroli!

I’ve been digging further to take your advice into account, and see what #read_yaml was doing and changed my page code to the following:

  class CategoryPage < Jekyll::Page
    # rubocop:disable Lint/MissingSuper
    def initialize(site, base, dir, category)
      @site = site
      @base = base
      @dir  = dir
      @name = 'index.html'

      process(@name)

      @data = site.layouts['category_index'].data
      @content = site.layouts['category_index'].content

      @data['category'] = category
      category_title_prefix = site.config['category_title_prefix'] || 'Category: '
      @data['title'] = "#{category_title_prefix}#{category}"
    end
    # rubocop:enable Lint/MissingSuper
  end

Things work much better since I don’t get any errors when building.

But all my category pages end up using the last category that was used, ie the page.title in the layout ends up always being the last category’s title that was used by the generator’s iteration.

I am a little rusty with Ruby but it’s like there was some shared state between all pages that were created and I can’t put my finger where that might come from. Any idea of what is going wrong here? :thinking::pray:

The reason for that is because all of your generated pages have the same @name attribute
equal to "index.html" :slight_smile:

I had this idea already but it behaves the same if I use say index-#{slugify(category)}.html.
Which makes sense since I can have many index.html files in different directories (dir is distinct for each category).

Oh right!
You use @dir to dictate what category the page is associated with…

That said, I have no clue what Buckygem.slugify(input) does…

For further reference, please look into the code-base for jekyll-archives

1 Like

jekyll-archives does pretty much what I wanted to do. I think I’ll just use it instead of developing my own thing.

I will try to look more into the source code anyway and if I manage to get a clear view of a generator’s requirements, I will also try to update https://jekyllrb.com/docs/plugins/generators/ since I believe it lacks quite some details.

Thanks a lot @ashmaroli! :pray:

1 Like