Creating a simple post generator in Bridgetown with the command line


I’ve always wanted to create posts via the command line in Bridgetown.

Something like how Rails does it, say:

rails new model Car

It’s easy to do the same in Bridgetown with rake.


1. Create a task in your Rakefile

You can create a rake task and run it with the bin/bridgetown executable, as explained here in the docs.

For our post generator, we’ll add a create_post task.

# Rakefile

task :create_post do
end

We also want to pass arguments to our task, so we can do something like:

create_post TITLE="Hello World"

I’ll use ENV variables to capture arguments in this guide. Here’s a good article on passing arguments to rake tasks if you want to try something different.

Going back to the example above, we want to pass in a title to our post generator, which is done as shown below:

# Rakefile

task :create_post do
  title = ENV["TITLE"]
end

Now, we need to construct a filename that matches Bridgetown’s naming convention for posts, which is in the form of:

YEAR-MONTH-DAY-post-title.EXT

We’ll use Time.now to generate the date:

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 
end

Here’s a good cheatsheet for the different strftime combinations in Ruby.

We also need to replace the spaces in our post title with dashes, which we do below:

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")
end

String#tr is a nifty method for replacing characters efficiently in Ruby. Rails also uses #tr for dasherize in the ActiveSupport module.

We want to downcase the title to keep the filename consistent with the naming convention.

Don’t forget the extension for the filename.

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")

  # Extension for post file
  extension = ".md"
end

You can also accept a command line argument for the extension with something like ENV["EXTENSION"] if you use multiple extensions for your posts.

Finally, combine them all together (don’t forget the extra dash that connects the date and the title):

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")

  # Extension for post file
  extension = ".md"

  filename = date + "-" + formatted_title + extension

  puts filename
end

We also added a temporary puts statement to test whether our task works so far.

Try running the command and check if the output adheres to Bridgetown’s conventions, for example:

bin/bridgetown create_post TITLE="Hello World"
=> 2022-04-02-hello-world.md

You may need to modify the script to cater for specific scenarios (e.g. stripping off commas, umlauts, extra whitespaces, etc.). That will not be covered in this guide for now.


2. Creating the file

Now that our filename generator is ready, it’s time to create the file.

Posts are stored in src/_posts/ by default, so we’re going to use that as our base directory when creating the post file. You may need to change this depending on your folder structure.

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")

  # Extension for post file
  extension = ".md"

  filename = date + "-" + formatted_title + extension

  # Don't forget the trailing / in your posts directory
  base_url = "src/_posts/"
end

Next, we’ll combine the base_url and filename to get the path for our generated post. We’ll also use File.open to create the file.

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")

  # Extension for post file
  extension = ".md"

  filename = date + "-" + formatted_title + extension

  # Don't forget the trailing / in your posts directory
  base_url = "src/_posts/"

  path = base_url + filename

  File.open(path, "w+") do |file|
  end
end

File.open takes in a string which will be the path for the file you’re creating.

The second argument is the file mode, which means whether the file is read-only or writable or both. Passing in w+ here means the file we’re creating is open to reads and writes.

The man page for fopen goes in detail about the different modes and how they work during file creation.

When you pass a block to File.open, you’ll receive an IO object that lets you interact with the newly-created file. This lets us edit the file directly which leads us to the next and final section.


3. Adding front matter to the post

We want our post generator to automatically include front matter so we don’t have to do it ourselves.

We do this by using the puts method of the IO object we had earlier. puts writes strings you pass to it to the file, which lets us generate front matter as shown below:

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")

  # Extension for post file
  extension = ".md"

  filename = date + "-" + formatted_title + extension

  # Don't forget the trailing / in your posts directory
  base_url = "src/_posts/"

  path = base_url + filename

  File.open(path, "w+") do |file|
    file.puts "---"
    file.puts "layout: post"
    file.puts "title: #{title}"
    file.puts "categories: updates"
    file.puts "---"
  end
end

This snippet:

File.open(path, "w+") do |file|
  file.puts "---"
  file.puts "layout: post"
  file.puts "title: #{title}"
  file.puts "categories: updates"
  file.puts "---"
end

will generate our file, and write to it with the puts calls above.

Open the file and you should see something like this:

---
layout: post
title: Your title here
categories: updates
---

For clarity, let’s add a post-create message to let us know if the post is created successfully.

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now

  # Returns a date with the format YYYY-MM-DD
  date = current_time.strftime("%F") 

  # Replace spaces with dashes
  formatted_title = title.downcase.tr(" ", "-")

  # Extension for post file
  extension = ".md"

  filename = date + "-" + formatted_title + extension

  # Don't forget the trailing / in your posts directory
  base_url = "src/_posts/"

  path = base_url + filename

  File.open(path, "w+") do |file|
    file.puts "---"
    file.puts "layout: post"
    file.puts "title: #{title}"
    file.puts "categories: updates"
    file.puts "---"
  end

  puts "Successfully created #{title} at #{path} 🎉"
end

…and you’re done!

Test it out by running:

bin/bridgetown create_post TITLE="Your title here"

and see if the post appears in your site, as well as checking whether the generated front matter appears in the Markdown file. Happy hacking!


Final script

# Rakefile

task :create_post do
  title = ENV["TITLE"]

  current_time = Time.now
  date = current_time.strftime("%F") 

  formatted_title = title.downcase.tr(" ", "-")

  extension = ".md"

  filename = date + "-" + formatted_title + extension

  base_url = "src/_posts/"

  path = base_url + filename

  File.open(path, "w+") do |file|
    file.puts "---"
    file.puts "layout: post"
    file.puts "title: #{title}"
    file.puts "categories: updates"
    file.puts "---"
  end

  puts "Successfully created #{title} at #{path} 🎉"
end