APR 30

Because I’m a) lazy and b) a huge nerd, I added support for drafts to this blog, which runs on Webby. It’s fairly straightforward, requiring two new tasks in blog.rake and of course, the draft.erb template. As always, the latest code can be found at my blog’s github repo.

In blog.rake:

desc 'Create a new draft'
task 'draft' do |t|
  page, title, dir = Webby::Builder.new_page_info
  title = Webby.site.args.raw[0] # undo the titlecasing

  raise "Don't specify a directory for a blog post!" unless dir.empty?

  page = File.join(Webby.site.draft_dir, File.basename(page))
  page = Webby::Builder.create(page,
                               :from => File.join(Webby.site.template_dir, 'blog', 'draft.erb'),
                               :locals => {
                                  :title => title,
                                  :directory => Webby.site.draft_dir })
  Webby.exec_editor(page)
end

desc 'Publish a draft'
task 'publish_draft' do |t|
  site = Webby.site
  draft = site.args.raw[0]
  drafts = Dir["#{site.content_dir}/#{site.draft_dir}/*#{draft}*.txt"]

  raise "No drafts matching '#{draft}'" if drafts.empty?

  raise "Found multiple drafts matching '#{draft}': #{drafts.map {|d| File.basename(d) }.join(', ')}" if drafts.size > 1

  # drafts.size == 1
  draft = drafts[0]
  draft = Webby::Resources::Page.new(draft)

  now = Time.now
  year = now.strftime('%Y')
  month = now.strftime('%m')
  day = now.strftime('%d')

  dir = File.join(Webby.site.blog_dir, year, month, day)
  page = File.join(dir, draft.name)

  page = Webby::Builder.create(page,
                               :from => File.join(Webby.site.template_dir, 'blog', 'post.erb'),
                               :locals => {
                                  :title => draft._meta_data['title'],
                                  :directory => dir,
                                  :created_at => now.to_y,
                                  :body => draft._read })
  Logging::Logger['Webby'].info "deleting #{draft.path}"
  FileUtils.rm(draft.path)
end

This does require the modifications to the create_year_index and create_month_index tasks I made, but should be relatively straightforward to port back into the blog.rake file included with Webby’s blog template.

To use this, I create draft posts using webby blog:draft "Some draft title" and when I’m ready to actually post the article, webby blog:publish_draft title. (The argument for publish_draft is used in a regex to locate the draft to publish.)

APR 27

The code for making a sitemap in Webby:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<%
  @pages.find(:all,
              :in_directory => '',
              :extension => 'html',
              :recursive => true,
              :draft => nil).each do |page|
-%>
<url>
  <loc><%= Webby.site.base %><%= page.url %></loc>
  <lastmod><%= page.mtime.strftime('%Y-%m-%d') %></lastmod>
  <priority><%= page.blog_post ? 1 : 0.8 %></priority>
</url>
<% end -%>
</urlset>

APR 19

In the process of moving this blog from Typo to Webby, it quickly became obvious that copying the blog posts over by hand was not an option. Searching on Google led me to a task for importing the posts using an RSS feed, but since I used an SQLite3 database with Typo, I thought it would be easier to just grab the posts directly from the database file.

The bulk of the changes are in this changelist on GitHub, but I made a few more modifications to the process that are included in later changelists.

tasks/blog.rake differs mainly in that it uses the actual database instead of an RSS feed and has regular expressions to convert my Typo code highlighting to use Webby’s UltraVioletHelper. The code is also cleaned up a bit, to use Rake arguments instead of passing around the created_at time as an ENV variable and also to avoid copying Webby::Apps::Main#capture_command_line_args. Changing the database source is left as an exercise for the reader.

desc "Import blog posts"
task :import_posts, :dbfile do |t,args|
  require 'active_record'

  ActiveRecord::Base.establish_connection(
    :adapter => 'sqlite3',
    :dbfile => args.dbfile
  )

  class Content < ActiveRecord::Base; end
  class Page < Content; end
  class Article < Content; end

  Article.find(:all, :conditions => { :published => true }).each do |post|
    published_at = post.published_at

    page = ::Webby::Resources.basename(post.title).to_url
    title = post.title
    dir = "#{Webby.site.blog_dir}/#{published_at.strftime('%Y/%m/%d')}"

    ::Webby.site.args = OpenStruct.new(:raw => [title], :page => page, :title => title, :dir => '')

    year = published_at.strftime('%Y')
    month = published_at.strftime('%m')

    Rake::Task['blog:create_year_index'].execute(Rake::TaskArguments.new([:year], [year]))
    Rake::Task['blog:create_month_index'].execute(Rake::TaskArguments.new([:year, :month], [year, month]))

    page = File.join(dir, File.basename(page))

    # Colons in the title isn't correct YAML
    title = "\"#{title}\"" if title =~ /:/

    body = post.body

    # Convert the Ultraviolet textfilter blocks
    body.gsub!(/<typo:ultraviolet(.*?)>/, '<% uv\1 do -%>')
    body.gsub!('</typo:ultraviolet>', '<% end -%>')
    body.gsub!(/<% uv (.*) do -%>/) do
      s = $1.split(' ').map {|i| i.gsub(/(.*)="(.*)"/) { ":#$1 => '#$2'" }}.join(', ')
      s.sub!('linenumber', 'line_numbers')
      "<% uv #{s} do -%>"
    end

    # Remove extra carriage returns from the SQLite3 string.
    body.gsub!("\r", '')

    Webby::Builder.create(page,
                          :from => File.join(Webby.site.template_dir, 'blog', 'post.erb'),
                          :locals => {
                             :title => title,
                             :directory => dir,
                             :body => body,
                             :created_at => published_at.to_y })
  end
end

You’ll have to of course add the <%= body %> tag in post.erb.

Adding arguments to the year and month indices isn’t too complicated. At the same time, I disabled the dirty flag for the indices created by the imported posts, as they don’t need to be re-rendered more than once.

task :create_year_index, :year do |t,args|
  args.with_defaults(:year => Time.now.strftime('%Y'))
  year = args.year

... snip ...

    Webby::Builder.create(fn, :from => tmpl,
        :locals => {:title => year, :directory => dir, :dirty => (year == Time.now.strftime('%Y'))})
  end
end
task :create_month_index, :year, :month do |t,args|
  args.with_defaults(:year => Time.now.strftime('%Y'), :month => Time.now.strftime('%m'))
  year = args.year
  month = args.month

... snip ...

    Webby::Builder.create(fn, :from => tmpl,
        :locals => {:title => month, :directory => dir, :dirty => ("#{year}.#{month}" == Time.now.strftime('%Y.%m'))})
  end
end

The month.erb and year.erb templates also need to be changed to support disabling the dirty flag:

title:      <%= title %>
created_at: <%= Time.now.to_y %>
filter:     erb
<%= 'dirty:      true' if dirty %>

Finally, running webby blog:import_posts[typo.db] finishes the import process, populating the content directory with all of the Typo posts.

Edit (2009.04.20): Although this blog post is still technically correct, it’s probably better just to grab the latest versions of my tasks and templates from my github repo. I’ve cleaned up the tasks a bit and added arguments to the blog:post task to specify the date.