Octopress

Notable Octopress tweaks: Copyright, Tags, & Feed Excerpts

October 28, 2013 · 8 min read

This is almost pointless to mention but the standard templates give you a very specific copyright with respect to atom feeds (Copyright (c) x-y). The file source\_includes\custom\footer.html includes a way of gathering the system time in the form:

Copyright © {{ site.time | date: "%Y" }}

I simply replaced the current hard-coded year with the ruby code above so that when the site is generated, it always gives the current year.

In case any of you are wondering how to insert code blocks with liquid syntax, see this post.

Tags

This is more involved but luckily gist makes it stupid simple to include here. Everything is pretty much a rip off of the category_generator plugin and includes with just minor tweaks to use the tag object.

_config.yml

tag_dir: tags
view raw _config.yml hosted with ❤ by GitHub

plugins/tag_generator.rb (an almost complete copy of category_generator.rb)

# encoding: utf-8
#
# Jekyll tag page generator.
# Completely stolen from the category generator, where I copied the file and replaced text here:
# http://recursive-design.com/projects/jekyll-plugins/
#
# Version: 0.1.4 (201101061053)
#
# Copyright (c) 2010 Dave Perrett, http://recursive-design.com/
# Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
#
# A generator that creates tag pages for jekyll sites.
#
# Included filters :
# - tag_links: Outputs the list of tags as comma-separated <a> links.
# - date_to_html_string: Outputs the post.date as formatted html, with hooks for CSS styling.
#
# Available _config.yml settings :
# - tag_dir: The subfolder to build tag pages in (default is 'tags').
# - tag_title_prefix: The string used before the tag name in the page title (default is
# 'Tag: ').
require 'stringex'
module Jekyll
# The TagIndex class creates a single tag page for the specified tag.
class TagIndex < Page
# Initializes a new TagIndex.
#
# +base+ is the String path to the <source>.
# +tag_dir+ is the String path between <source> and the tag folder.
# +tag+ is the tag currently being processed.
def initialize(site, base, tag_dir, tag)
@site = site
@base = base
@dir = tag_dir
@name = 'index.html'
self.process(@name)
# Read the YAML data from the layout page.
self.read_yaml(File.join(base, '_layouts'), 'tag_index.html')
self.data['tag'] = tag
# Set the title for this page.
title_prefix = site.config['tag_title_prefix'] || 'Tag: '
self.data['title'] = "#{title_prefix}#{tag}"
# Set the meta-description for this page.
meta_description_prefix = site.config['tag_meta_description_prefix'] || 'Tag: '
self.data['description'] = "#{meta_description_prefix}#{tag}"
end
end
# The TagFeed class creates an Atom feed for the specified tag.
class TagFeed < Page
# Initializes a new TagFeed.
#
# +base+ is the String path to the <source>.
# +tag_dir+ is the String path between <source> and the tag folder.
# +tag+ is the tag currently being processed.
def initialize(site, base, tag_dir, tag)
@site = site
@base = base
@dir = tag_dir
@name = 'atom.xml'
self.process(@name)
# Read the YAML data from the layout page.
self.read_yaml(File.join(base, '_includes/custom'), 'tag_feed.xml')
self.data['tag'] = tag
# Set the title for this page.
title_prefix = site.config['tag_title_prefix'] || 'Tag: '
self.data['title'] = "#{title_prefix}#{tag}"
# Set the meta-description for this page.
meta_description_prefix = site.config['tag_meta_description_prefix'] || 'Tag: '
self.data['description'] = "#{meta_description_prefix}#{tag}"
# Set the correct feed URL.
self.data['feed_url'] = "#{tag_dir}/#{name}"
end
end
# The Site class is a built-in Jekyll class with access to global site config information.
class Site
# Creates an instance of TagIndex for each tag page, renders it, and
# writes the output to a file.
#
# +tag_dir+ is the String path to the tag folder.
# +tag+ is the tag currently being processed.
def write_tag_index(tag_dir, tag)
index = TagIndex.new(self, self.source, tag_dir, tag)
index.render(self.layouts, site_payload)
index.write(self.dest)
# Record the fact that this page has been added, otherwise Site::cleanup will remove it.
self.pages << index
# Create an Atom-feed for each index.
feed = TagFeed.new(self, self.source, tag_dir, tag)
feed.render(self.layouts, site_payload)
feed.write(self.dest)
# Record the fact that this page has been added, otherwise Site::cleanup will remove it.
self.pages << feed
end
# Loops through the list of tag pages and processes each one.
def write_tag_indexes
if self.layouts.key? 'tag_index'
dir = self.config['tag_dir'] || 'tags'
self.tags.keys.each do |tag|
self.write_tag_index(File.join(dir, tag.to_url), tag)
end
# Throw an exception if the layout couldn't be found.
else
raise <<-ERR
===============================================
Error for tag_generator.rb plugin
-----------------------------------------------
No 'tag_index.hmtl' in source/_layouts/
Perhaps you haven't installed a theme yet.
===============================================
ERR
end
end
end
# Jekyll hook - the generate method is called by jekyll, and generates all of the tag pages.
class GenerateTags < Generator
safe true
priority :low
def generate(site)
site.write_tag_indexes
end
end
# Adds some extra filters used during the tag creation process.
module Filters
# Outputs a list of tags as comma-separated <a> links. This is used
# to output the tag list for each post on a tag page.
#
# +tags+ is the list of tags to format.
#
# Returns string
#
def tag_links(tags)
tags = tags.sort!.map { |c| tag_link c }
case tags.length
when 0
""
when 1
tags[0].to_s
else
"#{tags[0...-1].join(', ')}, #{tags[-1]}"
end
end
# Outputs a single tag as an <a> link.
#
# +tag+ is a tag string to format as an <a> link
#
# Returns string
#
def tag_link(tag)
dir = @context.registers[:site].config['tag_dir']
"<a class='tag' href='/#{dir}/#{tag.to_url}/'>#{tag}</a>"
end
# Outputs the post.date as formatted html, with hooks for CSS styling.
#
# +date+ is the date object to format as HTML.
#
# Returns string
def date_to_html_string(date)
result = '<span class="month">' + date.strftime('%b').upcase + '</span> '
result += date.strftime('<span class="day">%d</span> ')
result += date.strftime('<span class="year">%Y</span> ')
result
end
end
end

