Skip to main content
  1. Posts/

New Day 0 - This looks different

NewDays blog jekyll ruby
Table of Contents

In which we come back, update Jekyll, and start again.


Yes, I got a job and abandoned the blog for quite a while. And now it looks similar but a little different.

Jekyll 4
#

Eager to join the modern era, I grabbed Jekyll 4.2.2, and a nice theme called “Basically Basic”; but then the tweaking began. Looking at the existing blog posts, it was clear that the code blocks and images would need some work. Because the blog was originally built on pre-3.0 Octopress, I went there to get the original plugins for code blocks and images.

Unfortunately Jekyll 4 no longer supports Pygments for syntax highlighting, so I had to convert the code block plugin to use Rouge in order to format it in the same way. Rouge is mostly compatible with Pygments-based post-processing, but the line numbering doesn’t quite match up when you want something like arbitrary beginning line numbers (which I parse out of the link URL and handle myself, as you can see below).

_plugins/code_block.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# Fork of "Simple Code Blocks for Jekyll" https://github.com/imathis/octopress/blob/master/plugins/code_block.rb
require './_plugins/rouge_code'
require './_plugins/raw'

module Jekyll

  class CodeBlock < Liquid::Block
    CaptionUrlTitle = /(\S[\S\s]*)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/i
    CaptionUrlTitleNew = /(\S[\S\s]*)\s+<a[\S\s]*>(https?:\/\/\S+|\/\S+)<\/a>\s*(.+)?/i
    Caption = /(\S[\S\s]*)/
    def initialize(tag_name, markup, tokens)
      @title = nil
      @caption = nil
      @filetype = nil
      @starting_line = 1
      @highlight = true

      if markup =~ /\s*lang:(\S+)/i
        # puts "We found language #{$1}"
        @filetype = $1
        markup = markup.sub(/\s*lang:(\S+)/i,'')
      end
      # puts "Checking #{markup} against #{CaptionUrlTitle}..."
      before = Time.now
      if markup.index('<a href').nil? && markup =~ CaptionUrlTitle
      # if markup =~ CaptionUrlTitle
        # puts "We found CaptionUrlTitle file #{$1} code_url #{$2} linktext #{$3}"
        @file = $1
        code_url = $2
        @caption = "<figcaption><span>#{$1}</span><a href='#{$2}'>#{$3 || 'link'}</a></figcaption>"
        if code_url =~ /\S+#L(\d+)/
          @starting_line = $1.to_i
        end
      elsif markup =~ Caption
        # puts "We found Caption file #{$1}"
        @file = $1
        @caption = "<figcaption><span>#{$1}</span></figcaption>\n"
      end
      # puts "...that took #{Time.now - before} seconds."
      if @file =~ /\S[\S\s]*\w+\.(\w+)/ && @filetype.nil?
        # puts "We found filetype #{$1}"
        @filetype = $1
      end
      # puts "...done"
      super
    end

    def render(context)
      output = super
      code = super
      code_lines = code.lines
      # code_lines.each_with_index do |line,index|
      #   puts "raw code line #{index}, with #{line.length} chars, is \"#{line}\""
      #   if line.length < 20
      #     puts "   Dumped, it's #{line.dump}"
      #   end
      # end
      if code_lines[0] == "\n" || code_lines[0] == "\r\n"
        code_lines.shift
      end
      if code_lines.last == "\n" || code_lines.last == "\r\n"
        code_lines.pop
      end
      # code_lines.each_with_index do |line,index|
      #   puts "less raw code line #{index}, with #{line.length} chars, is \"#{line}\""
      #   if line.length < 20
      #     puts "   Dumped, it's #{line.dump}"
      #   end
      # end
      code = code_lines.join
      source = "<figure class='code'>"
      source += @caption if @caption
      if @filetype
        source += "#{HighlightCode::highlight(code, @filetype, @starting_line)}</figure>"
      else
        source += "#{HighlightCode::tableize_code(code.lstrip.rstrip.gsub(/</,'&lt;'))}</figure>"
      end
      source = TemplateWrapper::safe_wrap(source)
      source = context['pygments_prefix'] + source if context['pygments_prefix']
      source = source + context['pygments_suffix'] if context['pygments_suffix']
      source
    end
  end
end

Liquid::Template.register_tag('codeblock', Jekyll::CodeBlock)
_plugins/rouge_code.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Conversion of https://github.com/imathis/octopress/blob/master/plugins/pygments_code.rb for use
#   with Rouge 3.x.
require 'fileutils'
require 'digest/md5'

# ROUGE_CACHE_DIR = File.expand_path('../../.rouge-cache', __FILE__)
# FileUtils.mkdir_p(ROUGE_CACHE_DIR)

module HighlightCode
  def self.highlight(str, lang, starting_line = 1)
    lang = 'ruby' if lang == 'ru'
    lang = 'objc' if lang == 'm'
    lang = 'perl' if lang == 'pl'
    lang = 'yaml' if lang == 'yml'
    str = rouge(str, lang) 
    tableize_code(str, lang, starting_line)
  end

  def self.rouge(code, lang)
    require "rouge"
    formatter = ::Rouge::Formatters::HTML.new
    formatter = ::Rouge::Formatters::HTMLLinewise.new(formatter, {class: "highlight"})
    lexer = ::Rouge::Lexer.find_fancy(lang, code) || Rouge::Lexers::PlainText
    if defined?(ROUGE_CACHE_DIR)
      path = File.join(ROUGE_CACHE_DIR, "#{lang}-#{Digest::MD5.hexdigest(code)}.html")
      if File.exist?(path)
        highlighted_code = File.read(path)
      else
        begin
          highlighted_code = formatter.format(lexer.lex(code))
        end
        File.open(path, 'w') {|f| f.print(highlighted_code) }
      end
    else
      highlighted_code = formatter.format(lexer.lex(code))
    end
    highlighted_code
  end
  def self.tableize_code (str, lang = '', starting_line = 1)
    table = '<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers">'
    code = ''
    str.lines.each_with_index do |line,index|
      # puts "code line #{index} is #{line}"
      # now get rid of those div tags
      line = line.delete_prefix("</div>")
      line = line.delete_prefix("<div class=\"highlight\">")
      line = line.tr("\r", '') if lang == 'json' # getting extra carriage returns from JSON for some reason.
      # the last line is likely blank now
      if index == str.lines.length - 1 && line == ""
        # puts "   ...skipping that one."
        next
      end
      table += "<span class='line-number'>#{index+starting_line}</span>\n"
      code  += "<span class='line'>#{line}</span>"
    end
    table += "</pre></td><td class='code'><pre><code class='#{lang}'>#{code}</code></pre></td></tr></table></div>"
  end
end

There’s a lot of “printf debugging” in there (commented out at the moment); it took a lot of trial and error to get the formatting I expected. The caching proved to have no impact at all in this environment, so I disabled it so it wouldn’t be a concern. It took a bit of CSS work as well, and the code blocks look a bit better to me now:

Not perfect, but better.
Not perfect, but better.

The image tag code and CSS from Octopress worked well, without much trouble.

But then…
#

I started considering how I would handle the categories, and add some less-minimal navigation. Given the time that had elapsed, I also looked into what newer (but not too new) static site generators existed. And then I saw Hugo-Octopress.

Hugo Reyes says what.
Hugo Reyes says what.

…so I guess I know what I’ll be doing next.


More to come
More to come

New Day 0 code