Hacking Off

20% Time All the Time

Hacking Conditional Sidebars Into the _s (Underscores) WordPress Theme

Wrote a guide, as the process seemed mildly convoluted.

Here’s how to modify the _s (“underscores”) WordPress theme to support different sidebars that trigger depending on the page being viewed. There are plug-ins for this, but some of them are shady or just break the UI.

(E.g., Custom Sidebars notably made it impossible for me to save widget content, because its donation bar’s shitty CSS prevents users from being able to scroll far enough down to hit the button. Good times!)

Anyway, here’s the run down:

Overview

  1. Edit your functions.php to register more sidebars.

  2. Edit your sidebar.php file to throw in conditionals to determine how you invoke dynamic_sidebar().

  3. Edit the corresponding widgets.

Edit your functions.php to register more sidebars.

functions.php (original from _s)
1
2
3
4
5
6
7
8
9
10
_s's functions.php file initially contains the following:
function themenamegoeshere_widgets_init() {
  register_sidebar( array(
    'name' => __( 'Sidebar', 'themenamegoeshere' ),
    'id' => 'sidebar-1',
    'before_widget' => '<aside id="%1$s" class="widget %2$s">',
    'after_widget' => '</aside>',
    'before_title' => '<h1 class="widget-title">',
    'after_title' => '</h1>',
  ) );

Registering additional sidebars in functions.php is pretty straightforward. Here’s an example of how to register a second sidebar with a semantically meaningful name.

functions.php (modified)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Relevant API documentation:
// http://codex.wordpress.org/Function_Reference/register_sidebars
function themenamegoeshere_widgets_init() {
  register_sidebar( array(
    'name' => __( 'Sidebar', 'themenamegoeshere' ),
    'id' => 'sidebar-1',
    'before_widget' => '<aside id="%1$s" class="widget %2$s">',
    'after_widget' => '</aside>',
    'before_title' => '<h1 class="widget-title">',
    'after_title' => '</h1>',
  ) );
  register_sidebar( array(
    'name' => __( 'Page-Sidebar', 'themenamegoeshere' ),
    'id' => 'sidebar-page',
    'before_widget' => '<aside id="%1$s" class="widget %2$s">',
    'after_widget' => '</aside>',
    'before_title' => '<h1 class="widget-title">',
    'after_title' => '</h1>',
  ) );
}

I was initially tempted to use register_sidebars() instead, but it would lead to a bunch of sidebars named by number (e.g., sidebar-1, sidebar-2, etc.) which would be a maintenance hazard in that the names would lack any sort of semantic meaning, forcing you to look things up. A stitch in time, and all that jazz.

Edit your sidebar.php file to throw in conditionals to determine how you invoke dynamic_sidebar().

sidebar.php (original from _s)
1
2
3
<?php if ( ! dynamic_sidebar( 'sidebar-1' ) ) : ?>
  // ...
<?php endif; // end sidebar widget area ?>

Brief note for the uninitiated: that hideous ‘: … endif’ pairing is a PHP-idiomatic replacement for everyday curly-braces {} when the stuff between braces would have otherwise contained embedded HTML. (Yuck.)

sidebar.php (modified)
1
2
3
4
5
6
7
8
9
// Suppose you want one sidebar for pages and another for absolutely everything else.
if (is_page()) {
  $no_sidebar = !dynamic_sidebar('sidebar-page'); // Attempt showing page sidebar
} else {
  $no_sidebar = !dynamic_sidebar('sidebar-1'); // Fall back to showing generic sidebar
}
if $no_sidebar { ?>
  ...
<?php } ?>

The key here is nesting ifs (or using a case statement) to intelligently exploit WordPress’s conditional tags to only show a sidebar appropriate to the type of content being displayed. Said tags can be used to differentiate between pages, the blog index, categort pages, and so on, all the way down to even the individual post level. You’ve got a lot of control.

Warning

Be really cautious, here. Using things that otherwise seem they’d make sense can blow up in your face. Notably, using !dynamic_sidebar(‘sidebar-a’) || !dynamic_sidebar(‘sidebar-b’) will behave unexpectedly. Swapping &&, in the previous statement, for || does too.

Edit the corresponding widgets.

Open the WordPress admin panel. Appearance -> Widgets has a pretty obvious interface that will let you set up what gets displayed in each widget.

Bam! A little hand-waving later, and you’re good to go.

Octopress 2.0 Default SEO Flaws

Minor, but present gripes:

  1. Canonical tags for Octopress categories and posts are slightly off. Directories (and blog posts) redirect to ending in a slash. The canonical tags produced take the form http://domain/blogdir/categories/category. The trailing slash is missing.

    .themes/classic/source/includes/head.html (DEFAULT)

    {% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% else %}{{ page.url | remove:'index.html' | strip_slash }}{% endif %}{% endcapture %}

    .themes/classic/source/includes/head.html (FIXED)

    {% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% elsif site.permalink contains category_dir %}{{ '/' | page.url | remove:'index.html' | '/' }}{% else %}{{ page.url | remove:'index.html' | '/' }}{% endif %}{% endcapture %}

    Note that, if you specify yaml _config.yml (problem) category_dir: categories as opposed to yaml _config.yml (fixed) category_dir: /categories The canonical tags for categories will be entirely broken, taking the form: http://domain/blogdircategories/category (see: missing slash in the middle of the link). It would be more consistent to have users forego the leading slash, and to add another ’/’ | to the canonical tag for a category URL.

  2. Meta description for the blog index takes the meta-desc of the first post, rather than contents of the description field from _config.yml. Unsure how to change this.

  3. Posts are redirected to a trailing slash. http://domain/dir/post gets redirected to http://domain/dir/post/. This isn’t an SEO matter so much as an aesthetic one. Unsure how to change this.

2 is minor and 3’s a matter of taste, so I haven’t hunted the source of either down. Yet.

Generate Rails Sitemap From Routes

In a nutshell:

  1. add sitemap_generator to your Gemfile
  2. rake sitemap:install in your rails directory
  3. modify config/sitemap.rb to
    1. iterate over routes
    2. filter out bad routes
    3. add each iterated route to the index
  4. (optional) add other sitemaps to the sitemap index (useful if you have Jekyll or Octopress living in public/subdir).
  5. schedule updates with cron or regenerate sitemap upon deploy/push/etc.
Gemfile
1
gem 'sitemap_generator'
RAILSROOT
1
$ rake sitemap:install
RAILSROOT/config/sitemap.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SitemapGenerator::Sitemap.default_host = "http://hackingoff.com"

SitemapGenerator::Sitemap.create do
  routes = Rails.application.routes.routes.map do |route|
    {alias: route.name, path: route.path.spec.to_s, controller: route.defaults[:controller], action: route.defaults[:action]}
  end

  # Set a list of controllers you don't want to generate routes for.
  # /rails/info in particular maps to something inaccessible.
  # redirects have a nil controller. This prevents duplicate content penalties.
  banned_controllers = ["rails/info", nil]
  routes.reject! {|route| banned_controllers.include?(route[:controller])}

  # sitemap_generator includes root by default; prevent duplication
  routes.reject! {|route| route[:path] == '/'}

  routes.each {|route| add route[:path][0..-11]} # Strips off '(.:format)

  # Notice the below if you're hosting Jekyll/Octopress in a subdirectory
  # or otherwise want to index content outside of Rails' routes.
  # add_to_index '/path/sitemap.xml'

Booyah. Better living through technology. For versions of rails older than 3.2, check out http://stackoverflow.com/a/10138878/1296445 and adapt the routes assignment accordingly.

For information about automatically regenerating the sitemap and pinging search engines via cron or upon deploy, read the sitemap generator gem’s documentation. In my case, a post-receive hook for git fit the bill well enough.

Jekyll (Octopress) in a Subdirectory: Removing Redundant /blog Path to Archives and Categories

When Octopress lives in /blog, the extra blog subdirectory attached to categories and archives results in a redundant “/blog/blog/resource” path. Here’s how to fix that.

_config.yml
1
2
3
# category_dir: blog/categories # Responsible for redundant path.
# Change to:
category_dir: categories
source/_includes/custom/navigation.html
1
2
3
<li><a href="/blog/archives">Archives</a></li>
<!-- change to -->
<li><a href="/archives">Archives</a></li>

Finally, in the source directory, move the /blogs/archive/index.html file to /archive/index.html. (Erase the empty blog folder, too.)

Also, in case you want the massive site-name header to link to your domain root, instead of the blog here’s what you do.

source/_includes/custom/header.html
1
2
3
  <h1><a href="/">Hacking Off</a></h1>
<!-- change to -->
  <h1><a href="http://yourdomain/">Hacking Off</a></h1>

I don’t especially like this fix. It seems like something that should be handled with the “root” entry in config.yml. Changing “root: /blog” to “root: /” in config.yml doesn’t do the trick, and only serves to screw up the deployment.

How to Make Ruby-Graphviz Draw Left-to-Right

1
2
3
4
5
require 'graphviz'
# ...
graph = GraphViz::new
graph[:rankdir] = "LR"
# ...

The default (top-to-bottom) behavior is accomplished via using “TB” instead of “LR”, or by just never specifying graph[:rankdir].

For a more in-depth example, see sample 35 from Ruby-Graphviz’s examples on GitHub.

Features undocumented by Ruby-Graphviz can occasionally be accessed. Just experiment with stuff from the Node, Edge, and Graph Attributes list on GraphViz’s site.

EBNF to NFA for LR(0) and SLR(1) Grammars

This post is still somewhat incomplete.

LR(0) and SLR(1) grammars produce identical non-deterministic finite automata (NFA), and consequently, the same deterministic finite automata (DFA). However, they don’t produce the same tables.

Here’s how to build them.

Squashing a Rails Cache Error Bug

I’m probably not the only one who has or will run into this, especially if others are running a new (read: >=3.x) version of Ruby on Rails, using RVM, on Dreamhost, and were following some outdated instructions from the Dreamhost Wiki on getting Rails 3 up and running.

The Active Support Cache Error Message

1
2
3
4
Exiting
/home/yourusername/.rvm/gems/ruby-1.9.2-p290/gems/activesupport-3.2.1/lib/active_support/cache.rb:65:in `rescue in lookup_store': Could not find cache store adapter for file_store (no such file to load -- active_support/cache/file_store) (RuntimeError)
  from /home/yourusername/.rvm/gems/ruby-1.9.2-p290/gems/activesupport-3.2.1/lib/active_support/cache.rb:62:in `lookup_store'
  from /home/yourusername/.rvm/gems/ruby-1.9.2-p290/gems/railties-3.2.1/lib/rails/application/bootstrap.rb:54:in `block (2 levels) in <module:Bootstrap>'

Solution after the jump.

Finite Automata-zilla: Determinization Explosion

Exploring fun and horrible ways in which non-deterministic automata (NFA) determinization can go wrong. We consider ways to make automata, search for a worst case, and then make inductive leaps. All without the sanity-checking of a textbook or good research. What could possibly go wrong?