Nanoc Plugin for Global Content Captures
I use Nanoc, a Ruby static website generator. It is pretty nice and has all the features I need for generating this site. One feature that was missing, however, was the ability to capture content on one page and use it on others. I manage the archive links as a page and the page content is inserted in the left nav bar using the site's layout template. I also reuse my resume content on the printable version of the page.
After inquiring on the Nanoc mail list, I came up with the following hack to solve my problem. Just add the code at the bottom of the page to a .rb file in your lib directory and then you can use it as follows.
<% global_content_for :foo do %>
This is the FOO stuff.
<% end %>
In any page you want to see the :foo content, just use markup like the following.
<%= global_content_for :foo %>
I wrote in a rudimentary ability to detect that one or more pages has unsatisfied global references. This situation arises pretty much whenever you use nanoc to do an update compile. In this case, nanoc will raise an exception with a message that there are unsatisfied global references. The "workaround" is to ensure that you are recompiling not only the pages that print out the global capture, but also the pages where the global capture is made. Because I have so few pages, it is not a problem for me to just recompile the whole site.
The real solution to this problem is smarter dependency resolution. It should be possible in a page's metadata to mark a page as dependent on some other page. If a page were being recompiled, all it's dependencies would be recompiled as well. If Nanoc can't handle it, maybe one of the other static website generators such as Webby will be able to handle it.
The Code
Add this to a file in your site's lib directory, say lib/global_capturing.rb:
# override some stuff
module Nanoc
#Raised in the event of an unsatisified global capture dependency
class UnsatisfiedGlobalCaptureReference < StandardError
end
class Compiler
attr_accessor :recompiles
alias :old_run :run
def run(objects = nil, params = {})
# Track recursion depth. We allow a depth of no more
# than 2. If greater, we have unsatisfied global capture
# references
(@depth ||= 0)
@depth += 1
if @depth > 2
raise UnsatisfiedGlobalCaptureReference.new("The following pages have unsatisfied global capture references: #{@recompiles.join(', ')}")
end
@recompiles = []
old_run(objects, params)
# Recompile stuff that had a global capture miss
recompile_pages = @recompiles.map do |path|
@site.pages.find do |site_page|
check_path = path.gsub('.html', '/')
site_page.path == check_path
end
end
if recompile_pages.size > 0
run(recompile_pages, :also_layout => params[:also_layout], :even_when_not_outdated => true, :from_scratch => true)
end
end
end
end
module GlobalCapturing
CAPTURES = {}
def global_content_for(name, &block)
if !block.nil?
global_captures[name] = global_capture(&block)
end
if !global_captures.has_key?(name)
# TODO: Is this possible
# Need to somehow signal the compiler that this page should be skipped for now
# HACK: For some reason '==' does not work on @page objects, so use the path as the
# equality check
@site.compiler.recompiles << @page.path
end
global_captures[name]
end
def global_captures
CAPTURES
end
private
def global_capture(*args, &block)
buffer = eval('_erbout', block.binding)
pos = buffer.length
block.call(*args)
data = buffer[pos..-1]
buffer[pos..-1] = ''
data
end
end
include GlobalCapturing