# This is my kitchen. In it, we have kitchen_appliances, and
# ingredients. kitchen_appliances are methods that take one or more
# arguments. The first argument is the ingredient that it works on.
#
# Ingredients are also methods. When you define them, you given them
# a name and some options of how the ingredient is made.
begin
require 'rubygems'
require 'erb'
require 'optparse'
require 'rdiscount'
rescue LoadError => e
puts "Missing dependency #{e.message}"
exit 1
end
KITCHEN = {
:appliances => {},
:ingredients => {},
}
# some utility methods from Rails I like
module HashExtensions
def reverse_merge(other_hash)
other_hash.merge(self)
end
def reverse_merge!(other_hash)
replace(other_hash.merge(self))
end
def [](key)
key.respond_to?(:to_sym) ? super[key.to_sym] : super
end
def []=(key, val)
key.respond_to?(:to_sym) ? (super[key.to_sym] = val) : super
end
def symbolize_keys!
keys.each { |k| self[k.to_sym] = delete(k) if k.respond_to?(:to_sym) }
end
end
Hash.send(:include, HashExtensions)
def markdown_file(filename)
RDiscount.new(File.read(filename)).to_html
end
def markdown(string)
RDiscount.new(string).to_html
end
# register a new 'ingredient' in KITCHEN[:ingredients]
#
# ingredient_name - string or symbol to reference this ingredient by.
# Must be unique across all ingredients
# options - Hash of optional arguments keyed by symbols.
# :regex - a string pattern that is compatible with the -regex flag in gnu find.
def ingredient(ingredient_name, options = {})
KITCHEN[:ingredients][ingredient_name] = Proc.new {
options.reverse_merge!({ :regex => '.*',
:search_paths => ['.']
})
filenames = options[:files]
filenames = `find #{options[:search_paths].join(' ')} -regex '#{options[:regex]}' -print0`.split("\0") if filenames.nil?
filenames.each { |f| yield f } if block_given?
filenames
}
end
def kitchen_appliance(appliance_name, &proc)
# check that proc takes at least one argument
if proc.arity < 1
raise ArgumentError.new("kitchen_appliance block must take at least 1 argument")
end
# register proc in our kitchen
KITCHEN[:appliances][appliance_name] = proc
end
def cook(ingredient_name, options = {})
raise ArgumentError.new(":with required") unless options[:with]
unless KITCHEN[:ingredients].has_key?(ingredient_name)
raise ArgumentError.new("unknown ingredient #{ingredient_name}")
end
unless KITCHEN[:appliances].has_key?(options[:with])
raise ArgumentError.new("unknown kitchen_appliance #{options[:with]}")
end
KITCHEN[:appliances][options[:with]].call(KITCHEN[:ingredients][ingredient_name].call)
end
def describe(ingredient_or_appliance_name)
ingredient_or_appliance = KITCHEN[:ingredients][ingredient_or_appliance_name] || KITCHEN[:appliances][ingredient_or_appliance_name]
puts "Sorry, don't know about #{ingredient_or_appliance_name}" unless ingredient_or_appliance
end
def show_ingredient(ingredient_name)
ingredient = KITCHEN[:ingredients][ingredient_name]
puts "Sorry, don't know about #{ingredient_name}" unless ingredient
ingredient.call
end
# Configuration #
config = YAML.load(File.read('flaco.yml'))
config.symbolize_keys!
config.reverse_merge!({ :last_published => File.exist?('.last_published') ? File.mtime('.last_published') : Time.at(0),
})
# Ingredients #
ingredient(:static_pages, :files => ['missing.text', 'README.text'])
ingredient(:all_articles,
:regex => '.*text',
:search_paths => ['./articles'])
ingredient(:all_blurbs,
:regex => '.*\/blurb\.tmp',
:search_paths => ['./articles'])
ingredient(:latest_articles,
:regex => '.*text',
:search_paths => ['./articles'],
:modified_since => config[:last_published])
ingredient(:emacs_backups, :regex => '.*~')
# Kitchen Appliances #
kitchen_appliance(:static_page_maker) { |ingredients|
stylesheets = ['main']
ingredients.each do |i|
puts "static_page_maker: #{i}"
title = config[:blog_title]
commentable = false
index_page = false
depth = 0
content_html = markdown_file(i)
output_path = i.sub('.text', '.html')
template = ERB.new(File.read(config[:main_template]))
File.open(output_path, 'w') { |f| f.write(template.result(binding)) }
end
}
kitchen_appliance(:index_page_maker) { |ingredients|
stylesheets = ['main']
# each index page has a next and a prev link except for the first
# and last pages.
# potentially add one extra page that has less than articles_per_page
pages = (ingredients.size / config[:articles_per_page]).ceil
(0...pages).each do |page|
puts "index_page_maker: page #{page+1}"
title = config[:blog_title]
commentable = false
index_page = true
depth = 0
output_path = "./articles/pages/#{page+1}.html"
content_html = ""
(0...config[:articles_per_page]).each do |i|
blurb_path = ingredients[i + (config[:articles_per_page] * page)]
# last page may have less than articles_per_page
break if blurb_path.nil?
content_html << File.read(blurb_path)
end
template = ERB.new(File.read(config[:main_template]))
File.open(output_path, 'w') { |f| f.write(template.result(binding)) }
end
}
kitchen_appliance(:article_maker) { |ingredients|
stylesheets = ['main']
ingredients.each do |i|
puts "article_maker: #{i}"
title = File.open(i) { |f| f.readline }.gsub(/(\s*#)|(#\s*)/, '')
commentable = true
index_page = false
depth = i.count('/') - 1
content_html = markdown_file(i)
output_path = i.sub('.text', '.html')
template = ERB.new(File.read(config[:main_template]))
File.open(output_path, 'w') { |f| f.write(template.result(binding)) }
end
}
# a blurb is the title, a newline, and the first paragraph
kitchen_appliance(:blurb_maker) { |ingredients|
ingredients.each do |i|
puts "blurb_maker: #{i}"
seen_newlines = 0
blurb = ''
File.open(i) { |f|
while seen_newlines <= 1
line = f.readline
blurb << line
seen_newlines += 1 if line =~ /^\s*$/
end
}
content_html = markdown(blurb)
# hyperlink the title
article_url = i.gsub(/\.text$/, ".html")
content_html.gsub!(/<h1>(.*?)<\/h1>/) do |match|
"<h1><a href='#{article_url}'>#{$1}</a></h1>"
end
template = ERB.new(File.read(config[:blurb_template]))
blurb_tmp = File.join(File.dirname(i), 'blurb.tmp')
File.open(blurb_tmp, 'w') { |f| f.write(template.result(binding)) }
end
}
kitchen_appliance(:rm) { |ingredients|
ingredients.each { |i| File.delete(i) }
}
# Recipe #
cook(:static_pages, :with => :static_page_maker)
cook(:latest_articles, :with => :article_maker)
cook(:all_articles, :with => :article_maker)
cook(:all_articles, :with => :blurb_maker)
cook(:all_blurbs, :with => :index_page_maker)
# cleanup tmp files
cook(:all_blurbs, :with => :rm)
cook(:emacs_backups, :with => :rm)
# update the last_published timestamp for next time
`touch .last_published`
# symlink index page to first page
`ln -fs articles/pages/1.html index.html`