Jekyll Archives Plugin
A Jekyll plugin to generate site post archives.
Installation
Add this line to your site’s Gemfile:
gem 'coffeebrew_jekyll_archives'
And then add this line to your site’s _config.yml
:
plugins:
- coffeebrew_jekyll_archives
Upon building the Jekyll site, the plugin will automatically generate the archive indexes using the following default configuration:
---
archives:
navigation:
data: "navigation"
name: "Archives"
before:
after:
depths: 3
title_format:
root:
type: "string"
style: "/"
year:
type: "date"
style: "%Y"
month:
type: "date"
style: "%b, %Y"
day:
type: "date"
style: "%d %b, %Y"
root_dir: "/"
root_basename: "archives"
index_root: "/archives"
index_basename: "index"
permalink:
root: "/:root_dir"
year: "/:root_dir/:index_root/:year"
month: "/:root_dir/:index_root/:year/:month"
day: "/:root_dir/:index_root/:year/:month/:day"
An example of the generated directory structure is as such:
_site/
├── archives
│ └── 2023
│ ├── 03
│ │ ├── 30
│ │ │ └── index.html
│ │ └── index.html
│ └── index.html
├── archives.html
└── index.html
Configuration
You can override any of the default configuration values above. The plugin will perform a simple validation on your overrides according to these rules:
Navigation config
This tells the plugin how to generate navigation menu.
Key | Allowed Value(s) | Default | Remark |
---|---|---|---|
data | String | navigation | This tells the plugin where in site.data[] to get the navigation data. Usually this would be set in site.data["navigation"] . |
name | String | Archives | This is the name rendered in the navigation menu item. |
before | String | nil | This tells the plugin before which existing menu item to insert the archives’ root index menu item. |
after | String | nil | This tells the plugin after which existing menu item to insert the archives’ root index menu item. |
When before
and after
are nil
, the archives’ root index menu item will be appended last.
If both before
and after
are configured, before
config will be used and after
will be ignored.
Depths config
This tells the plugin what indexes to generate.
There are 3 levels of depths:
Level | Description |
---|---|
1 | Generate yearly index only |
2 | Generate yearly and monthly indexes |
3 | Generate yearly, monthly and daily indexes |
Title format config
This tells the plugin what is the format of the title to be used for the index entries.
There are 4 titles that can be generated:
Level | Description |
---|---|
root | This will be used for the root index entry |
year | This will be used for the yearly index entries |
month | This will be used for the monthly index entries |
day | This will be used for the daily index entries |
For each title format, you can use one of these types:
Type | Description |
---|---|
string | A string that uses format syntax and use available variables: year, month, day, eg. Archive of %{year}-%{month}-%{day} will be interpreted as Archive of 2023-03-12 |
date | A string that strictly uses Ruby date identifiers, the plugin will call date.strftime() on the format string, eg. %d %b, %Y will be interpreted as 12 Mar, 2023 |
Directory and path config
This tells the plugin how to create the directory structure and index page file name.
Key | Allowed Value(s) | Default | Remark |
---|---|---|---|
root_dir | String | / | This tells the plugin what is the root directory to create the root index page. |
root_basename | String | archives | This tells the plugin what is the root index page file name, with .html as the extension. |
index_root | String | /archives | This tells the plugin what is the root directory for the yearly/monthly/daily index pages to be generated. |
index_basename | String | index | This tells the plugin what is the file name to use for the yearly/monthly/daily index pages, with .html as the extension. |
Permalink config
This tells the plugin how to create the directory structure of the yearly/monthly/daily index pages, and the permalink for the index pages will follow the directory structure.
There are 4 levels of pages that can be generated:
Level | Description |
---|---|
root | This will be used for the root index entry |
year | This will be used for the yearly index entries |
month | This will be used for the monthly index entries |
day | This will be used for the daily index entries |
A few placeholders are available to be used in the permalink:
Field | Description |
---|---|
root_dir | This will be the root directory where the root index page is created. |
index_root | This will be the root directory where the yearly/monthly/daily index pages are created. |
year | This will be the current year of the current index page. If the current page is the root index, then year will always be 0001 . |
month | This will be the current month of the current index page. If the current page is a yearly index, then month will always be 01 . |
day | This will be the current day of the current index page. If the current page is a yearly or monthly index, then day will always be 01 . |
Validation
If the config overrides have invalid structure, keys or values, the plugin will raise a
Jekyll::Errors::InvalidConfigurationError
during build.
Layout
The plugin does not provide a default layout. You will need to create your own layout in _layouts
and configure the
defaults in _config.yml
:
---
defaults:
- scope:
type: "archives"
values:
layout: "archive"
permalink: /:path/:basename:output_ext
In addition to Jekyll’s default page data, you can also use the page data generated by the plugin in the layout:
Field | Description |
---|---|
dir | The current index page directory. |
sub_pages | The current index page’s sub-pages according to the hierarchy of: 1) root, 2) yearly, 3) monthly, 4) daily. If the current page is the daily index page, then there will be no sub-pages. |
root | The root index page absolute url. This is useful if you need to highlight the archives’ root navigation menu item while displaying the nested index pages. |
ancestors | The ancestors of the current page. This is useful if you need to render a tree view of the ancestor pages relative to the current page. |
parent | The immediate parent of the current page. |
collection | The posts collection under the current index page, eg. if the current page is a monthly index page, then all the current month’s posts will be available. |
title | The title generated for the current page according to the title_format config. |
year | The current year of the current index page. If the current page is the root index, then year will always be 0001 . |
month | The current month of the current index page. If the current page is a yearly index, then month will always be 01 . |
day | The current day of the current index page. If the current page is a yearly or monthly index, then day will always be 01 . |
An example of a layout that is used by the plugin’s test cases:
---
layout: default
---
<h1>Archives of 0.2.0</h1>
<div class="archives">
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/versions/0.2.0.html">0.2.0</a>
</li>
<ul>
<li>
<i class="fa-solid fa-file"></i>
<a href=""> - </a>
</li>
</ul>
</ul>
</div>
The resulting index pages are as such:
Root
Generated at: _site/archives.html
.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>/</title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about.html">About</a>
<a href="/articles.html">Articles</a>
<a href="/projects.html">Projects</a>
<a href="/archives.html">Archives</a>
</nav>
<div class="container">
<h1>Archives of /</h1>
<div class="archives">
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives.html">/</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/index.html">2021</a>
</li>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2022/index.html">2022</a>
</li>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2023/index.html">2023</a>
</li>
</ul>
</ul>
</div>
</div>
</body>
</html>
Yearly index
Generated at: _site/2021/index.html
for the year of 2021.
Note: Header and navigation elements omitted for clarity.
<div class="container">
<h1>Archives of 2021</h1>
<div class="archives">
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives.html">/</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/index.html">2021</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/03/index.html">Mar, 2021</a>
</li>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/05/index.html">May, 2021</a>
</li>
</ul>
</ul>
</ul>
</div>
</div>
Monthly index
Generated at: _site/2021/03/index.html
for the month of March 2021.
Note: Header and navigation elements omitted for clarity.
<div class="container">
<h1>Archives of Mar, 2021</h1>
<div class="archives">
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives.html">/</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/index.html">2021</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/03/index.html">Mar, 2021</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/03/12/index.html">12 Mar, 2021</a>
</li>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/03/28/index.html">28 Mar, 2021</a>
</li>
</ul>
</ul>
</ul>
</ul>
</div>
</div>
Daily index
Generated at: _site/2021/03/12/index.html
for the day of 12 March 2021.
Note: Header and navigation elements omitted for clarity.
<div class="container">
<h1>Archives of 12 Mar, 2021</h1>
<div class="archives">
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives.html">/</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/index.html">2021</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/03/index.html">Mar, 2021</a>
</li>
<ul>
<li>
<i class="fa-solid fa-calendar-days"></i>
<a href="/archives/2021/03/12/index.html">12 Mar, 2021</a>
</li>
<ul>
<li>
<i class="fa-solid fa-file"></i>
<a href="/2021/03/12/test-post-1.html">12 Mar, 2021 - This is test post 1</a>
</li>
</ul>
</ul>
</ul>
</ul>
</ul>
</div>
</div>
Contributing
Contribution to the gem is very much welcome!
- Fork it (https://github.com/coffeebrewapps/coffeebrew_jekyll_archives/fork).
- Create your feature branch (
git checkout -b my-new-feature
). - Make sure you have setup the repo correctly so that you can run RSpec and Rubocop on your changes. Read more under the Development section.
- Commit your changes (
git commit -am 'Add some feature'
). - If you have added something that is worth mentioning in the README, please also update the README.md accordingly and commit the changes.
- Push to the branch (
git push origin my-new-feature
). - Create a new Pull Request.
The repo owner will try to respond to a new PR as soon as possible.
Development
We want to provide a robust gem as much as possible for the users, so writing test cases will be required for any new feature.
If you are contributing to the gem, please make sure you have setup your development environment correctly so that RSpec and Rubocop can run properly.
- After forking the repo, go into the repo directory (
cd coffeebrew_jekyll_archives/
). - Make sure you have the correct Ruby version installed. This gem requires Ruby >= 2.7.0.
- Once you have the correct Ruby version, install the development dependencies (
bundle install
). - To test that you have everything installed correctly, run the test cases (
bundle exec rspec
). - You should see all test cases pass successfully.
Source directory structure
All the gem logic lives in the /lib
directory:
lib
├── coffeebrew_jekyll_archives
│ ├── config.yml
│ ├── generator.rb
│ ├── page.rb
│ ├── validator.rb
│ └── version.rb
└── coffeebrew_jekyll_archives.rb
The files that are currently in the repo:
File | Description |
---|---|
lib/coffeebrew_jekyll_archives/config.yml |
This contains the default configuration for the plugin to generate the archive indexes. |
lib/coffeebrew_jekyll_archives/generator.rb |
This is the generator that reads the configuration and generate the index pages. |
lib/coffeebrew_jekyll_archives/page.rb |
This is the abstract model of the index pages. |
lib/coffeebrew_jekyll_archives/validator.rb |
This validates the configuration. |
lib/coffeebrew_jekyll_archives/version.rb |
This contains the version number of the gem. |
lib/coffeebrew_jekyll_archives.rb |
This is the entry point of the gem, and it loads the dependencies. |
Test cases directory structure
All the test cases and fixtures live in the /spec
directory:
Note: Some files have been omitted for clarity.
spec
├── coffeebrew_jekyll_archives_spec.rb
├── dest
├── fixtures
│ ├── _config.yml
│ ├── _data
│ │ └── navigation.yml
│ ├── _includes
│ │ └── navigation.html
│ ├── _layouts
│ │ ├── archive.html
│ │ └── default.html
│ └── _posts
│ ├── 2021-03-12-test-post-1.md
│ ├── 2021-03-28-test-post-2.md
│ ├── 2021-05-03-test-post-3.md
│ ├── 2021-05-03-test-post-4.md
│ ├── 2022-01-27-test-post-5.md
│ ├── 2022-03-12-test-post-6.md
│ ├── 2022-11-23-test-post-7.md
│ └── 2023-02-21-test-post-8.md
├── scenarios
│ ├── default
│ │ ├── _site
│ │ │ ├── archives
│ │ │ │ ├── 2021
│ │ │ │ │ ├── 03
│ │ │ │ │ │ ├── 12
│ │ │ │ │ │ │ └── index.html
│ │ │ │ │ │ ├── 28
│ │ │ │ │ │ │ └── index.html
│ │ │ │ │ │ └── index.html
│ │ │ │ │ ├── 05
│ │ │ │ │ │ ├── 03
│ │ │ │ │ │ │ └── index.html
│ │ │ │ │ │ └── index.html
│ │ │ │ │ └── index.html
│ │ │ │ └── 2023
│ │ │ │ ├── 02
│ │ │ │ │ ├── 21
│ │ │ │ │ │ └── index.html
│ │ │ │ │ └── index.html
│ │ │ │ └── index.html
│ │ │ └── archives.html
│ │ ├── _config.yml
│ │ └── context.rb
│ └── invalid_config_keys
│ ├── _config.yml
│ └── context.rb
└── spec_helper.rb
The files that are currently in the repo:
File | Description |
---|---|
spec/coffeebrew_jekyll_archives_spec.rb |
This is the main RSpec file to be executed. It contains all the contexts of various scenarios. |
spec/dest/ |
This directory is where generated files are located. It will be deleted immediately after each context is executed. |
spec/fixtures/ |
This directory follows the Jekyll site source structure and contains the minimal files required to generate the archive index pages. |
spec/fixtures/_posts |
This directory contains the test posts, you can add more to it to test your new feature. |
spec/scenarios/ |
This directory contains the expected files of various test scenarios. |
spec/scenarios/<scenario>/ |
This is the scenario name. |
spec/scenarios/<scenario>/_site/ |
This directory contains the expected archive index pages. |
spec/scenarios/<scenario>/_config.yml |
This contains the config overrides for the scenario. |
spec/scenarios/<scenario>/context.rb |
This is the file that sets up the context for the test case. |
spec/spec_helper.rb |
This contains RSpec configuration and certain convenience methods for the main RSpec file. |
Writing test cases
There is a certain convention to follow when writing new test scenarios. The recommendation is to use the rake tasks provided in the gem to generate the scenario files.
For success scenarios, run:
bundle exec rake coffeebrew:jekyll:archives:test:create_success[test_scenario]
This will generate a placeholder file and directory:
spec
├── coffeebrew_jekyll_archives_spec.rb
├── scenarios
│ └── test_scenario
│ ├── _site
│ ├── _config.yml
│ └── context.rb
└── spec_helper.rb
Where the context.rb
file will be pre-populated:
# frozen_string_literal: true
CONTEXT_TEST_SCENARIO = "when using test_scenario config"
RSpec.shared_context CONTEXT_TEST_SCENARIO do
let(:scenario) { "test_scenario" }
let(:overrides) {} # TODO: remove if unused
let(:generated_files) {} # TODO: remove if unused
let(:expected_files) do
[
]
end
end
For failure scenarios, run:
bundle exec rake coffeebrew:jekyll:archives:test:create_failure[test_scenario]
This will generate a placeholder file and directory:
spec
├── coffeebrew_jekyll_archives_spec.rb
├── scenarios
│ └── test_scenario
│ ├── _config.yml
│ └── context.rb
└── spec_helper.rb
Where the context.rb
file will be pre-populated:
# frozen_string_literal: true
CONTEXT_TEST_SCENARIO = "when using test_scenario config"
RSpec.shared_context CONTEXT_TEST_SCENARIO do
let(:scenario) { "test_scenario" }
let(:generated_files) { [] }
let(:expected_files) { [] }
let(:overrides) do
{
}
end
let(:expected_errors) do
[
]
end
end
If you do write other test cases that are not asserting the generated files like above, you can initiate your convention. The repo owner will evaluate the PR and accept the convention if it fits the repo existing convention, or will recommend an alternative if it doesn’t.
License
See the LICENSE file.