source/_includes/archive_post_tags.html

{% capture tag %}{{ post.tags | size }}{% endcapture %}
<div class="row-fluid">
<div class="span1">
<h1 class="date-time"><time datetime="{{ post.date | datetime | date_to_xmlschema }}" pubdate>{{ post.date | date: "<span class='month'>%b</span> <span class='day'>%d</span><span class='year'><!--%Y--></span>"}}</time></h1>
</div>
<div class="span10">
<h1><a href="{{ root_url }}{{ post.url }}">{{post.title}}</a></h1>
</div>
</div>
{% if tag != '0' %}
<div class="row-fluid">
<div class="span1">
</div>
<div class="span10">
<footer class="archive">
<span class="tags">posted in {{ post.tags | tag_links }}</span>
</footer>
</div>
</div>
{% endif %}

source/_includes/custom/tag_feed.xml

---
layout: nil
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text" xml:lang="en"><![CDATA[{{ site.title }}]]></title>
<link type="application/atom+xml" href="{{ site.url }}/{{ page.feed_url }}" rel="self"/>
<link type="text" href="{{ site.url }}/"/>
<updated>{{ site.time | date_to_xmlschema }}</updated>
<id>{{ site.url }}/</id>
<author>
<name><![CDATA[{{ site.author | strip_html }}]]></name>
{% if site.email %}<email><![CDATA[{{ site.email }}]]></email>{% endif %}
</author>
<rights>Copyright (c) 2010-{{ site.time | date_to_string | date: "%Y" }} <![CDATA[{{ site.author | strip_html }}]]></rights>
<generator uri="http://octopress.org/">Octopress</generator>
{% for post in site.tags[page.tag] limit: 5 %}
<entry>
<title type="html"><![CDATA[{{ post.title | cdata_escape }}]]></title>
<link href="{{ site.url }}{{ post.url }}"/>
<updated>{{ post.date | date_to_xmlschema }}</updated>
<id>{{ site.url }}{{ post.id }}</id>
{% if post.has_excerpt %}<summary type="html">{{ post.excerpt | xml_escape }}</summary>{% endif %}
<content type="html"><![CDATA[{{ post.content | expand_urls: site.url | markdownify | cdata_escape }}]]></content>
</entry>
{% endfor %}
</feed>
view raw tag_feed.xml hosted with ❤ by GitHub

source/_includes/post/tags.html

{% capture tag %}{% if post %}{{ post.tags | tag_links | size }}{% else %}{{ page.tags | tag_links | size }}{% endif %}{% endcapture %}
{% unless tag == '0' %}
{% if post %}
{% for tag in post.tags %}
<a class="tag" href="{{ root_url }}/{{ site.tag_dir }}/{{ tag | to_url }}/"><span class="badge">{{ tag }}</span></a>
{% endfor %}
{% else %}
{% for tag in page.tags %}
<a class="tag" href="{{ root_url }}/{{ site.tag_dir }}/{{ tag | to_url }}/"><span class="badge">{{ tag }}</span></a>
{% endfor %}
{% endif %}
{% endunless %}
view raw tags.html hosted with ❤ by GitHub

