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("