이 세상에 하나는 남기고 가자

이 세상에 하나는 남기고 가자

세상에 필요한 소스코드 한줄 남기고 가자

Octopress 2에서 Jekyll로 전환

아사마루

최근 이틀동안 현재 운영중인 블로그에서 사용중인 옥토프레스를 걷어내고 순수 Jekyll로 전환하는 작업을 했다. 사실 처음엔 지금 사용중인 옥토프레스를 3버전으로 버전업 하려고 했다. 그래서 Migrating Octopress 2 to Octopress 3를 따라 전환을 시도했다. 이 글에서도 나와 있는 것처럼 옥토프레스 제작자가 Octopress 3.0 Is Coming이라고만 해두고 정식 릴리즈를 하지 않아 기다림에 지쳐 작업을 시작했다(이때까지만 하더라도 일이 간단하리라 생각했다).

아래의 글은 체계적인 설치 안내서 보다는 전환 경험담에 가깝다. 아래의 과정을 따라 설치하면 동일하게 안될 확률이 높으므로 참고만 하길 바란다. 사실 처음엔 잘 정리해 두려고 했는데 워낙에 이리저리 시도를 많이 하면서 정리하는 것을 포기했다.

Jekyll 전환에 앞서 octopress 버전업 시도 과정부터 이야기하려고 한다.

Octopress 3 - Github에 가면 최신 옥토프레스를 확인할 수 있다. 현재 시점을 기준으로 3.0.11이 최신이다. 정식 릴리즈를 공식적으로 출시하지 않았지만 개발을 나름 활발히 진행중이다. 나중에 버전업을 진행해 보니 왜 정식 릴리즈를 하지 못하고 있는지 알 것도 같다. Jekyll 기반으로 여러가지 플러그인을 사용하고 템플릿의 제작도 프로그램 제작에 준할 정도로 커스텀되는 상황이다보니 체계적인 마이그레이션 방법을 제시하기 어려운 것 같다.

Migrating Octopress 2 to Octopress 3에 나름 잘 설명되어 있기는하나 나의 경우엔 그대로 적용되지 않았다(특별한 플러그인 다수 사용하지도 않았는데). 결국은 여러가지 시도 끝에 남은 거라곤 옥토프레스 3.0.11이 설치되었다는 것 밖에 없다(사실 이건 간단히 설치가 되므로 대부분의 시간을 허비한 것이다).

이러한 이유로 순수 Jekyll로 전환을 시작하게 되었다. 사실 이 과정도 옥토프레스를 버전업하는 것과 같이 새로운 블로그를 생성하고 주요 설정을 이전하는 것이다. 옥토프레스는 원래 Jekyll을 기반으로 확장된 것이라 전환이 간단할 것이라고 생각했다. 이것 또한 착각이었다. 이 과정에서도 여러가지 실패기가 있지만 다 나열하기도 힘들다. 이후부터는 설치 과정을 간략히 요약하려고 한다.


아래는 Jekyll을 설치하기 위한 Gemfile 의 내용이다. 거의 필수에 가까운 플러그인만으로 구성했다.

source 'https://rubygems.org'

gem 'jekyll', '= 3.0.1'
gem 'jekyll-paginate'
gem 'jekyll-archives'
gem 'jekyll-sitemap'
gem 'jekyll-feed'
gem 'pygments.rb'

Gemfile 을 사용한 설치는 아래와 같이 할 수 있다. 단, ruby 버전 2.1 이상을 요구할 수 있는데 그때는 rvm을 사용해 설치하면 된다.

$ sudo gem install bundler
$ bundle install

다음은 Jekyll에서 가장 중요한 _config.yml 파일의 내용이다.

gems:
  - jekyll-paginate
  - jekyll-archives
  - jekyll-sitemap
  - jekyll-feed
  - pygments.rb

exclude: [".idea", ".git", "README.md", "Gemfile", "Gemfile.lock"]