source/_layouts/tag_index.html

---
layout: page
footer: false
---
<div id="blog-archives" class="tag">
{% for post in site.tags[page.tag] %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
<div class="row-fluid">
<div class="span1">
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% unless year == this_year %}
{% assign year = this_year %}
<h2>{{ year }}</h2>
{% endunless %}
</div>
<div class="span11">
<article>
{% include archive_post_tags.html %}
</article>
</div>
</div>
{% endfor %}
</div>
view raw tag_index.html hosted with ❤ by GitHub

Feed excerpts

The following goes on line 23 in my atom.xml file, or right before the content tag. A better alternative would be to detect an excerpt and only display that or the content, not both.

{% if post.has_excerpt %}<summary type="html">{{ post.excerpt | xml_escape }}</summary>{% endif %}

Octopress site generation on Windows 8.1

October 24, 2013 · 1 min read

Install RailsInstaller from here. I use the version with Rails 3.2.

libcurl

  • Download the latest "Win32 - Generic" libcurl with SSL development release from here (this is the last entry as of 10/23/2013).
  • Unpack the zip file.
  • Copy curl.exe, libcurl.dll and libidn-11.dll to C:\RailsInstaller\bin.
    The reason I copy curl to test the program. It verifies the files are in the expected path and has an added bonus of making sure all the dll dependencies are present. This is how I figured out libidn-11.dll was necessary to include.

SSL certificate setup

  • Download cacert.pem.
  • Place file in C:\RailsInstaller as cacert.pem.
  • Set an Environment Variable in Control Panel with the Variable name: SSL_CERT_FILE and Variable value: C:\RailsInstaller\cacert.pem.
  • To temporarily set the variable for this command prompt session, use set SSL_CERT_FILE=C:\RailsInstaller\cacert.pem.

My blog's notable Octopress tweaks

July 11, 2013 · 1 min read

My plan is for this post to serve as an index to the upcoming posts. Unlike the ASP.NET MVC posts that fizzled (sorry), I need to chronicle these changes in the event I need to dissect them again.

Here's the collection in no particular order:

  • Copyright date - This one is simple and almost not worth noting. I use a copyright date range of [start year]-[current year] and this post simply explains the what and why.
  • Tag generator - Octopress will not support tags. Tag index pages are trivial to implement but I'm also starting to agree with the premise that one taxonomy should really be enough.
  • Feed excerpts - These aren't generally created to my knowledge, or specifically the summary section. The general way to create excerpts is a little awkward but smart.
  • Google Form notification full service sample taken from here.
  • (Upcoming) Category & Tag index generators for the primary /categories/ /tags/ directory. WordPress didn't have this either but it would be a good idea to list all of them in one place.

One thing in the FAQ that particularly bit me during the WordPress conversion was Using Non-ASCII Characters In Your Blog. Most conversion posts cover this but I'm in the habit of always having this as part of my environment just in case.

The primary reason for my approach so far was to be a close representation as possible of the WordPress site to combat 301 redirect woes. Tags will eventually disappear I think but I like the exercise of making sure they're around in full. The platform seems to support "something" so it's fun to see how far I can go with it.

Migration from WordPress to Octopress Complete

July 10, 2013 · 1 min read

In case anyone was watching, I switched the blog from a generic WordPress with a slightly customized theme to octopress. I'm using the excellent Octoflat theme with a few notable tweaks. I reintroduced the search and feed buttons as well as expanded the navigation to allow nested menus. The theme allows for nested menu elements and while 2 levels deep is highly acceptable, I happen to use 3.

I'm writing this post using the excellent MarkPad which serves all of my needs perfectly. You don't need a markdown editor, but having one with live-preview that reinforces the markdown syntax is killer. That I can insert a URL and it automatically format the document accordingly is a small but useful touch.

I plan on posting the various tweaks that make up this blog. While there aren't a ton, I think a few of the concepts are worth expanding on. I don't claim these tweaks are original by any means but I found a lot of ideas needed to be fleshed out a little more to be acceptable for my tastes. Yes, I'm anal.

I expect nothing but great things from this migration though it took roughly a month of quite a few tinker sessions to get things where I wanted it. I blame using Ruby on Windows to be a big culprit but also my n00bness got in the way in some areas. It also took me quite a bit to grok how deployments functioned but now that everything gels, I don't expect any issues.

The site will continue to be a slight work in progress as I button up some of the loose ends. I have a few design tweaks in mind and I'm looking to expand my portfolio with more current projects. Not everything I've done in the past 2 years can be seen unfortunately but there's quite a body of work piling up, waiting to be shown.