From 319cc95254c09bd121fc339a06f282d71a312cb9 Mon Sep 17 00:00:00 2001 From: Ashwin Maroli Date: Fri, 1 Jan 2021 21:59:26 +0530 Subject: [PATCH] Improve documentation on developing generators (#8527) Merge pull request 8527 --- docs/_docs/plugins/generators.md | 124 ++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/docs/_docs/plugins/generators.md b/docs/_docs/plugins/generators.md index 2fe7a8c8..06716eb4 100644 --- a/docs/_docs/plugins/generators.md +++ b/docs/_docs/plugins/generators.md @@ -3,36 +3,34 @@ title: Generators permalink: /docs/plugins/generators/ --- -You can create a generator when you need Jekyll to create additional content -based on your own rules. +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`]({{ site.repository }}/blob/master/lib/jekyll/site.rb). The -return value of `generate` is ignored. +A generator is a subclass of `Jekyll::Generator` that defines a `generate` method, which receives an instance of +[`Jekyll::Site`]({{ site.repository }}/blob/master/lib/jekyll/site.rb). 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`]({{ site.repository }}/blob/master/lib/jekyll/page.rb) -and are available via `site.pages`. Static files become instances of +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`]({{ site.repository }}/blob/master/lib/jekyll/page.rb) and are +available via `site.pages`. Static files become instances of [`Jekyll::StaticFile`]({{ site.repository }}/blob/master/lib/jekyll/static_file.rb) -and are available via `site.static_files`. See -[the Variables documentation page](/docs/variables/) and -[`Jekyll::Site`]({{ site.repository }}/blob/master/lib/jekyll/site.rb) -for details. +and are available via `site.static_files`. See [the Variables documentation page](/docs/variables/) and +[`Jekyll::Site`]({{ site.repository }}/blob/master/lib/jekyll/site.rb) for details. -For instance, a generator can inject values computed at build time for template -variables. In the following example, the template `reading.html` has two -variables `ongoing` and `done` that are filled in the generator: +In the following example, the generator will inject values computed at build time for template variables. The template +named `reading.html` has two undefined variables `ongoing` and `done` that will be defined or assigned a value when +the generator runs: ```ruby module Reading class Generator < Jekyll::Generator def generate(site) - ongoing, done = Book.all.partition(&:ongoing?) + book_data = site.data['books'] + ongoing = book_data.select { |book| book['status'] == 'ongoing' } + done = book_data.select { |book| book['status'] == 'finished' } - reading = site.pages.detect {|page| page.name == 'reading.html'} + # get template + reading = site.pages.find { |page| page.name == 'reading.html'} + + # inject data into template reading.data['ongoing'] = ongoing reading.data['done'] = done end @@ -40,42 +38,80 @@ module Reading end ``` -The following example is a more complex generator that generates new pages. In this example, the generator will create a series of files under the `categories` directory for each category, listing the posts in each category using the `category_index.html` layout. +The following example is a more complex generator that generates new pages. + +In this example, the aim of the generator is to create a page for each category registered in the `site`. The pages are +created at runtime, so their contents, front matter and other attributes need to be designed by the plugin itself. +* The pages are intended to render a list of all documents under a given category. So the basename of the rendered file +would be better as `index.html`. +* Having the ability to configure the pages via [front matter defaults](/docs/configuration/front-matter-defaults/) +would be awesome! So assigning a particular `type` to these pages would be beneficial. ```ruby -module Jekyll - class CategoryPageGenerator < Generator +module SamplePlugin + class CategoryPageGenerator < Jekyll::Generator safe true def generate(site) - if site.layouts.key? 'category_index' - dir = site.config['category_dir'] || 'categories' - site.categories.each_key do |category| - site.pages << CategoryPage.new(site, site.source, File.join(dir, category), category) - end + site.categories.each do |category, posts| + site.pages << CategoryPage.new(site, category, posts) end end end - # A Page subclass used in the `CategoryPageGenerator` - class CategoryPage < Page - def initialize(site, base, dir, category) - @site = site - @base = base - @dir = dir - @name = 'index.html' + # Subclass of `Jekyll::Page` with custom method definitions. + class CategoryPage < Jekyll::Page + def initialize(site, category, posts) + @site = site # the current site instance. + @base = site.source # path to the source directory. + @dir = category # the directory the page will reside in. - self.process(@name) - self.read_yaml(File.join(base, '_layouts'), 'category_index.html') - self.data['category'] = category + # All pages have the same filename, so define attributes straight away. + @basename = 'index' # filename without the extension. + @ext = '.html' # the extension. + @name = 'index.html' # basically @basename + @ext. - category_title_prefix = site.config['category_title_prefix'] || 'Category: ' - self.data['title'] = "#{category_title_prefix}#{category}" + # Initialize data hash with a key pointing to all posts under current category. + # This allows accessing the list in a template via `page.linked_docs`. + @data = { + 'linked_docs' => posts + } + + # Look up front matter defaults scoped to type `categories`, if given key + # doesn't exist in the `data` hash. + data.default_proc = proc do |_, key| + site.frontmatter_defaults.find(relative_path, :categories, key) + end + end + + # Placeholders that are used in constructing page URL. + def url_placeholders + { + :category => @dir, + :basename => basename, + :output_ext => output_ext, + } end end end ``` +The generated pages can now be set up to use a particular layout or output at a particular path in the destination +directory all via the config file using front matter defaults. For example: + +```yaml +# _config.yml + +defaults: + - scope: + type: categories # select all category pages + values: + layout: category_page + permalink: categories/:category/ +``` + +## Technical Aspects + Generators need to implement only one method:
@@ -99,6 +135,10 @@ Generators need to implement only one method:
-If your generator is contained within a single file, it can be named whatever you want but it should have an `.rb` extension. If your generator is split across multiple files, it should be packaged as a Rubygem to be published at https://rubygems.org/. In this case, the name of the gem depends on the availability of the name at that site because no two gems can have the same name. +If your generator is contained within a single file, it can be named whatever you want but it should have an `.rb` +extension. If your generator is split across multiple files, it should be packaged as a Rubygem to be published at +https://rubygems.org/. In this case, the name of the gem depends on the availability of the name at that site because +no two gems can have the same name. -By default, Jekyll looks for generators in the `_plugins` directory. However, you can change the default directory by assigning the desired name to the key `plugins_dir` in the config file. +By default, Jekyll looks for generators in the `_plugins` directory. However, you can change the default directory by +assigning the desired name to the key `plugins_dir` in the config file.