url: http://blog.asamaru.net
title: 이 세상에 하나는 남기고 가자
subtitle: 내가 할 수 있는 모든 것을...
author: 아사마루
simple_search: https://www.google.com/search
description: > # this means to ignore newlines until "baseurl:"
  세상에 필요한 소스코드 한줄 남기고 가자.

email: asamaru@asamaru.net
baseurl: "" # the subpath of your site, e.g. /blog/

permalink: /:year/:month/:day/:title/

markdown: KramdownPygments
markdown_ext: markdown,mkd,mkdn,md
textile_ext: textile
kramdown:
  input: GFM
  auto_ids: true
  footnote_nr: 1
  entity_output: as_char
  toc_levels: 1..6
  smart_quotes: lsquo,rsquo,ldquo,rdquo

paginate: 1
paginate_path: "posts/:num"
recent_posts: 5
excerpt_link: "Read on →"
excerpt_separator: "<!--more-->"

titlecase: false

# Disqus Comments
disqus-short-name: asamaru7

# Google Analytics
google_analytics_tracking_id: UA-65749108-2

github_username: asamaru7

feed:
  path: atom.xml

# archives 생성
jekyll-archives:
  enabled:
    - categories
    - year
    - month
    - day
    - tags
  layouts:
    year: archive-year
    month: archive-month
    day: archive-day
    tag: archive-tag
    category: archive-tag
  permalinks:
    year: '/:year/'
    month: '/:year/:month/'
    day: '/:year/:month/:day/'
    tag: '/tag/:name/'
    category: '/category/:name/'

하나씩 설명하자니 너무 많다. 다만 그리 어려운 설정들이 아니므로 보면 이해할 것으로 본다.

마지막으로 한가지. Kramdown with Pygments라는 플러그인을 사용했다. Jekyll에서 kramdown을 사용하면서 Pygments를 함께 사용(코드 하이라이팅)하기 위한 플러그인이다. 그런데 몇가지 문제가 있어 아래와 같이 변형해서 사용하고 있다. 덤으로 옥토프레스에서 코드 블럭에 사용되던 문법도 호환되게 변경했으나 마지막에 문제에 걸렸다. kramdown에서 그 문법을 정상적으로 인식하지 못했다. 코드에 빈 줄바꿈이 포함되면 정상적으로 인식하지 못한다. 따라서 이 플러그인이 동작하기 이전에 발생되는 문제로 여기서는 아직 처리하지 못했다. 시간을 들여 분석해보면 처리 방법이 있을수도 있지만 일단 두기로 했다. 중요한 문제는 아니므로(게다가 내가 ruby를 모른다. 이 작업도 기본 문법 몇가지만 공부해서 처리한 것이라 깊이있는 수정은 어렵다).

kramdown_pygments.rb

# We define the an additional option for the kramdown parser to look for
module Kramdown
  module Options
      define(:kramdown_default_lang, Symbol, nil, <<EOF)
Sets the default language for highlighting code blocks

If no language is set for a code block, the default language is used
instead. The value has to be one of the languages supported by pygments
or nil if no default language should be used.

Default: nil
Used by: PygmentsHtml converter
EOF
  end
end

