(Accidentally) Implementing the World’s Worst Static Site Generator: Part 1

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(“\n”).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>\n”
    else
      “<span style=’color: #{colors.sample}’>#{char}</span>\n”
    end
  end
  def base_doc
    “<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\”UTF-8\”\n</head>”
  end
  def buttons
    “<button onclick=\”document.body.background = ‘’\”>Sandwich Off</button><button onclick=\”document.body.background = ‘sandwich.jpg’\”>Sandwich On</button>\n”
  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>\n”
  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(“\n”).map { |post| post_to_html(post) }.join(“#{‘</br>’ * 75}\n”)
    html_document = base + “#{‘</br>’ * 75}” + post_html + “</body>\n</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(“\n”)
  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(“\n”).map { |post| post_to_html(post) }.join(“#{‘</br>’ * 75}\n”)
    html_document = base + “#{‘</br>’ * 75}” + post_html + “</body>\n</html>”
    copy_to_clipboard(html_document)
    puts html_document
    html_document
  end

Here is the first bit:


post_html = `ls posts`.split(“\n”).map { |post| post_to_html(post) }.join(“#{‘</br>’ * 75}\n”)

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>\n</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


Thanks for reading! Wanna work on a mission-driven team that loves farcical thought exercises and Phish? We’re hiring!


Footer top

To learn more about Flatiron School, visit the website, follow us on Facebook and Twitter, and visit us at upcoming events near you.

Flatiron School is a proud member of the WeWork family. Check out our sister technology blogs WeWork Technology and Making Meetup.

Footer bottom