このサイトができるまで
はじめに
作ったモノ
コードの詳細は Github をご覧下さい。
使い方
jekyll の _plugin
に submodule としてでも放り込んで下さい。
org ファイルを html へ出力する際のタグのカスタマイズをしたい場合には、
_html_tags.yml
を作成しておきます(リポジトリに example を置いてあります)。
原稿となる org ファイルには、最低限
#+TITLE:
#+DATE:
#+LAYOUT:
を記載しておいて下さい。これが Jekyll の Front Matter になります。
他にも #+HTML
および #+LATEX
以外の Option が書け、
downcase したのち Front Matter として使用できます。
解説(?)
解説するようなモンでもないですけれど。
org ファイルの変換
ファイルの HTML への変換には wallyqs/org-ruby を使っています。
- 利点: Emacs の org-exporter のカスタマイズを行なう必要が無い。
- 利点: jekyll に自然に組み込める
- 欠点: org-exporter での code block 実行結果の export が使えなくなる
org-exporter による「code block 実行結果の埋め込み」を使いたい場合には、 org-exporter での html 出力をカスタマイズした後に、jekyll で処理することになります。 このアプローチは org-jekyll, export blog posts from org-mode でしょう。 また、Github Pages でコンテンツを公開することを考える場合には、 plugin が使えないため、 リンク先のアプローチを取る必要があります。
以前は同様の事を行なっていたのですが、
- jekyll を動作させるサーバ上の Emacs のバージョンと手元のバージョンとの乖離
- org のファイルと html ファイルの双方をリポジトリで管理する無駄さ
から、結局 org ファイルを直接 jekyll で処理することにしました。
org-ruby の拡張(?)
最新の org-ruby では org-mode の search link を export してくれません。
そんな訳で monkey patch です。
Orgmode::HtmlOutputBuffer.inline_formatting
が割と大きな method なので、
小さな monkey patch のつもりが、ほぼコピペになってしまいました…。
#! /usr/bin/env ruby
# -*- mode: ruby; coding: utf-8 -*-
# file: org_monkey.rb
#
# Monkey Patch: Add support org search link
require 'org-ruby'
module Orgmode
# monkey patch Orgmode::to_html inline_formatter
class HtmlOutputBuffer < OutputBuffer
alias_method :_orig_add_line_attributes, :add_line_attributes
def add_line_attributes(headline)
_orig_add_line_attributes(headline)
@output << "<a id=\"#{headline.headline_text}\"><span class='anchor'>_</span></a>"
end
alias_method :_orig_inline_formatting, :inline_formatting
def inline_formatting(str)
@re_help.rewrite_emphasis str do |marker, s|
if marker == "=" or marker == "~"
s = escapeHTML s
"<#{Tags[marker][:open]}>#{s}</#{Tags[marker][:close]}>"
else
quote_tags("<#{Tags[marker][:open]}>") + s +
quote_tags("</#{Tags[marker][:close]}>")
end
end
if @options[:use_sub_superscripts] then
@re_help.rewrite_subp str do |type, text|
if type == "_" then
quote_tags("<sub>") + text + quote_tags("</sub>")
elsif type == "^" then
quote_tags("<sup>") + text + quote_tags("</sup>")
end
end
end
@re_help.rewrite_links str do |link, defi|
[link, defi].compact.each do |text|
# We don't support search links right now. Get rid of it.
# -> Support search links!!
text.sub!(/\A(file:[^\s]+)::([^\s]*?)\Z/, "\\1#\\2")
text.sub!(/\Afile:(?=[^\s]+\Z)/, "")
end
# We don't add a description for images in links, because its
# empty value forces the image to be inlined.
defi ||= link unless link =~ @re_help.org_image_file_regexp
if defi =~ @re_help.org_image_file_regexp
defi = quote_tags "<img src=\"#{defi}\" alt=\"#{defi}\" />"
end
if defi
link = @options[:link_abbrevs][link] if @options[:link_abbrevs].has_key? link
quote_tags("<a href=\"#{link}\">") + defi + quote_tags("</a>")
else
quote_tags "<img src=\"#{link}\" alt=\"#{link}\" />"
end
end
if @output_type == :table_row
str.gsub! /^\|\s*/, quote_tags("<td>")
str.gsub! /\s*\|$/, quote_tags("</td>")
str.gsub! /\s*\|\s*/, quote_tags("</td><td>")
end
if @output_type == :table_header
str.gsub! /^\|\s*/, quote_tags("<th>")
str.gsub! /\s*\|$/, quote_tags("</th>")
str.gsub! /\s*\|\s*/, quote_tags("</th><th>")
end
if @options[:export_footnotes] then
@re_help.rewrite_footnote str do |name, defi|
# TODO escape name for url?
@footnotes[name] = defi if defi
quote_tags("<sup><a class=\"footref\" name=\"fnr.#{name}\" href=\"#fn.#{name}\">") +
name + quote_tags("</a></sup>")
end
end
# Two backslashes \\ at the end of the line make a line break without breaking paragraph.
if @output_type != :table_row and @output_type != :table_header then
str.sub! /\\\\$/, quote_tags("<br />")
end
escape_string! str
Orgmode.special_symbols_to_html str
str = @re_help.restore_code_snippets str
end
alias_method :_orig_normalize_lang, :normalize_lang
def normalize_lang(lang)
case lang
when 'conf'
'ruby'
else
_orig_normalize_lang(lang)
end
end
end
end
任意の org ファイルを Jekyll で扱うために
Jekyll の原稿として org ファイルを使用する際の不満は、
- Jekyll の原稿として認識してもらうためには、 org ファイルに yaml で書かれた Front Matter が必要。 しかしながら、これは org としては美しくない。
- Front Matter に記述する内容は、
そもそも org ファイルに存在する内容が多い(
#+TITLE
とか)。 つまり情報が重複している。
でした。
これらの不満を解消してくれるプラグインとしては、
既に eggcaker/jekyll-org があります。
ただし、これは Post
専用なので、
Jekyll の blog 記事の範疇に無いページ( いわゆる Page
等)は対象としていません。
そんな訳で、結局自分でプラグインを書きました。
Jekyll::Utils.has_yaml_header?
の上書き
org ファイルに Front Matter 相当の情報が存在することを前提として処理、
すなわち、拡張子が .org
の場合は常に true
を返すようにします。
#!/usr/bin/env ruby
# -*- mode: ruby; coding: utf-8 -*-
# file: org_utils.rb
module Jekyll
# Judge the file is Org mode?
module Utils
alias_method :_orig_has_yaml_header?, :has_yaml_header?
def has_yaml_header?(file)
if File.extname(file) =~ /org/
true
else
_orig_has_yaml_header?(file)
end
end
end
end
Jekyll::Convertible
の上書き
org
ファイルの #+TITLE
等を FRONTMATTER として扱った上で、
html に出力します。
また #+TITLE
が 2 回出力されるのを防ぐために、
org-ruby で処理する前に #+TITLE
を削除しています。
#! /usr/bin/env ruby
# -*- mode: ruby; coding: utf-8 -*-
require 'org-ruby'
module Jekyll
# Handling Org options as YAML front matter, escape liquid tag
module Convertible
alias_method :_orig_read_yaml, :read_yaml
def read_yaml(base, name, opts = {})
if name =~ /org$/
content = File.read(site.in_source_dir(base, name),
merged_file_read_opts(opts))
if File.exist?('_html_tags.yml')
org = Orgmode::Parser.new(content,
markup_file: '_html_tags.yml')
else
org = Orgmode::Parser.new(content)
end
yaml_front_matter = {}
org.in_buffer_settings.each_pair do |k, v|
yaml_front_matter.merge!(k.downcase => v)
end
# remove '#+HTML'
yaml_front_matter = yaml_front_matter.delete_if { |k, v| k == 'html' }
# remove '#+LATEX'
yaml_front_matter = yaml_front_matter.delete_if { |k, v| k == 'latex' }
self.data = SafeYAML.load(yaml_front_matter.to_yaml + "---\n")
# remove '#+TITLE' avoid double exporting
org.in_buffer_settings.delete_if {|k, v| k == 'TITLE' }
if yaml_front_matter.key?('liquid')
self.content = org.to_html
self.content = self.content.gsub('‘', "'")
self.content = self.content.gsub('’', "'")
else
self.content = <<ORG
#{org.to_html.gsub('{','{').gsub('{','}')}
ORG
end
else
_orig_read_yaml(base, name, opts)
end
end
end
end
Jekyll::OrgConverter
の追加
Converter の追加は Jekyll 本家のドキュメントにもあるので、
割と簡単です。実際の処理は上書きした Jekyll::Convertible
で行なわれています。
ただ、search link を処理するために、最後にリンクを置換しています。
#!/usr/bin/env ruby
# -*- mode: ruby; coding: utf-8 -*-
# file: org.rb
require 'org-ruby'
module Jekyll
# Add New Converter handling org-mode
# main logic -> @see org_convertible.rb
class OrgConverter < Converter
safe true
priority :low
def matches(ext)
ext =~ /^\.org$/i
end
def output_ext(ext)
'.html'
end
def convert(content)
# ad hoc file link conversion
content.gsub(/<a href="([^(http:\/\/|https:\/\/|mailto:)]\S+)\.org/,
"<a href=\"\\1.html")
end
end
end
まとめ…?
まあ、ちゃんと動いているみたいですし、これで良いのかなぁ。
お気付きの点などございましたら、Issues へお願いします。