Some Basic Rake Tasks for Jekyll Users
I recently tweaked a few of the (very basic) rake tasks I’m using to keep this blog going. Ruby isn’t my thing, but writing these was interesting and I figured I’d share in case they are useful to anyone else.
Wait, back up. What’s a rake task?
Rake is a task runner in Ruby. Jekyll, which powers this blog, is written in Ruby. Similar to how a JS-based project might have scripts in package.json
that allow a developer to run tests or start a server, rake allows devs to define custom tasks and then run them, using the syntax rake taskname
. In my case, I wanted a task that would create a new draft based on a template, and a task that would automate deployment of the blog, to prevent any mistakes from making it into prod. (I recently erased my site by rsync
ing the wrong directory to my server. I got it back a minute later, but I’m tired of doing that.)
Ok, I’m sold. How do I write a rake task?
I created a file in my site’s root directory called rakefile
that holds the tasks. Then I define my tasks in Ruby. I had to google a lot of syntax for these very basic tasks – Ruby is not a language I know!
desc "Create draft with template."
task :newdraft do
puts "new draft name: "
my_file = STDIN.gets.chomp
source_file = "path/to/site/root/folder/template.md"
destination_file = "path/to/site/root/folder/_drafts/#{my_file}.md"
unless File.exist?(destination_file)
FileUtils.cp(source_file,destination_file)
puts destination_file
end
cmd = "code #{destination_file}"
system(cmd)
end
This defines a task that takes input from the user (me), and creates a new Markdown file from copying a basic template with some default front-matter defined, then opens it in VSCode. Notice I have basically no input sanitization here, and things like the default IDE (VSCode) are hardcoded. Change to suit your needs.
The other thing you’ll notice is that the task has a description. This, it turns out, is required for the task to be discoverable by Ruby, so don’t leave it out!
The second task is even simpler:
desc "Build and deploy."
task :deploy do
system("/path/to/deploy/script.sh")
end
The deploy script is a simple bash script that changes to the site’s root directory, runs the jekyll build command, then rsyncs the _site
folder to another folder on my computer, which is set up with Syncthing to copy new files to the remote web server. (This is not a typical build pipeline and I am thankful to be in a two-nerd household.)
One might ask, why have a rake task if all it does is execute a shell script? The cool thing about rake tasks is they can be executed from anywhere in the directory tree at the level of the rakefile or below. As I understand it, rake
will start in its current directory and then keep going upward until it finds a rakefile with a matching task name.
This is why ‘real’ Ruby projects usually namespace their tasks, because this behavior in a big project could lead to issues. In my tiny project, though, it’s exactly what I want. Now, I can do rake deploy
from anywhere within my site structure.
Yes, I could also just run /the/full/path/to/deploy.sh
from anywhere, but that involves more typing.
That got me thinking, too – to start the Jekyll local server, I type bundle exec jekyll serve --drafts
. This can be shortened to bundle exec jekyll s -D
, and if I use history search it’s really not that bad to retrieve this command, but could I make this more efficent?
With a rake task that looks like this:
task :start, :withDrafts do |t, args|
trap('SIGINT') { puts "\nI quit."; exit }
args.with_defaults(:withDrafts=>"false")
puts("Running the server with drafts #{args[:withDrafts]? 'on': 'off'}")
system '/path/to/start/file/start.sh', args[:withDrafts]
end
and a bash script that looks like this:
#! /bin/bash
SITE_ROOT_DIR="/full/path/to/my/site/root"
WITH_DRAFTS=$1
if [ $WITH_DRAFTS = 'true' ]; then
cd $SITE_ROOT_DIR && bundle exec jekyll serve --drafts
else
cd $SITE_ROOT_DIR && bundle exec jekyll serve
fi
I can start a rake task that starts the server and waits until I ctrl-c to end the task. There are two somewhat interesting bits in the Ruby code: trap
works the same as a bash
trap, in that it listens for the ctrl-c signal and then executes the code in the function. This is just to make the exit look cleaner, the task works fine without it.
And adding the :withDrafts
parameter to the task, with a default set of false, allows me to pass true or false into the shell script. This syntax is pretty bonkers to me as a non-Ruby user but it seems to work fine.
Note that for both of the tasks that run shell scripts, chmod +x script.sh
is required to make Ruby able to run these as executables.
This was a fun exercise. I have now written nearly 900 words on this topic, plus the testing and writing of the rake file itself, which means I have not yet saved myself any time with these new commands. But maybe a hundred or so blog posts later I’ll be singing a different tune. :)