On my WordPress blog I ran a plugin called Download Monitor which allowed me to create download ids that could be inserted via short tags. When I updated a download version, any mention of it throughout the site would be updated to show the latest version and link to the most recent download package. I needed something similar on my Jekyll blog to keep things up to date. The following system is geared toward Jekyll but the concept could be adapted to any static blog.
I started by using a script that looped through all of the downloads listed in my WordPress database and generated a CSV file of all of my downloads and their associated metadata. For example:
id,title,file,version,description,info,icon,updated
1,Clippable to Evernote Service,/share/clippable-to-evernote.zip,1.0,"A Snow Leopard System Service [...]",http://brettterpstra.com/code/clippable-to-evernote-service/,/images/serviceicon.jpg,Tue Feb 23 21:55:00 -0600 2010
This file is editable in a plain text editor or in a spreadsheet app like Numbers, and doesn’t require an existing WordPress database to start, just a plain old CSV file with the appropriate data.
Next, I just needed a tag plugin that would let me create a tag like:
{% download 59 %}
The tag would insert a templated “download card” with the appropriate data:
The current tag plugin I’m using has the template hardcoded and needs to be updated to handle an actual external template file, but it should give you the idea:
# Title: Download tag for Jekyll# Author: Brett Terpstra http://brettterpstra.com# Description: Create updateable download cards for downloadable projects. Reads from a downloads.csv file in the root folder## Example: {% download id %} to read download [id] from the CSVmoduleJekyllclassDownloadTag<Liquid::Tagrequire'csv'@title=nil@file=''@info=''@icon=nil@template='link'definitialize(tag_name,markup,tokens)ifmarkup=~/^(\d+)( .*)?$/req_id=$1.strip@format=$2.strip.to_iunless$2.nil?CSV.read("downloads.csv").eachdo|line|ifline[0]==req_idid,title,file,version,description,info,icon,updated=line@file=file@version=version==''?'':%Q{ v#{version}}@title=%Q{#{title}#{@version}}@icon=icon==''?'':%Q{<img src="#{icon}" width="100" height="100">}@updated=updated==''?'':%Q{<p class="dl_updated">Updated #{updated.sub(/[\d:]+ -\d+ (\d+)/,"\\1").strip}.</p>}@description=description==''?'':%Q{<p class="dl_description">#{description}</p>#{@updated}}@info=info==''?'':%Q{<p class="dl_info"><a href="#{info}" title="More information on #{title}">More info…</a></p>}breakendendendsuperend# TODO: Make this read templates# Example:# Dir.chdir(includes_dir) do# choices = Dir['**/*'].reject { |x| File.symlink?(x) }# if choices.include?(file)# source = File.read(file)# partial = Liquid::Template.parse(source)# context.stack do# rtn = rtn + partial.render(context)# end# else# rtn = rtn + "Included file '#{file}' not found in _includes directory"# end# enddefrender(context)output=super# TODO: Enable template selectionif@titledownload=%Q{<div class="download"><h4>#{@title}</h4><p class="dl_icon"><a href="#{@file}" title="Download #{@title}">#{@icon}</a></p><div class="dl_body"><p class="dl_link"><a href="#{@file}">Download #{@title}</a></p>#{@description}#{@info}</div></div>}else"Error processing input, expected syntax: {% download title filename [url/to/related/post] %}"endendendendLiquid::Template.register_tag('download',Jekyll::DownloadTag)
Updating a download’s row in the CSV file with a new download link, version number, update time and/or description change will recreate all of the references to it in the site next time I build it.
I also added a Rake task for searching my downloads and finding the ID for use in the tag:
desc"Find a download ID"task:find_download,:termdo|t,args|raise"### You haven't created a download csv yet."unlessFile.exists?('downloads.csv')results=CSV.read("downloads.csv").delete_if{|row|row[0].strip=~/^\d+$/&&row[1]+" "+row[4]=~/.*#{args.term}.*/i?false:true}results.sort!{|a,b|a[0].to_i<=>b[0].to_i}.map!{|res|res[0]+": "+res[1]+" v"+res[3]}results_menu(results,"download")print("Select download")whileline=Readline.readline(": ",true)if!line||line=~/^[a-z]/iputs"## Canceled"Process.exit0endline=line.to_iif(line>0&&line<=results.length)id=results[line.to_i-1].match(/^\d+/)[0]download_tag="{% download #{id} %}"%x{echo "#{download_tag}\\c"| pbcopy}puts%Q{Download tag in clipboard: "#{download_tag}"}Process.exit0elseputs"## Selection out of range"Process.exit0endendend# creates a user menu from a hash or arraydefresults_menu(res,type="file")counter=1putsres.reverse!res.eachdo|match|match=match.class==String?match:match[:path]iftype=="file"display=match.sub(/^.*?\/([^\/]+)$/,"\\1")display.gsub!(/^[\d-]+/,'')display.gsub!(/\.(md|markdown)$/,'')display.gsub!(/-/,' ')elsiftype=="download"display=match.sub(/^\d+:\s*/,'')elsedisplay=match.stripendprintf("%2d ) %s\n",counter,display)counter+=1endputsend
Now typing rake find_download[nvalt] will show me all downloads matching the search term “nvalt,” offer a menu of matches and put the complete Liquid tag for the selected result in my clipboard:
I also plan to add a Rake task to make adding new downloads and updating existing versions from the command line as simple as possible.
With a little modification, this system could easily be used to generate a “Downloads” page for my site, though I’ve decided that’s really not necessary. I may change my mind in the future, though.
Todo
What I’ve shared here is a functioning system that is currently in use. Before it’s “complete” and ready to share in my plugins repo, there are a few things I’d like to polish:
A template system
reads a template name from the tag
works with multiple templates
Rake tasks for adding and editing downloads more easily
script for adding downloads via Service or droplet
(Possibly) a plugin for generating a Downloads page