(Accidentally) Implementing the World’s Worst Static Site Generator: Part 1
Tags: Elixir, Ruby, Docker, Phish, DevOps

Part One

Dear Diary,

Today I decided to make a static site generator. It will be extremely simple and will just do its job. I will start with a posts directory and a ruby script. From there, I’m going to let my mind wander.

Version 1: Ruby a Gem, and a Directory,

If you want a static site generator, you will need to have some posts to convert to HTML from some markup language. I chose markdown and the gem rdiscount for parsing them to HTML. The initial version is incredibly simple. I made a base page that had the roots of an HTML document, add the posts, then close the original tags.

It looked like this:

<DOCTYPE html> <body> <h1>hi underneath here is a blog</h1>

The Ruby code looks like this:

#!usr/bin/ruby require ‘rdiscount’ def main base = File.read(“index.html”) html_docs = `ls posts`.split(“

”).map do |file| markdown = File.read(“posts/#{file}”) RDiscount.new(markdown).to_html end puts base + html_docs + “</body></html>” main

You can make a Gemfile or just make sure rdiscount is installed. Either works, we’re winging it. From here we have the dead basics we need to get things started. We have a doc that if we paste it into a file or direct the STDOUT to a file from running this that will open in a browser and have our posts.

Version 2: An Object Oriented Ruby Solution

Thinking further, I wondered if I could make it a more OO setup.

I have decided that the page will have a couple sections added and a new feature, enabled by this. We want to have an about me that prints as a cool banner and to add a button that will make a spaghetti-filled grilled cheese sandwich become the background. I added a sandwich.jpg file to my directory after finding it.

Let’s start with the about me since it will be on the top.

class AboutMePage attr_reader :html, :colors, :header_size def initialize(phrase, header_size = 2) # I know light blue isn’t a color to the browser in this string form, I just like it there @colors = [“red”, “light blue”, “orange”] @header_size = header_size words = phrase.split(“ “) headered_span_words = create_headered_span_words(words) about_me_page_that_needs_closing_html_and_body_tags = base_doc + buttons + “<div>” + headered_span_words + “</div>” # simple api, documenting by name. so clever. lol @html = about_me_page_that_needs_closing_html_and_body_tags end def create_headered_span_words(words) words.map do |word| chars = word.split(“”) spans = chars.map.with_index { |char, i| span_for_character(char, chars, i) }.join “<h#{header_size}>#{spans}</h#{header_size}>” end.join

end def span_for_character(char, chars, index) if index == chars.length — 1 “<span style=’color: #{colors.sample}’>#{char}</br></span>

” else “<span style=’color: #{colors.sample}’>#{char}</span>

” end end def base_doc “<!DOCTYPE html>

<html>

<head>

<meta charset=\”UTF-8\”

</head>” end def buttons “<button onclick=\”document.body.background = ‘’\”>Sandwich Off</button><button onclick=\”document.body.background = ‘sandwich.jpg’\”>Sandwich On</button>

” end end

Let’s start with initialize .

def initialize(phrase, header_size = 2) # I know light blue isn’t a color to the browser in this string form, I just like it there @colors = [“red”, “light blue”, “orange”] @header_size = header_size words = phrase.split(“ “) headered_span_words = create_headered_span_words(words) about_me_page_that_needs_closing_html_and_body_tags = base_doc + buttons + “<div>” + headered_span_words + “</div>” # simple api, documenting by name. so clever. lol @html = about_me_page_that_needs_closing_html_and_body_tags end

@colors = [“red”, “light blue”, “orange”]

We alternate random colors for our banner.

This just sets us up for that.

However, it’s worth noting light blue isn’t a color that will resolve to anything and those letters will be black.

Cuz why not?

@header_size = header_size

This is for us to decide what size of <h> tag to use for our intro.

words = phrase.split(“ “)

Here we split out into words because we’re printing one word per line for our intro.

headered_span_words = create_headered_span_words(words)

This sets up our coloring and spacing for the words we made — more on it later.

about_me_page_that_needs_closing_html_and_body_tags = base_doc + buttons + “<div>” + headered_span_words + “</div>”

This is where we glue it all together, and on the next line set it to an instance var we can read out for whatever purpose we need.

Now let’s take a look at those other functions we call starting from the top.

def create_headered_span_words(words) words.map do |word| chars = word.split(“”)

spans = chars.map.with_index { |char, i| span_for_character(char, chars, i) }.join

“<h#{header_size}>#{spans}</h#{header_size}>”

end.join

end

Here, we map through the words and get their characters. The span_for_character function is coloring them at random and making new tags. We then join them all together. In order to get our buttons for sandwich on/off functionality, we add this:

def buttons “<button onclick=\”document.body.background = ‘’\”>Sandwich Off</button><button onclick=\”document.body.background = ‘sandwich.jpg’\”>Sandwich On</button>

” end

These are self-explanatory. They just shove in the tags.

Next let’s look at the class that we can use to orchestrate the posts along with the about me page.

class OnePageSite def initialize(posts_directory = “/posts”) @posts_directory = posts_directory end def generate post_html = `ls posts`.split(“

”).map { |post| post_to_html(post) }.join(“#{‘</br>’ * 75}

”) html_document = base + “#{‘</br>’ * 75}” + post_html + “</body>

</html>” copy_to_clipboard(html_document) puts html_document html_document end def copy_to_clipboard(document) pbcopy document end def html_posts posts.map { |post| post_to_html(post) } end def post_to_html(post) RDiscount.new(File.read(“posts/#{post}”)).to_html end def posts `ls posts`.split(“

”) end def base AboutMePage.new(“Hi I’m Bobby. I’m a programmer in New York”).html end def pbcopy(text) IO.popen(‘pbcopy’, ‘w’) { |f| f << text } end end

This class’s main logic is in its initialization. Let’s walk through it piece by piece.

def generate post_html = `ls posts`.split(“

”).map { |post| post_to_html(post) }.join(“#{‘</br>’ * 75}

”) html_document = base + “#{‘</br>’ * 75}” + post_html + “</body>

</html>” copy_to_clipboard(html_document) puts html_document html_document end

Here is the first bit:



post_html = `ls posts`.split(“

”).map { |post| post_to_html(post) }.join(“#{‘</br>’ * 75}

”)

Here we convert our posts to markdown just like last time, but this time we add a bunch of line breaks between them.

html_document = base + “#{‘</br>’ * 75}” + post_html + “</body>

</html>”

Now we get the base , which is a function creating the basis of our HTML + About Me section, and combine it with more line breaks before posts, the posts themselves, then the closing body and html tags.

copy_to_clipboard(html_document) puts html_document html_document

Now, we copy the document to the clipboard, puts it out so it’s in STDOUT, and also return the string itself so that it can be manipulated if anyone needs. With this we have a functioning site that makes a page.

It looks like this:

The about me section

The blog

But to summarize, so far we:

generate HTML from ruby classes that may or may not be valid

use a color code that doesn’t exist to keep the default letter color

inlined button functionality to interact with the page, duplicating a lot of logic

complicated what was initially a simple script that did its job

inline every single style

made it so that there’s a sick photo of a spaghetti and mozzarella grilled cheese on garlic bread that can appear

Some (but not all) of what we’ll end up after my mind wanders further *spoilers*

build ruby from source

a fully dockerized setup

build elixir from source

orchestrate with unix on top of docker

horrible ideas that we can’t spoil let

Gotta jibboo — talk soon,

Bobdawg