I don't program at work much, as a technical writer. From time to time, however, I do get to dig into a bit of code as I work on the docs repository at CircleCI. Turns out Ruby is a pretty neat and tidy language (but you maybe already knew that).

I have often heard people profess their love for Ruby as a nice / clean / easy / fun language; and usually within the same sentence, those people would say they quite like the syntax.

My use of Ruby has only ever been at companies that used Rails, and to be honest, it's kind of hard to see the Ruby code, and not the Rails code, from my experience. When I was refactoring/writing Ruby code, I was thinking more about "The Rails Way" and wasn't always sure what methods were part of Ruby and what had been brought in by Rails.

In the last two weeks I've been able to write some simple scripts in Ruby. No frameworks, no libraries. It's been fun!

For one task, I learned about monkey patching, which I had heard of several times, but never actually implemented. In this case, I was patching a Jekyll plugin that ensures all external links on a site open in a new tab. The library worked well but not on non-ascii characters (and would simply stop execution). As some of our links have Japanese characters in them, the plugin would give up . Someone had put up a PR 6 months ago or so, but it never got merged in. Rather than have to fork the library, or vendor the library, I remembered monkey patching (Thanks Kieran for teaching me about it so long ago) and set about to work.

The next sample was writing another Jekyll plugin that iterates over files in a directory, shells out to (rip)grep and checks if the file paths are being used in content directories. This was super easy and fun!

Since the code-base is open source I can share some snippets.

#!/usr/bin/env ruby

# This script is intended to be run locally. It iterates over all the images in
# assets/img/docs and then scans all markdown files for links to those images.
# If an image is not being used, it gets moved to the assets/img/docs/_unused/
# folder.
#
# This script assumes you have ripgrep installed, as it can take a bit of time.
# This script makes use of a few system calls, but their output is not
# significant and so their output has been silenced.

require 'fileutils'

def find_unused

  # check if user has ripgrep installed
  if not system("which rg > /dev/null")
    puts "\n NOTE! Ripgrep was not found on your system, 'find_unused_images' unable to run without it.\n\n"
    return
  end

  img_files = Dir["assets/img/docs/**"]
  unused_files = []

  puts "Looking for unused image files..."

  img_files.each do |file_path|
    x = system("rg #{File.basename(file_path)}  > /dev/null " )
    if !x
      unused_files.push(file_path)
      FileUtils.mv(file_path, "assets/img/docs/_unused/#{File.basename(file_path)}")
    end
  end

  puts "Moved #{unused_files.count()} unused images from /assets/img/docs/ to /assets/img/docs/_unused"
end

# Uncomment when you want to use this script.
# Jekyll::Hooks.register :site, :after_init do |site|
#   find_unused()
# end

I definitely feel like the above could probably be even cleaner, as I haven't put the hours in to learn what constitutes idiomatic Ruby (many programming language communities like to talk about how there is "writing X" and there is "writing idiomatic X"), but nonetheless, it was quite delightful to write.

The only thing I feel quite clunky about is classes and modules. I don't know what is expected here. This is probably because I've had my head buried in functional languages over the past two years. In my mind, I just want to write functions that take data, do a thing, and spit out data. The above code probably should be in a module, otherwise, I suppose the function goes into a global runtime namespace? I'm not sure yet!

Until next time, shiny gem!

Aside: This post is definitely making me want to return to Elixir, which I probably suffered the same "forest for the trees" syndrome with Phoenix, and just do some scripting with the language itself.