Use HTML pipeline in Middleman
Published:
Updated:
How to use html-pipeline
in middleman.
Why
The idea is to be able add postprocessing steps after the markdown processing. The same idea can be used to wrap a default tilt template with any kind of postprocessing operations.
How
We need those gems (Gemfile
):
gem 'html-pipeline'
gem 'github-linguist'
gem 'github-markdown'
# Ships with non-free emojis but you can use free ones:
gem 'gemoji'
Define a tilt template based on a given html-pipeline
(lib/mytemplate.rb
) generating HTML from markdown:
require 'tilt/template'
class MarkdownHtmlFilterTemplate < Tilt::Template
self.default_mime_type = "text/html"
def self.engine_initialized?
defined? ::Pygments and defined? ::Html::Pipeline and defined? ::Linguist
end
def initialize_engine
require 'html/pipeline'
require 'linguist'
require "pygments"
end
def prepare
@engine = HTML::Pipeline.new [
HTML::Pipeline::MarkdownFilter,
HTML::Pipeline::EmojiFilter,
HTML::Pipeline::SyntaxHighlightFilter,
HTML::Pipeline::TableOfContentsFilter
], :asset_root => "/", :gfm => false
end
def evaluate(scope, locals, &block)
@output ||= @engine.call(data)[:output].to_s
end
end
Use it in middleman (config.rb
) for processing markdown files:
require 'lib/mytemplates'
# We need to omit the Template suffix:
set :markdown_engine, :MarkdownHtmlFilter
Alternative: rack filter
Another solution is to use rack filter:
module Rack
class MyEmojiFilter
def initialize(app, options = {})
@app = app
@options = options
end
def call(env)
status, headers, response = @app.call(env)
if headers["Content-Type"] and headers["Content-Type"].include? "text/html"
html = ""
response.each { |chunk| html << chunk }
html = process(html)
headers['Content-Type'] = "text/html; charset=utf-8"
headers['Content-Length'] = "".respond_to?(:bytesize) ? html.bytesize.to_s : html.size.to_s
[status, headers, [html]]
else
[status, headers, response]
end
end
def process(html)
html.gsub(/:([a-zA-Z0-9_]{1,}):/) do |match|
emoji = $1
"<img class='emoji' src='/img/emoji/#{emoji}.png' alt=':#{emoji}:' />"
end
end
end
end
The difference is that this processes all the files and the whole content of them.
Alternative: extending the basic template by composition
class MarkdownTemplate < Tilt::Template
self.default_mime_type = "text/html"
def self.engine_initialized?
defined? ::Tilt::KramdownTemplate
end
def initialize_engine
require 'tilt/markdown'
end
def prepare
@template = Tilt::KramdownTemplate.new(@file, @line, @options, &@reader)
end
def replace_emoji
text.gsub(/:([a-zA-Z0-9_]{1,}):/) do |match|
"<img class='emoji' src='/img/emoji/#{$1}.png' alt=':#{$1}:' />"
end
end
def evaluate(scope, locals, &block)
@output ||= replace_emoji(@template.render(scope, locals, &block))
end
end
Alternative: extending the processor
This monkey-patches the Kramdown parser in order to recognise emojis:
require 'kramdown/parser/kramdown.rb'
module Kramdown
module Parser
class Kramdown
alias_method :old_emoji_initialize, :initialize
def initialize(source, options)
old_emoji_initialize source, options
@span_parsers.unshift(:emoji)
end
def parse_emoji
start_line_number = @src.current_line_number
@src.pos += @src.matched_size
emoji = @src[1]
el = Element.new(:img, nil, nil, :location => start_line_number)
add_link(el, "/img/emoji/#{emoji}.png", nil, ":#{emoji}:")
end
EMOJI_MATCH = /:([0-9a-zA-Z_]{1,}):/
define_parser(:emoji, EMOJI_MATCH)
end
end
end