After writing over two dozen Jekyll plugins, I distilled the common code into Jekyll_plugin_support
.
This F/OSS Ruby gem facilitates writing and testing Jekyll plugins and handles the standard housekeeping that every Jekyll
inline and block tag plugin requires.
Logging, parsing arguments, obtaining references to site and page objects, etc. are all handled.
The result is faster Jekyll plugin writing with fewer bugs.
Jekyll_plugin_support
can be used to create simple Jekyll plugins in
the _plugins/
directory of your Jekyll project, or gem-based Jekyll plugins.
At present, only Jekyll tags and blocks are supported.
Public plugins that use jekyll_plugin_support
include:
jekyll_badge
jekyll_draft
jekyll_emoji
jekyll_flexible_include
jekyll_google_translate
jekyll_href
jekyll_img
jekyll_outline
jekyll_pre
jekyll_quote
... and also the demonstration plugins in
jekyll_plugin_support
Jekyll plugin tags created from jekyll_plugin_support
framework automatically have the following features:
-
Boilerplate is removed, so you can focus on the required logic and output.
-
Arguments are parsed for keywords and name/value parameters.
-
Single or double quotes can be used for arguments and parameters.
-
Important variables are predefined.
-
Error handling is standardized, and includes an automatically defined error type and corresponding CSS tag for each Jekyll tag.
-
Jekyll and Liquid variables, including
layout
,page
andinclude
variables, can be passed as parameters to tags, and used in the body of block tags. -
Plugin registration is integrated, and important configuration details are reported during registration.
-
A custom logger is created for each tag, independent of the default Jekyll logger.
-
Variables can be defined in
_config.yml
, and optionally have different values for debug mode, production mode and test mode. -
An attribution message is available.
-
Draft pages are automatically detected.
-
A demonstration website is provided for easy testing of every plugin.
-
Visual Studio Code debugging is set up for the plugin code and the demo website.
-
Plugins can be subclassed.
-
Nugem
can create working scaffolding for new plugins built usingjekyll_plugin_support
. -
Four new attributes are added to
site
:a.
all_collections
includes all documents in all collections.b.
all_documents
includesall_collections
plus all standalone pages.c.
everything
includesall_documents
plus all static files.d.
sorted_lru_files
is used by a new binary search lookup for matching page suffixes. Thejekyll_href
andjekyll_draft
plugins use this feature.
Jekyll_plugin_support
is packaged as a Ruby gem.
If you want to write a custom Jekyll plugin that will reside in a Jekyll project’s _plugins
directory,
add the following line to your Jekyll plugin’s Gemfile
.
group :jekyll_plugins do
# ...
gem 'jekyll_plugin_support', '>= 1.1.0'
# ...
end
Run the standard jekyll_plugin_support
setup procedure:
$ bin/setup
If your custom plugin will be packaged into a gem, add the following to your plugin’s .gemspec
:
Gem::Specification.new do |spec|
# ...
spec.add_dependency 'jekyll_plugin_support', '>= 1.1.0'
# ...
end
Install the jekyll_plugin_support
gem into your plugin project in the usual manner:
$ bundle
Copy the CSS classes from
demo/assets/css/jekyll_plugin_support.css
to your Jekyll project’s CSS file.
This Jekyll plugin includes a generator,
triggered by a high-priority hook, and a block tag called all_collections
.
JekyllSupport::JekyllBlock
and JekyllSupport::JekyllTag
provide support for Jekyll tag block plugins
and Jekyll inline tag plugins, respectively.
They are very similar in construction and usage.
Instead of subclassing your custom Jekyll block tag class from Liquid::Block
,
subclass from JekyllSupport::JekyllBlock
.
Similarly, instead of subclassing your custom Jekyll tag class from Liquid::Tag
,
subclass from JekyllSupport::JekyllTag
.
Both JekyllSupport classes instantiate new instances of
PluginMetaLogger
(called @logger
) and
JekyllPluginHelper
(called @helper
).
JekyllPluginHelper
defines a generic initialize method, and your tag or block tag class should not need to override it.
Also, your tag or block tag class should not define a method called render, because jekyll_plugin_support
defines one.
Instead, define a method called render_impl
.
For inline tags, render_impl
does not accept any parameters.
For block tags, a single parameter is required, which contains text passed from your block in the page.
Your implementation of render_impl can parse parameters passed to your tag, as described in Tag Parameter Parsing.
In addition, within render_impl
,
the arguments passed to the tag will have been tokenized and parsed,
with Jekyll and Liquid variables substituted for their values,
and all the public Jekyll variables will be available as instance variables.
Error handling will also have been set up,
and access to your tag's entry within _config.yml
will have been set up.
Please see the demo/
project for a well-documented set of demonstration Jekyll plugins built from jekyll_plugin_support
.
Additional information is available here and the
jekyll_plugin_support
documentation.
JekyllSupport::JekyllBlock
and
JekyllSupport::JekyllTag
provide
support for Jekyll block tags and Jekyll inline tags, respectively.
They are similar in construction and usage.
Instead of subclassing your Jekyll block tag class from Liquid::Block
,
subclass from JekyllSupport::JekyllBlock
instead.
Likewise, instead of subclassing your Jekyll inline tag class from Liquid::Tag
,
subclass from JekyllSupport::JekyllTag
instead.
Both JekyllSupport
classes instantiate new instances of
PluginMetaLogger
(called @logger
) and
JekyllPluginHelper
(called @helper
).
Both JekyllSupport
classes define a generic initialize
method,
and your inline tag or block tag class should not override it.
Also, your inline tag or block tag class should not define a method called render
,
because both JekyllSupport
classes define this method.
Instead, define a method called render_impl
.
For inline tags, render_impl
does not accept any parameters.
For block tags, a single parameter is required, which receives any text enclosed within your block by the website author.
No explicit initialization or setup is required.
Jekyll plugins can access the value of
site.all_collections
, site.all_documents
and site.everything
;
however, Liquid code in Jekyll pages and documents cannot.
There are two ways to exclude files from the new site
attributes.
-
The
exclude
entry in_config.yml
can be used as it normally would. -
Adding the following entry to a page’s front matter causes that page to be excluded from the collection created by this plugin:
--- exclude_from_all: true ---
Jekyll generators and tags receive an enhanced version of the site
Jekyll variable.
In the following example of how to use the all_collections
plugin in a custom plugin,
the do_something_with
function processes all Jekyll::Page
s, Jekyll:Document
s, and static files.
@site.everything.each do |apage|
do_something_with apage
end
The general form of the Jekyll tag, including all options, is:
{% all_collections
date_column='date|last_modified'
heading='All Posts'
id='asdf'
sort_by='SORT_KEYS'
%}
One of two date columns can be displayed in the generated HTML:
either date
(when the article was originally written),
or last_modified
.
The default value for the date_column
attribute is date
.
If no heading
attribute is specified, a heading will automatically be generated, which contains the sort_by
values,
for example:
{% all_collections id='abcdef' sort_by="last_modified" %}
The above generates a heading like:
<h2 id="abcdef">All Posts Sorted By last_modified</h2>
To suppress both a h2
heading (and the enclosed id
) from being generated,
specify an empty string for the value of heading
:
{% all_collections heading='' %}
If your Jekyll layout employs jekyll-toc
, then id
attributes are important.
The jekyll-toc
include checks for id
attributes in h2
... h6
tags, and if found,
and if the attribute value is enclosed in double quotes
(id="my_id"
, not single quotes like id='my_id'
),
then the heading is included in the table of contents.
To suppress an id
from being generated,
and thereby preventing the heading from appearing in the automatically generated table of contents from jekyll-toc
,
specify an empty string for the value of id
, like this:
{% all_collections id='' %}
SORT_KEYS
specifies how to sort the collection.
Values can include one or more of the following attributes:
date
, destination
, draft
, label
, last_modified
, last_modified_at
, path
, relative_path
,
title
, type
, and url
.
Ascending sorts are the default; however, a descending sort can be achieved by prepending -
before an attribute.
To specify more than one sort key, provide a comma-delimited string of values.
Included spaces are ignored.
For example, specify the primary sort key as draft
,
the secondary sort key as last_modified
,
and the tertiary key as label
:
{% all_collections
date_column='last_modified'
heading='All Posts'
id='asdf'
sort_by='draft, last_modified, label'
%}
Here is a short Jekyll page, including front matter, demonstrating this plugin being invoked with all default attribute values:
---
description: "
Dump of all collections, sorted by date originally written, newest to oldest.
The heading text will be <code>All Posts Sorted By -date</code>
"
layout: default
title: Testing, 1, 2, 3
---
{% all_collections %}
Following are examples of how to specify the sort parameters.
Explicitly express the default sort
(sort by the date originally written, newest to oldest):
{% all_collections sort_by="-date" %}
Sort by date, from oldest to newest:
{% all_collections sort_by="date" %}
Sort by the date last modified, oldest to newest:
{% all_collections sort_by="last_modified" %}
Sort by the date last modified, newest to oldest:
{% all_collections sort_by="-last_modified" %}
Several attributes can be specified as sort criteria
by passing them as a comma-delimited string.
Included spaces are ignored:
{% all_collections sort_by="-last_modified, -date" %}
{% all_collections sort_by="-last_modified, title" %}
{% all_collections sort_by="-last_modified, -date, title" %}
The following two examples produce the same output:
{% all_collections sort_by="-last_modified,-date" %}
{% all_collections sort_by="-last_modified, -date" %}
Jekyll_plugin_support
defines the following Ruby variables that you can use in your plugin’s render_impl
method:
-
@argument_string
Unparsed markup passed as a parameter to your block tag and inline tag. -
@argv
returns any remaining tokens afterparameter_specified?
has been invoked. -
@attribution
Attribution markup -
@helper
JekyllPluginHelper
instance for your plugin. -
@layout
Layout information -
@logger
jekyll_plugin_logger
instance for your Jekyll plugin. -
@mode
Indicatesproduction
,test
ordevelopment
mode. -
@page
Jekyll::Page
variables -
@paginator
Pagination variables -
@site
Site variables -
@tag_config
Contents of the section of_config.yml
named after your plugin. -
@tag_name
Name of your Jekyll block tag or inline tag plugin. -
@theme
Theme variables -
text
Content provided to your block tag.
Tag arguments can be obtained within render_impl
.
Both keyword options and name/value parameters are supported.
Both JekyllTag
and JekyllBlock
use the standard Ruby mechanism for parsing command-line options:
shellwords
and
key_value_parser
.
All your code has to do is to specify the keywords to search for in the string
passed from the HTML page that your tag is embedded in.
The included demo
website has examples;
both demo/_plugins/demo_inline_tag.rb
and
demo/_plugins/demo_block_tag.rb
contain the following:
@keyword1 = @helper.parameter_specified? 'keyword1'
@keyword2 = @helper.parameter_specified? 'keyword2'
@name1 = @helper.parameter_specified? 'name1'
@name2 = @helper.parameter_specified? 'name2'
If an argument has a variable reference in it, the value of the variable is substituted for the reference. For example, given:
_layouts/default.html
defines a variable calledvar_layout
in its front matter.index.html
defines a variable calledvar_page
in its front matter.index.html
assigns a variable calledx
via the liquidassign
statement.
... then the following references in a page will be substituted for their values in arguments and in block tag bodies:
{% my_block_tag
param1="x={{x}}"
param2="var_page={{page.var_page}}"
param3="var_layout={{layout.var_layout}}"
%}
Assigned variables do not need a namespace: x={{x}}
Page variables must be qualified with the 'page' namespace:
var_page={{page.var_page}}
Layout variables must be qualified with the 'layout' namespace:
var_layout={{layout.var_layout}}
{% endmy_block_tag %}
You can see similar code in demo/demo_inline_tag.html
.
The page['excerpt']
and page['output']
key/value pairs are removed from processing because of recursion issues.
You cannot look up those values from a jekyll_plugin_support
plugin.
For all keyword options, values specified in the document may be provided.
If a value is not provided, the value true
is assumed.
Otherwise, if a value is provided, it must be wrapped in single or double quotes.
The following examples use the die_if_error
keyword option for the
pre
and
exec
tags from the jekyll_pre
plugin %}.
The following sets die_if_error
true
:
{% pre die_if_error %} ... {% endpre %}
The above is the same as writing:
{% pre die_if_error='true' %} ... {% endpre %}
Or writing:
{% pre die_if_error="true" %} ... {% endpre %}
Neglecting to provide surrounding quotes around the provided value causes the parser to not recognize the option.
Instead, what you had intended to be the keyword/value pair will be parsed as part of the command.
For the pre
tag, this means the erroneous string becomes part of the label
value,
unless label
is explicitly specified.
For the exec
tag, this means the erroneous string becomes part of the command to execute.
The following demonstrates the error.
{% pre die_if_error=false %} ... {% endpre %}
The above causes the label to be die_if_error=false
.
{% exec die_if_error=false ls %} ... {% endpre %}
The above causes the command to be executed to be die_if_error=false ls
instead of ls
.
Parameter values can be quoted.
If the value consists of only one token then quoting is optional. The following name/value parameters all have the same result:
pay_tuesday="true"
pay_tuesday='true'
pay_tuesday=true
pay_tuesday
If the values consist of more than one token, quotes must be used. The following examples both yield the same result:
pay_tuesday="maybe not"
pay_tuesday='maybe not'
After your plugin has parsed all the keyword options and name/value parameters,
call @helper.remaining_markup
to obtain the remaining markup that was passed to your plugin.
jekyll_plugin_support
provides support for
Liquid variables
to be defined in _config.yml
, in a section called liquid_vars
.
These variables behave exactly like Liquid variables defined by assign
and capture
expressions,
except they are global in scope; these variables are available in every Jekyll web page.
The following _config.yml
fragment defines 3 variables called var1
, var2
and var3
:
liquid-vars:
var1: value1
var2: 'value 2'
var3: value3
Liquid variables defined in this manner are intended to be embedded in a webpage. They are can be used like any other Liquid variable.
Jekyll expands Liquid variable references during the page rendering process.
Jekyll does not expand Liquid variable references passes as parameters to tag and block plugins, however.
However, plugins made from jekyll_plugin_support
automatically
expand all types of variable references passed as parameters and in block tag bodies.
Jekyll_plugin_support
tag and block plugins expand the following types of variables:
- Jekyll_plugin_support configuration variables, discussed above.
- Jekyll page and layout variables.
- Inline Liquid variables (defined in assign and capture statements).
In the following example web page, Jekyll expands the var1
reference within the <p></p>
tag,
but not the var1
or var2
references passed to my_plugin
.
<p>This is the value of var1: {{var1}}.</p>
{% my_plugin param1="{{var1}}" param2="{{var2}}" %}
Assuming that my_plugin
was written as a jekyll_plugin_support
plugin,
all variable references in its parameters are expanded.
Thus, the above is interpreted as follows when my_plugin
is evaluated during the Jekyll rendering process:
<p>This is the value of var1: value1.</p>
{% my_plugin param1="value1" param2="value 2" %}
Jekyll_plugin_support
expands most of the plugin variables described above,
replacing Liquid variable references with their values.
The exception is @argument_string
, which is not expanded.
jekyll_plugin_support
allows Liquid variables defined in _config.yml
to have different values
when Jekyll is running in development
, production
and test
modes.
When injecting variables into your Jekyll website, Jekyll_plugin_support
refers to definitions specific to the current environment,
and then refers to other definitions that are not overridden.
Here is an example:
liquid-vars:
development:
var1: 'http://localhost:4444/demo_block_tag.html'
var2: 'http://localhost:4444/demo_inline_tag.html'
production:
var1: 'https://github.com/django/django/blob/3.1.7'
var2: 'https://github.com/django-oscar/django-oscar/blob/3.0.2'
var3: 'https://github.com/mslinn'
For the above, the following variable values are set in development
mode:
var1
:http://localhost:4444/demo_block_tag.html
var2
:http://localhost:4444/demo_inline_tag.html
var3
:https://github.com/mslinn
... and the following variable values are set in production
and test
modes:
var1
:https://github.com/django/django/blob/3.1.7
var2
:https://github.com/django-oscar/django-oscar/blob/3.0.2
var3
:https://github.com/mslinn
You can define additional Liquid variables in plugins built using jekyll_plugin_support
.
To do this, make entries in _config.yml
under a key named after the value of @tag_name
.
For example, let’s imagine you create a plugin using jekyll_plugin_support
,
and hou register it with the name phonetic_alphabet
.
You could define Liquid variables that would be made available to content pages in web applications that
incorporate the phonetic_alphabet
plugin.
The following section in _config.yml
defines variables called x
, y
and z
,
with values xray
, yankee
and zulu
, respectively:
phonetic_alphabet:
x: xray
y: yankee
z: zulu
The above definitions allow you to write content pages that use those variables, like the following page containing markup:
---
layout: default
title: Variable demo
---
The letter `x` is pronounced {{x}}.
Similarly, the letters `y` and `z` are pronounced {{y}} and {{z}}.
This information is only useful if a plugin might be executed from within an included file.
While Liquid handles regular variables, Jekyll has special handling for variables defined by include parameters.
For example, the following defines a variable in the include
scope called var1
when processing the body of an included file:
{% include myfile.html var1='value1' %}
You can obtain the value of this variable from the render_impl
method of a
JekyllSupport::JekyllTag
or JekyllSupport::JekyllBlock
subclass as follows:
@var1 = @scopes.first['include']&.[]('var1')
JekyllSupport::JekyllBlock
and JekyllSupport::JekyllTag
subclasses
automatically create error classes, named after the subclass.
For example, if you create a JekyllSupport::JekyllBlock
subclass called DemoBlockTag
,
the automatically generated error class will be called DemoBlockTagError
.
Although you could use it as you would any other error class, JekyllPluginSupport
provides additional helper methods.
These methods fill in the page path and line number that caused the error, shorten the stack trace,
log an error message, and can be used to return an HTML-friendly version of the message to the web page.
The following example is a shortened version of demo/_plugins/demo_block_tag.rb
.
You might want to write similar code in your rescue
blocks.
class DemoBlock < JekyllSupport::JekyllBlock
VERSION = '0.1.2'.freeze
def render_impl(text)
raise DemoBlockTagError, 'Fall down, go boom.'
rescue DemoBlockTagError => e
@logger.error e.logger_message
exit! 1 if @die_on_demo_block_error
e.html_message
end
end
Error class methods have been provided for standardized and convenient error handling:
shorten_backtrace
- most of the lines that spew from a Jekyll backtrace are uninteresting.logger_message
- The message is constructed from the string provided when the error was raised, with the page path and line number added.html_message
- The same aslogger_message
, but constructed with HTML.
When each tag is registered, it self-reports, for example:
INFO PluginMetaLogger: Loaded plugin demo_inline_tag v0.1.2. It has:
Error class: DemoTagError
CSS class for error messages: demo_tag_error
_config.yml contains the following configuration for this plugin:
{"die_on_demo_tag_error"=>false, "die_on_standard_error"=>false}
INFO PluginMetaLogger: Loaded plugin demo_inline_tag_no_arg v0.1.0. It has:
Error class: DemoTagNoArgsError
CSS class for error messages: demo_tag_no_args_error
_config.yml does not contain configuration information for this plugin.
You could add a section containing default values by specifying a section for the tag name,
and an entry whose name starts with `die_on_`, followed by a snake_case version of the error name.
demo_inline_tag_no_arg:
die_on_demo_tag_no_args_error: false
If your tag or block plugin only needs access to the raw arguments passed from the web page,
without tokenization, and you expect that the plugin might be invoked with large amounts of text,
derive your plugin from JekyllBlockNoArgParsing
or JekyllTagNoArgParsing
.
The following minimal examples define VERSION
,
which is important because JekyllPluginHelper.register
logs that value when registering the plugin.
This is how you would define plugins in the _plugins
directory
Boilerplate for an inline tag plugin
require 'jekyll_plugin_support'
module Jekyll
class DemoTag < JekyllSupport::JekyllTag
VERSION = '0.1.0'.freeze
def render_impl
@helper.gem_file __FILE__ # Enables attribution; only works when plugin is a gem
# Your Jekyll plugin logic goes here
end
JekyllPluginHelper.register(self, 'demo_tag')
end
end
Boilerplate for a tag block plugin
require 'jekyll_plugin_support'
module Jekyll
class DemoBlock < JekyllSupport::JekyllBlock
VERSION = '0.1.0'.freeze
def render_impl(text)
@helper.gem_file __FILE__ # Enables attribution; only works when plugin is a gem
# Your Jekyll plugin logic goes here
end
JekyllPluginHelper.register(self, 'demo_block')
end
end
If your plugin is packaged as a gem, then you might need to include version.rb
into the plugin class.
For example, if your version module looks like this:
lib/my_plugin/version.rb:
module MyPluginVersion
VERSION = '0.5.0'.freeze
end
Then your plugin can incorporate the VERSION constant into your plugin like this:
lib/my_plugin.rb:
require 'jekyll_plugin_support'
require_relative 'my_plugin/version'
module Jekyll
class MyBlock < JekyllSupport::JekyllBlock
include MyPluginVersion
def render_impl(text)
@helper.gem_file __FILE__ # Enables attribution; only works when plugin is a gem
# Your code here
end
JekyllPluginHelper.register(self, 'demo_block')
end
end
JekyllTag
and JekyllBlock
subclasses of jekyll_plugin_support
can utilize the attribution
option if they are published as a gem.
JekyllTagNoArgParsing
and JekyllBlockNoArgParsing
subclasses cannot.
This feature is usually only desired for JekyllBlock
subclasses.
- When used as a keyword option, a default value is used for the attribution string.
- When used as a name/value option, the attribution string can be specified.
Using the attribution
option cause subclasses to replace their usual output with HTML that looks like:
<div id="jps_attribute_12345" class="jps_attribute">
<a href="https://github.com/mslinn/jekyll_outline">
<b>Generated by <code>jekyll_outline</code>.
</a>
</div>
The id
attribute is in the sample HTML above is randomized so more than one attribution can appear on a page.
You can decide where you want the attribution string for your Jekyll tag to appear by invoking @helper.attribute
.
For example, this is how the
jekyll_outline
tag generates output:
<<~HEREDOC
<div class="outer_posts">
#{make_entries(collection)&.join("\n")}
</div>
#{@helper.attribute if @helper.attribution}
HEREDOC
Typical usage for the attribution
tag is:
{% my_block_tag attribution %}
Content of my_block_tag.
{% endmy_block_tag %}
The normal processing of my_block_tag
is augmented by interpolating the attribution format string,
which is a Ruby-compatible interpolated string.
The default attribution format string is:
"Generated by the #{name} #{version} Jekyll plugin, written by #{author} #{date}."
Because jekyll_plugin_suppprt
subclasses are gem
s, their gemfile
s define values for
name
, version
, homepage
, and authors
, as well as many other properties.
The date
property is obtained from the plugin/gem publishing date.
An alternative attribution string can be specified properties can be output using any of the above properties:
{% my_tag attribution="Generated by the #{name} #{version} Jekyll plugin, written by #{author} #{date}" %}
Jekyll plugins created using jekyll_plugin_support
are implemented as Ruby classes.
If you would like to create a version of an existing Jekyll plugin, you will need to subclass the plugin.
In order to do that, you will need to override the plugin name and version, which are defined as constants.
Jekyll_plugin_support
provides a method that allows
a constant to be redefined, called redef_without_warning
.
Use it in a subclass like this:
redef_without_warning :PLUGIN_NAME, 'my_plugin'.freeze
redef_without_warning :VERSION, '0.1.0'.freeze
This plugin incorporates a generator, which adds four new attributes to
site
:
all_collections
, all_documents
, everything
, and sorted_lru_files
.
These three attributes can be referenced as site.everything
, site.all_collections
and site.all_documents
.
-
all_collections
includes all documents in all collections. -
all_documents
includesall_collections
plus all standalone pages. -
everything
includesall_documents
plus all static files. -
sorted_lru_files
is used by a new binary search lookup for matching page suffixes. Currently onlyjekyll_href
andjekyll_draft
use this feature.
Jekyll provides inconsistent attributes for
site.pages
,
site.posts
and
site.static_files
.
- While the
url
attributes of items insite.posts
andsite.pages
start with a slash (/),site.static_files
items do not have aurl
attribute. - Static files have a
relative_path
attribute, which starts with a slash (/), but although that attribute is also provided insite.posts
andsite.pages
, those values do not start with a slash. - Paths ending with a slash (
/
) imply that a file calledindex.html
should be fetched. - HTML redirect files created by the
jekyll-redirect-from
Jekyll plugin, which are included insite.static_files
, should be ignored.
These inconsistencies mean that combining the standard three collections of files
provided as site
attributes will create a new collection that is difficult
to process consistently:
# This pseudocode creates `oops`, which is problematic to process consistently
oops = site.all_collections + site.pages + site.static_files
oops
, above, is difficult to process because of inconsistencies in the provided attributes
and how the attributes are constructed.
The generator normalizes these inconsistencies by utilizing the APage
class
and filtering out HTML redirect files.
The all_collections
collection contains APage
representations of site.collections
.
The all_documents
collection contains APage
representations of site.pages
.
The everything
collection contains APage
representations of:
# Pseudocode
site.collections + site.pages + site.static_files - HTML_redirect_files
The site.all_collections
, site.all_documents
and site.everything
attributes
consist of arrays of APage
instances.
The APage
class has the following attributes:
content
(HTML or Markdown), data
(array), date
(Ruby Date), description
, destination
,
draft
(Boolean), ext
, href
, label
, last_modified
or last_modified_at
(Ruby Date),
layout
, origin
, path
, relative_path
, tags
, title
, type
, and url
.
-
href
always starts with a slash. This value is consistent witha href
values in website HTML. Paths ending with a slash (/
) haveindex.html
appended so the path specifies an actual file. -
origin
indicates the original source of the item. Possible values arecollection
,individual_page
andstatic_file
. Knowing the origin of each item allows code to process each type of item appropriately.
The all_collections
block tag creates a formatted listing of Jekyll files.
The ordering is configurable; by default, the listing is sorted by date
, newest to oldest.
The all_collections
tag has a data_source
parameter that specifies which new property to report on
(all_collections
, all_documents
, or everything
).
All the pages in the Jekyll website must have an implicit date
(for example, all posts are assigned this property by Jekyll),
or an explicit date
set in front matter, like this:
---
date: 2022-01-01
---
If a front matter variable called last_modified
or last_modified_at
exists,
its value will be converted to a Ruby Date
:
---
last_modified: 2023-01-01
---
Or:
---
last_modified_at: 2023-01-01
---
Otherwise, if last_modified
or last_modified_at
is not present in the front matter for a page,
the date
value will be used last modified date value.
After checking out the jekyll_plugin_suppprt
repository, run bin/setup
to install dependencies.
bin/console
provides an interactive prompt that allows you to experiment.
To build and install this gem onto your local machine, run:
$ bundle exec rake install
jekyll_plugin_support 0.1.0 built to pkg/jekyll_plugin_support-0.1.0.gem.
jekyll_plugin_support (0.1.0) installed.
Examine the newly built gem:
$ gem info jekyll_plugin_support
*** LOCAL GEMS ***
jekyll_plugin_support (0.1.0)
Author: Mike Slinn
Homepage:
https://github.com/mslinn/jekyll_plugin_support
License: MIT
Installed at: /home/mslinn/.gems
Provides support for writing Jekyll plugins.
To build and install this gem onto your local machine, run:
$ bundle exec rake install
jekyll_all_collections 0.3.8 built to pkg/jekyll_all_collections-0.3.8.gem.
jekyll_all_collections (0.3.8) installed.
Examine the newly built gem:
$ gem info jekyll_all_collections
*** LOCAL GEMS ***
jekyll_all_collections (0.3.8)
Author: Mike Slinn
Homepage:
https://www.mslinn.com/jekyll_plugins/jekyll_all_collections.html
License: MIT
Installed at (0.3.8): /home/mslinn/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0
Provides normalized collections and extra functionality for Jekyll websites.
To release a new version:
-
Update the version number in
version.rb
. -
Add an entry in
CHANGELOG.md
describing the changes since the last release. -
Commit all changes to git; if you don't the next step might fail with a confusing error message.
-
Run the following:
$ bundle exec rake release
The above creates a git tag for the version, commits the created tag, and pushes the new
.gem
file to RubyGems.org.
A pry
breakpoint will be set in the StandardError
handler if pry_on_standard_error: true
is set in variable configuration section of _config.yml
.
For example, if your plugin is called blah
, enable the breakpoint with the following section:
blah:
pry_on_standard_error: true
You can control the verbosity of log output by adding the following to _config.yml
in your Jekyll project:
plugin_loggers:
AllCollectionsTag::AllCollectionsTag: warn
-
First set breakpoints in the Ruby code that interests you.
-
You have several options for initiating a debug session:
-
Use the Debug Demo Visual Studio Code launch configuration.
-
Type the
demo/_bin/debug
command, without the-r
options shown above.... lots of output as bundle update runs... Bundle updated! INFO PluginMetaLogger: Loaded AllCollectionsHooks v0.2.0 :site, :pre_render, :normal hook plugin. INFO PluginMetaLogger: Loaded DraftFilter plugin. INFO PluginMetaLogger: Loaded all_collections v0.2.0 tag plugin. Configuration file: /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/_config.yml Cleaner: Removing /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/_site... Cleaner: Removing /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/.jekyll-metadata... Cleaner: Removing /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/.jekyll-cache... Cleaner: Nothing to do for .sass-cache. Fast Debugger (ruby-debug-ide 0.7.3, debase 0.2.5.beta2, file filtering is supported) listens on 0.0.0.0:1234
-
Run
bin/attach
and pass the directory name of a Jekyll website that has a suitable script called_bin/debug
. Thedemo
subdirectory fits this description.$ bin/attach demo Successfully uninstalled jekyll_all_collections-0.1.2 jekyll_all_collections 0.1.2 built to pkg/jekyll_all_collections-0.1.2.gem. jekyll_all_collections (0.1.2) installed. Fast Debugger (ruby-debug-ide 0.7.3, debase 0.2.4.1, file filtering is supported) listens on 0.0.0.0:1234
-
-
Attach to the debugger process if required. This git repo includes two Visual Studio Code launch configurations for this purpose labeled Attach rdbg and Attach with ruby_lsp.
-
Point your web browser to http://localhost:4444
If a debugging session terminates abruptly and leaves ports tied up,
run the demo/_bin/release_port
script.
A demo / test website is provided in the demo
directory.
It can be used to debug the plugin or to run freely.
The following example plugins use
Ruby’s squiggly heredoc operator (<<~
).
The squiggly heredoc operator removes the outermost indentation.
This provides easy-to-read multiline text literals.
demo/_plugins/demo_tag.rb:
require 'jekyll_plugin_support'
# Use the JekyllSupport module namespace so the self methods are automajically found
module JekyllSupport
DemoInlineTagError = JekyllSupport.define_error
class DemoTag < JekyllTag
VERSION = '0.1.2'.freeze
# JekyllSupport.redef_without_warning 'VERSION', '0.1.2'.freeze
def render_impl
@demo_tag_error = @helper.parameter_specified? 'raise_demo_tag_error'
@keyword1 = @helper.parameter_specified? 'keyword1'
@keyword2 = @helper.parameter_specified? 'keyword2'
@name1 = @helper.parameter_specified? 'name1'
@name2 = @helper.parameter_specified? 'name2'
@standard_error = @helper.parameter_specified? 'raise_standard_error'
if @tag_config
@die_on_demo_tag_error = @tag_config['die_on_demo_tag_error'] == true
@die_on_standard_error = @tag_config['die_on_standard_error'] == true
end
raise DemoInlineTagError, 'This DemoInlineTagError error is expected.' if @demo_tag_error
raise StandardError, 'This StandardError error is expected.' if @standard_error
# _infinity = 1 / 0 if @standard_error # Not required
output
rescue DemoInlineTagError => e # jekyll_plugin_support handles StandardError
@logger.error { e.logger_message }
exit! 1 if @die_on_demo_tag_error
e.html_message
end
private
def output
<<~END_OUTPUT
<pre># jekyll_plugin_support becomes able to perform variable substitution after this variable is defined.
# The value could be updated at a later stage, but no need to add that complexity unless there is a use case.
@argument_string="#{@argument_string}"
@helper.argv=
#{@helper.argv&.join("\n ")}
# Liquid variable name/value pairs
@helper.params=
#{@helper.params&.map { |k, v| "#{k}=#{v}" }&.join("\n ")}
# The keys_values property serves no purpose any more, consider it deprecated
@helper.keys_values=
#{(@helper.keys_values&.map { |k, v| "#{k}=#{v}" })&.join("\n ")}
@layout='#{@layout}'
@page.keys='#{@page.keys}'
remaining_markup='#{@helper.remaining_markup}'
@keyword1='#{@keyword1}'
@keyword2='#{@keyword2}'
@name1='#{@name1}'
@name2='#{@name2}'</pre>
END_OUTPUT
end
JekyllPluginHelper.register(self, 'demo_inline_tag')
end
end
demo/_plugins/demo_block.rb:
require 'cgi'
require 'jekyll_plugin_support'
# Use the JekyllSupport module namespace so the self methods are automajically found
module JekyllSupport
DemoBlockError = JekyllSupport.define_error
class DemoBlock < JekyllBlock
VERSION = '0.1.2'.freeze
def render_impl(text)
@demo_block_error = @helper.parameter_specified? 'raise_demo_block_error'
@keyword1 = @helper.parameter_specified? 'keyword1'
@keyword2 = @helper.parameter_specified? 'keyword2'
@name1 = @helper.parameter_specified? 'name1'
@name2 = @helper.parameter_specified? 'name2'
@standard_error = @helper.parameter_specified? 'raise_standard_error'
if @tag_config
@die_on_demo_block_error = @tag_config['die_on_demo_block_error'] == true
@die_on_standard_error = @tag_config['die_on_standard_error'] == true
end
raise DemoBlockTagError, 'This DemoBlockTagError error is expected.' if @demo_block_error
raise StandardError, 'This StandardError error is expected.' if @standard_error
# _infinity = 1 / 0 if @standard_error # Not required
output text
rescue DemoBlockTagError => e # jekyll_plugin_support handles StandardError
@logger.error { e.logger_message }
exit! 1 if @die_on_demo_block_error
e.html_message
end
private
def output(text)
<<~END_OUTPUT
<pre>@helper.tag_name=#{@helper.tag_name}
@mode=#{@mode}
# jekyll_plugin_support becomes able to perform variable substitution after this variable is defined.
# The value could be updated at a later stage, but no need to add that complexity unless there is a use case.
@argument_string="#{@argument_string}"
@helper.argv=
#{@helper.argv&.join("\n ")}
# Liquid variable name/value pairs
@helper.params=
#{@helper.params&.map { |k, v| "#{k}=#{v}" }&.join("\n ")}
# The keys_values property serves no purpose any more, consider it deprecated
@helper.keys_values=
#{(@helper.keys_values&.map { |k, v| "#{k}=#{v}" })&.join("\n ")}
@helper.remaining_markup='#{@helper.remaining_markup}'
@envs=#{@envs.keys.sort.join(', ')}
@config['url']='#{@config['url']}'
@site.collection_names=#{@site.collection_names&.sort&.join(', ')}
@page['description']=#{@page['description']}
@page['path']=#{@page['path']}
@keyword1=#{@keyword1}
@keyword2=#{@keyword2}
@name1=#{@name1}
@name2=#{@name2}
text=#{text}</pre>
END_OUTPUT
end
JekyllPluginHelper.register(self, 'demo_block_tag')
end
end
The following is an example of no_arg_parsing optimization.
require 'jekyll_plugin_support'
# Use the JekyllSupport module namespace so the self methods are automajically found
module JekyllSupport
class DemoTagNoArgs < JekyllTagNoArgParsing
VERSION = '0.1.0'.freeze
def render_impl
<<~END_OUTPUT
The raw arguments passed to this <code>DemoTagNoArgs</code> instance are:<br>
<code>#{@argument_string}</code>
END_OUTPUT
end
JekyllPluginHelper.register(self, 'demo_inline_tag_no_arg')
end
end
-
Run from the command line:
$ demo/_bin/debug -r
-
View the generated website, which might be at
http://localhost:4444
, depending on how you configured it.
-
Set breakpoints in Visual Studio Code.
-
Run the Debug Demo development or Debug Demo production launch configuration.
Alternatively, you can:
-
Initiate a debug session from the command line by running the
demo/_bin/debug
script:$ demo/_bin/debug Fetching gem metadata from https://rubygems.org/.......... Resolving dependencies... Fetching public_suffix 5.0.4 Fetching nokogiri 1.15.5 (x86_64-linux) Installing public_suffix 5.0.4 Installing nokogiri 1.15.5 (x86_64-linux) Bundle complete! 17 Gemfile dependencies, 96 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. INFO PluginMetaLogger: Loaded DraftFilter plugin. INFO PluginMetaLogger: Loaded outline_js v1.2.1 plugin. INFO PluginMetaLogger: Loaded outline v1.2.1 plugin. Configuration file: /mnt/f/jekyll_plugin_support/demo/_config.yml Cleaner: Removing /mnt/f/jekyll_plugin_support/demo/_site... Cleaner: Removing /mnt/f/jekyll_plugin_support/demo/.jekyll-metadata... Cleaner: Removing /mnt/f/jekyll_plugin_support/demo/.jekyll-cache... Cleaner: Nothing to do for .sass-cache. DEBUGGER: Debugger can attach via TCP/IP (127.0.0.1:37177) DEBUGGER: wait for debugger connection...
-
Once the
DEBUGGER: wait for debugger connection...
message appears, run the Visual Studio Code launch configuration called Attach with rdbg. -
View the generated website, which might be at
http://localhost:4444
, depending on how you configured it.
To release a new version:
-
Update the version number in
version.rb
. -
Add an entry to
CHANGELOG.md
describing the changes since the previous version. -
Commit all changes to git; if you don't the next step might fail with an unexplainable error message.
-
Run the following:
$ bundle exec rake release
The above creates a git tag for the version, commits the created tag, and pushes the new
.gem
file to RubyGems.org.
- Fork the project
- Create a descriptively named feature branch
- Add your feature
- Submit a pull request
The gem is available as open source under the terms of the MIT License.