Stale Bread (It was time for PicsOfBread 3.0)

The answer was yes.

Like I mentioned in another recent post, PicsOfBread was in dire need of an update to the site's backend. (Middle-end? I dunno, it's static.) So I implemented a new generator. Best part? It only turned out to be about 150 lines of Ruby.

How I did it (or why Ruby is quite nice for DSL-like things)

Ruby's simple and flexible syntax actually works out pretty well if you want to make something that looks like a DSL without having to actually do any DSL-type-stuff. Any function call in Ruby can take 2 forms:

function(args)

or

function args

This second form is key for making a not-really-DSL. That's what takes our declaration code from looking like this:

posts.each do |date, post|
    unless post[:is_draft]
        empty_dir(post[:short_name])
        new_page("%s/index.html" % post[:short_name], post[:title])
            title(generate_nice_title)
            content(post[:html_content])
        end_page
    end
end

To this:

posts.each do |date, post|
    unless post[:is_draft]
        empty_dir post[:short_name]
        new_page "%s/index.html" % post[:short_name], post[:title]
            title generate_nice_title
            content post[:html_content]
        end_page
    end
end

See? It already looks and feels much nicer. Like a "real" "programming" "language". If we want to accomplish this effect, it's as easy as declaring some ugly state variables:

$__current_page_f = nil
$__current_page_t = ""
$__current_page_nice_name = ""
$__current_section = ""

$md = Redcarpet::Markdown.new(Redcarpet::Render::HTML, superscript: true, strikethrough: true, lax_spacing: true, fenced_code_blocks: true)
$md_stripped = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)

And some ugly functions that manipulate that state:

def new_page(page_file, page_nice_name)
    $__current_page_f = File.open($SITE_DEST + page_file, "w")
    $__current_page_t = $page_base.dup
    $__current_page_nice_name = page_nice_name
end

def end_page()
    $__current_page_f.write $__current_page_t
    $__current_page_f.close
end

def title(title)
    $__current_page_t.gsub! '!(PG_TITLE)!', title
end

def content(content)
    $__current_page_t.gsub! '!(PG_CONTENT)!', '<div id="content">%s</div>' % content
end

def nocontent()
    $__current_page_t.gsub! '!(PG_CONTENT)!', ''
end

There are some more functions that could be added for additional "keywords", but you don't need a whole lot more to be able to describe the entirety of PicsOfBread with about 40 lines of Ruby our DSL:

empty_dir "dest/"
new_site "PicsOfBread.com", "dest/"

new_page "index.html", "Home"
    title $SITE_NAME
    nocontent
end_page

posts = iterate_group("posts/").sort.reverse.to_h
pages = iterate_group("pages/").sort.reverse.to_h

empty_dir "posts/"
new_page "posts/index.html", "Blog"
    title generate_nice_title
    posts_list = ""

    posts_list = "<div>"
    posts.each do |date, post|
        unless post[:is_draft]
            posts_list += "<div><h1>#{date}</h1>"
            posts_list += "<h3>#{post[:title]}</h3><p>#{post[:preview_content]}</p><a href=!(PG_CONTENT)!quot;/#{post[:short_name]}!(PG_CONTENT)!quot;>Read more...</a></div>"
        end
    end
    posts_list += "</div>"

    content posts_list
end_page

posts.each do |date, post|
    unless post[:is_draft]
        empty_dir post[:short_name]
        new_page "%s/index.html" % post[:short_name], post[:title]
            title generate_nice_title
            content post[:html_content]
        end_page
    end
end

pages.each do |date, page|
    unless page[:is_draft]
        empty_dir "posts/%s" % page[:short_name]
        new_page "posts/%s/index.html" % page[:short_name], page[:title]
            title generate_nice_title
            content page[:html_content]
        end_page
    end
end

site_finished

Appendix: the full script

If you're curious, the entire script looks like this:

#!/usr/bin/env ruby

require 'fileutils'
require 'redcarpet'
require 'redcarpet/render_strip'

$page_base = File.read("pagebase.html")
$SITE_NAME = ""
$SITE_DEST = ""


$__current_page_f = nil
$__current_page_t = ""
$__current_page_nice_name = ""
$__current_section = ""

$md = Redcarpet::Markdown.new(Redcarpet::Render::HTML, superscript: true, strikethrough: true, lax_spacing: true, fenced_code_blocks: true)
$md_stripped = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)

# FUNDAMENTAL

def new_site(site_name, site_dest)
    $SITE_NAME = site_name
    $SITE_DEST = site_dest
end

def new_page(page_file, page_nice_name)
    $__current_page_f = File.open($SITE_DEST + page_file, "w")
    $__current_page_t = $page_base.dup
    $__current_page_nice_name = page_nice_name
end

def end_page()
    $__current_page_f.write $__current_page_t
    $__current_page_f.close
end

def title(title)
    $__current_page_t.gsub! '!(PG_TITLE)!', title
end

def content(content)
    $__current_page_t.gsub! '!(PG_CONTENT)!', '<div id="content">%s</div>' % content
end

def nocontent()
    $__current_page_t.gsub! '!(PG_CONTENT)!', ''
end

def site_finished()
    FileUtils.cp_r "res/.", "dest"
end

def empty_dir(dirname)
    FileUtils.mkdir_p("#{$SITE_DEST}#{dirname}")
end

# END FUNDAMENTAL
# HANDY

def generate_nice_title
    return "%s - %s" % [$SITE_NAME, $__current_page_nice_name]
end

def shorten(string, count)
    return string.match(/^.{0,#{count}}\b/)[0] + "..."
end  

# END HANDY


def iterate_group(dir)
    group = {}
    Dir.foreach(dir) do |post|
        unless File.directory? post
            post_md = File.read(dir + post)

            title_re = /(?<=title: !(PG_CONTENT)!quot;).*(?=!(PG_CONTENT)!quot;)/
            date_re = /(?<=date: ).*/
            draft_re = /(?<=draft: ).*/

            post_title = title_re.match(post_md)[0]
            post_date = date_re.match(post_md)[0].split("T")[0]
            post_is_draft = draft_re.match(post_md)[0] == 'true'

            post_md = post_md.split("
Mastodon