Building A Custom Jekyll Command Plugin

Published: November 23, 2016

Tags:

I recently built jekyll-migrate-permalink, a tool to help deal with the side effects of changing the permalink of a Jekyll blog.

The plugin was spurred by my own contemplation about removing the /blog prefix from the URLs on this blog, an action which, at the time of writing this, I still haven’t taken. If you just up and change URLs, you’ll wind up with a bunch of backlinks that 404. jekyll-redirect-from can help with creating redirects, however it requires updating the front matter on all existing posts with a redirect_from element referencing the old URL. Doing this manually is a lot of work, and error prone. So jekyll-migrate-permalink was born as an attempt to make this process less painful.

While Ruby isn’t my most comfortable language, I decided to build it as a custom command Jekyll plugin (rather than e.g. writing in PHP, which I work with every day). The benefit of writing the tool as a Jekyll plugin is that it allows access to the same primitives Jekyll uses when it compiles a site.

In the process I hit quite a few stumbling blocks. While other types of plugins just allow you to drop an .rb file into a _plugins folder in the root of the site, with commands, your plugin needs to be turned into a Gem. Maybe I just don’t know the right terms to Google, but I had a lot of trouble finding resources to help me through the process. Now that I’ve released the plugin, I decided to publish a guide for creating a simple custom command Jekyll plugin, putting everything I learned into one place.

What We’ll Build

In this guide we’ll build a dead simple plugin that adds a jekyll hello sub-command. Executing the command will simply print the word “hello” to the terminal.

What You Need

This guide assumes you already have the following set up on your computer…

  • Ruby
  • Bundler
  • Jekyll

Scaffold the Gem

The bundle executable can be used to scaffold a Gem to you. Head to a folder where you normally store your projects and run bundle gem jekyll-hello. You should see something like this in your terminal (if this is you’re first time you’ll have to answer a few questions which will be stored as defaults in ~/.bundle/config).

$ bundle gem jekyll-hello
Creating gem 'jekyll-hello'...
MIT License enabled in config
      create  jekyll-hello/Gemfile
      create  jekyll-hello/.gitignore
      create  jekyll-hello/lib/jekyll/hello.rb
      create  jekyll-hello/lib/jekyll/hello/version.rb
      create  jekyll-hello/jekyll-hello.gemspec
      create  jekyll-hello/Rakefile
      create  jekyll-hello/README.md
      create  jekyll-hello/bin/console
      create  jekyll-hello/bin/setup
      create  jekyll-hello/LICENSE.txt
      create  jekyll-hello/.travis.yml
Initializing git repo in /Users/maxchadwick/Projects/jekyll-hello

The Code

Open up the new jekyll-hello folder in your editor. Create a new folder in lib/jekyll called commands. Inside of that folder create a file named hello.rb. Inside hello.rb add the following code…

module Jekyll
  module Commands
    class Hello < Command
      class << self
        def init_with_program(prog)
          prog.command(:hello) do |c|
            c.action do |args, options|
              Jekyll.logger.info "Hello!"
            end
          end
        end
      end
    end
  end
end

You’re tree should now look like this…

$ tree
.
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── jekyll-hello.gemspec
└── lib
    └── jekyll
        ├── commands
        │   └── hello.rb
        ├── hello
        │   └── version.rb
        └── hello.rb

5 directories, 10 files

Make Sure Your Command Gets Loaded By The Gem

Next, open up lib/jekyll/hello.rb. At the bottom require the file we just created with require "jekyll/commands/hello.rb". The file should now look like this…

require "jekyll/hello/version"

module Jekyll
  module Hello
    # Your code goes here...
  end
end

require "jekyll/commands/hello.rb"

Make Sure Jekyll Loads Your Plugin

Open the Gemfile of your Jekyll site. Specify jekyll-hello in the jekyll_plugins group. Replace “path” as needed depending on where you created your gem.

group :jekyll_plugins do
   gem 'jekyll-hello', '0.1.0', :path => '/Users/maxchadwick/Projects/jekyll-hello'
end

This part really threw me for a loop. Turns out you MUST specify the version when working with Gems from local sources.

Try Out Your New Jekyll Plugin!

Now from within your jekyll project. you should be able to execute the jekyll hello sub-command.

$ bundle exec jekyll hello
             Hello!

How It Works

The magic happens as a result of inheriting Jekyll::Command. If you review the definition for that class you’ll see the following code.

# Keep a list of subclasses of Jekyll::Command every time it's inherited
# Called automatically.
#
# base - the subclass
#
# Returns nothing
def inherited(base)
  subclasses << base
  super(base)
end

Basically, the Command class is keeping track of all it’s children inside of subclasses. exe/jekyll, the file that gets executed by the jekyll command is the responsible for identifying the appropriate subcommand and calling the action.

Jekyll::Command.subclasses.each { |c| c.init_with_program(p) }

p.action do |args, _|
  if args.empty?
    Jekyll.logger.error "A subcommand is required."
    puts p
    abort
  else
    subcommand = args.first
    unless p.has_command? subcommand
      Jekyll.logger.abort_with "fatal: 'jekyll #{args.first}' could not" \
        " be found. You may need to install the jekyll-#{args.first} gem" \
        " or a related gem to be able to use this subcommand."
    end
  end
end

Packaging It Up

A command that just prints out “Hello” to the terminal is pretty useless, but I’d imagine if you’re reading this you’re working on something that other people might actually want to use. In order to make your gem available on RubyGems.org you need to update jekyll-hello.gemspec. The one that Bundler scaffolded for you contains comments about what you need to fill in. Then use gem build jekyll-hello.gemspec to build your gem,

Once built, make sure you’ve registered for an account at RubyGems.org. You’ll then be able to push with gem push jekyll-hello-0.1.0.gem once you’ve entered your credentials at the prompt.

Conclusion

I hope that this article was helped understand how to build command based Jekyll plugins. If you have any comments, feel free to drop a note comments below. I’m not a ruby expert by any means, so any corrections are appreciated as well. Of course, as always, you can reach me on Twitter.

Max Chadwick Hi, I'm Max!

I'm a software developer who mainly works in PHP, but loves dabbling in other languages like Go and Ruby. Technical topics that interest me are monitoring, security and performance. I'm also a stickler for good documentation and clear technical writing.

During the day I lead a team of developers and solve challenging technical problems at Rightpoint where I mainly work with the Magento platform. I've also spoken at a number of events.

In my spare time I blog about tech, work on open source and participate in bug bounty programs.

If you'd like to get in contact, you can find me on Twitter and LinkedIn.