Octopress: Terminal Tag

Our tutorials website is powered by the open source tool Octopress. Out of the box there are some great tools for rendering code. However, it was missing a number of small features that we needed to assist new developers understand code.

A challenge I saw at a number of the Rails Bridge events and teaching engagements was the difficulty for individuals to differentiate what you wrote in the: terminal; repl; and your editor. Traditional code fences allowed you to specify syntax and a preamble or post paragraph would explain when and where you were to type these things. But ultimately this failed many new developers because the content was not easy to quickly sight read.

To address this issue I created a liquid tag that would output the content to be expressed with terminal style.

Terminal Tag Example

Using A Liquid Tag

I started with a common scenario within many of our tutorials. The instructions that required an individual to install necessary dependencies.

To get started with Rails we need to install the gem. From the terminal type:

```
$ gem install rails
Fetching: i18n-0.6.1.gem (100%)
Fetching: multi_json-1.3.6.gem (100%)
Fetching: activesupport-3.2.8.gem (100%)
...
```

I changed those to the following:

To get started with Rails we need to install the gem. From the terminal type:

{% terminal %}
$ gem install rails
Fetching: i18n-0.6.1.gem (100%)
Fetching: multi_json-1.3.6.gem (100%)
Fetching: activesupport-3.2.8.gem (100%)
...
{% endterminal %}

Octopress immediately will start to complain about the missing tag terminal.

Defining a Liquid Tag

Octopress will load all files within your plugin directory. So we will want to define our liquid tag there. Create a new file named terminal_tag.rb.

We want to register our special tag that we will later define.

Liquid::Template.register_tag('terminal', Jekyll::TerminalTag)

Above this declaration define our new TerminalTag.

module Jekyll
  class TerminalTag < Liquid::Block
    def initialize(tag_name, markup, tokens)
      super
    end

    def render(context)
      output = super(context)
      # do something with the output
      output
    end
  end
end

Here we are more specifically defining a Liquid Block Tag. This defines a start tag (terminal) and end tag (endterminal) and allows us to process the content between the start tag and end tag (e.g. $ gem install rails).

The interface for Liquid::Block that we care about is with our two methods initialize and render.

  • Initialize will receive any additional parameters we might have provided to our tag when it is used. We could use these parameters to customize each terminal. In this example the content will go unused.

  • Render will receive the content between the start tag and end tag and the goal is to convert it from the text content into the the correct HTML output.

Content to HTML

Our goal now is to convert the content to the expected HTML.

def render(context)
  output = super(context)
  terminal_window promptize(output)
end

def terminal_window(output)
  %{<div class="window">
      <nav class="control-window">
        <a href="#" class="close">close</a>
        <a href="#" class="minimize">minimize</a>
        <a href="#" class="deactivate">deactivate</a>
      </nav>
      <h1 class="titleInside">Terminal</h1>
      <div class="container">
        <div class="terminal">#{output}</div>
      </div>
    </div>}
end

def promptize(content)
  # TODO: convert the raw input to a table with our commands and output
  content
end

This will have our liquid block render the raw text between the start tag and end tag in our new window.

The promptize method is where we will convert the content between those two tags into formatted HTML that will resemble our terminal output. We have two types of content that appears between our two tags: commands and output.

  • A command is prefaced with a “$”.

  • Output is all content that does not start with a “$”

We want to iterate through each line of the content and determine if it is a command or output. We want to be able to render the content differently, through css classes, based on the category of the content.

To lay the code correctly we are going to use an HTML table. The first column will be the gutter where we will show the terminal prompt character. The second column will be the command we execute or the output from the execution.

Placing the terminal prompt character in a separate table cell make it so when the viewer copies and pastes the code from the terminal window will not copy the command character (e.g. “$”) along with the instructions. For output we will simply fill the gutter with a space character to preserve the table formatting.

def promptize(content)
  lines_of_content = content.strip.lines
  gutters = lines_of_content.map { |line| gutter(line) }
  lines_of_code = lines_of_content.map { |line| line_of_code(line) }

  table = "<table><tr>"
  table += "<td class='gutter'><pre class='line-numbers'>#{gutters.join("\n")}</pre></td>"
  table += "<td class='code'><pre><code>#{lines_of_code.join("")}</code></pre></td>"
  table += "</tr></table>"
end

def command_character
  "$"
end

def gutter(line)
  gutter_value = line.start_with?(command_character) ? command_character : "&nbsp;"
  "<span class='line-number'>#{gutter_value}</span>"
end

def line_of_code(line)
  if line.start_with?(command_character)
    line_class = "command"
    line = line.sub(command_character,'').strip
  else
    line_class = "output"
  end
  "<span class='line #{line_class}'>#{line}</span>"
end

The lines of content are broken into lines, we figure out what should appear in the gutters, what are the lines of code, and finally merge them all together.

Terminal Tag Example

Styling our Window

The end result is not very pretty because our HTML lacks the appropriate styles.

Copy the following CSS stylesheet and add it to your existing stylesheets or create a new stylesheet for it.

Terminal Tag Example

Conclusion

Developers are using Octopress to deliver information to other developers. It i important that we consider those other developers when creating our content and how that content is displayed. Defining a custom liquid tag helps you represent your content in a way that is easy to understand.

The following gist contains all the content you need to start using this liquid tag within your Octopress or Jekyll site.

comments powered by Disqus
banner_sidebar

Upcoming Events

  • GoGaRuCo 2013

    GoGaRuCo 2013

    Mission Bay Conference Center

    September 20 & 21 - San Francisco, CA

  • RuPy 2013

    RuPy 2013

    October 11-14 - Budapest, Hungary

Contact Us

Twitter
@jumpstartlab
Github
jumpstartlab
Email
contact@jumpstartlab.com
Phone
(202) 670-2852
Fax
(202) 280–1257
Mail
1510 Blake Street
Denver, CO 80202 U.S.A

Stay Connected

Get the scoop on upcoming classes.