# This class is a plugin for kramdown, to make it use pygments instead of coderay
# It has nothing to do with Jekyll, it is simply used by the custom converter below
module Kramdown
  module Converter
    #AllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?\n/i
    #LangCaption = /([^\s]+)\s*(.+)?\n/i
    AllOptions = /([a-z0-9]+)[[:blank:]]+(.+?)[[:blank:]]+(https?:\/\/\S+|\/\S+)[[:blank:]]*([^\n]+)?\n/i
    LangCaption = /([a-z0-9]+)[[:blank:]]*([^\n]+)?\n/i

    class PygmentsHtml < Html

      begin
        require 'pygments'
      rescue LoadError
        STDERR.puts 'You are missing a library required for syntax highlighting. Please run:'
        STDERR.puts '  $ [sudo] gem install pygments'
        raise FatalException.new("Missing dependency: Pygments")
      end

      def convert_codeblock(el, indent)
        codeProc(el, indent, false)
      end

      def convert_codespan(el, indent)
        codeProc(el, indent, true)
      end

      def codeProc(el, indent, isSapn)
        attr = el.attr.dup
        lang = extract_code_language!(attr) || @options[:kramdown_default_lang]
        # STDERR.puts "lang #{el.value}"

        codeStr = el.value
        # octopress code 형식 지원
        caption = ""
        if isSapn
          if codeStr =~ AllOptions or codeStr =~ LangCaption
            acceptLang = Pygments::Lexer.find_by_alias("#{$1}")
            if acceptLang != nil
              # STDERR.puts "lang [#{$1}] [#{$2}]"
              isSapn = false
              lang = "#{$1}"

              if codeStr =~ AllOptions
                # STDERR.puts "lang all"
                # fn = "#{$2}".gsub(/\s+/, "")
                caption = "<figcaption><span>#{$2}</span><a href='#{$3}'>#{$4 || 'link'}</a></figcaption>"
              end
              if caption == "" and codeStr =~ LangCaption
                # STDERR.puts "lang lang"
                caption = "<figcaption><span>#{$2}</span></figcaption>"
              end
              if caption != ""
                codeStr = codeStr.lines.to_a[1..-1].join  # 첫줄 제거
              end
            end
          end
        end

        code = pygmentize(codeStr, lang)
        code_attr = {}
        if isSapn
          if lang
            code_attr['class'] = "highlight notranslate"
            if code_attr.has_key?('class')
              code_attr['class'] += " language-#{lang}"
            else
              code_attr['class'] = "language-#{lang}"
            end
          end
        else
          code_attr['class'] = "language-#{lang}" if lang
        end
        if code == nil
          code = escape_html(codeStr)
          if code_attr['class'] != nil
            code_attr['class'] += " nmcode"
          else
            code_attr['class'] = "nmcode"
          end
        end
        if isSapn
          "<code#{html_attributes(attr)}>#{code}</code>"
        else
          "#{' '*indent}<figure class='code'>#{caption}<div class=\"highlight notranslate\"><pre#{html_attributes(attr)}><code#{html_attributes(code_attr)}>#{code}</code></pre></div></figure>\n"
        end
      end

      def pygmentize(code, lang)
        if lang
          Pygments.highlight(code,
            :lexer => lang,
#            :options => { :startinline => true, :encoding => 'utf-8', :nowrap => true })
            :options => { :startinline => true, :encoding => 'utf-8', :linenos => 'table' })
        end
      end
    end
  end
end

# This class is the actual custom Jekyll converter.
class Jekyll::Converters::Markdown::KramdownPygments

  def initialize(config)
    require 'kramdown'
    @config = config
  rescue LoadError
    STDERR.puts 'You are missing a library required for Markdown. Please run:'
    STDERR.puts '  $ [sudo] gem install kramdown'
    raise FatalException.new("Missing dependency: kramdown")
  end

  def convert(content)
    html = Kramdown::Document.new(content, {
        :auto_ids             => @config['kramdown']['auto_ids'],
        :footnote_nr          => @config['kramdown']['footnote_nr'],
        :entity_output        => @config['kramdown']['entity_output'],
        :toc_levels           => @config['kramdown']['toc_levels'],
        :smart_quotes         => @config['kramdown']['smart_quotes'],
        :kramdown_default_lang => @config['kramdown']['default_lang'],
        :input                => @config['kramdown']['input'],
        :hard_wrap            => @config['kramdown']['hard_wrap']
    }).to_pygments_html
    return html;
  end
end

보다 자세한 처리 결과를 확인하고 싶다면 이 블로그의 소스를 참고하기 바란다. asamaru7/blog - Github

comments powered by Disqus