RedmineのコンテンツをMarkdownに変換しました

Textile vs Markdown

軽量マークアップ言語にはたくさんの種類があります。私も教育で使うテキストの作成やRedmineWikiでは長年 Textile を使って来ました。 しかし、最近はGithubの標準のマークアップ言語がMarkdownだったり、Atom(エディター)をはじめたくさんのMarkdownをサポートするツールが現れ、私も MacDownというツールが気に入り開発ドキュメント等のMarkdown化がどんんどん進んでいます。このブログもはてな記法ではなくMarkdownを使っています。

TextileとMarkdownを比べると 標準のMarkdown は記述能力が低くテキストを作るには能力不足ですが、 GitHubの拡張php Markdown Extra などの拡張機能を使えば、ほぼ同等です。

Redmine

Redmineマークアップ言語のデフォルトはTextileですが、Version 2.5からMarkdownをサポートするようになりました。ただし、Redmineサーバー全体の設定なので新規にRedmineを始める人以外には使いにくいものでした。 redmine_persist_wfmtのようなプラグインを使えばコンテンツ(Wiki, Ticket...)単位でTextitle/Markdownが選択できるのでこれを使えば良いかもしれません。

しかし、私はRedmineWikiを教育でよく使っています。お客様毎に教育で使うコード、ヒント、参考情報・・・などをWikiを使い提供しています。しかも以前に作ったWikiを元に作成する事が多いので、そのTextileとMarkdwonが混在するのは望ましくありません。

そこで、Textileコンテンツを全てMarkdownに変換してしま事にしました。

Textile to Markdown

"Convert Textile to Markdown" で検索すると Pandoc というドキュメント変換ツールが良く出来てきます。このツールは色々なドキュメント形式を相互変換できる素晴らしいツールですが、やはり完璧ではありませんでした。 Markdown→TextileではGitHub拡張をサポートしていますが、Textile→MarkdownではテーブルをGitHub拡張に変換してくれたりしません・・・

そこで、自作を検討しました。MarkdownとTexitleは似ています、正規表現を使えばほとんど変換できます。

def textile_to_markdown(textile)
  d = []
  pre = false
  table_header = false
  text_line = false

  textile.each_line do |s|
     s.chomp!

    if pre
      if s =~ /<\/pre>/
        d << "~~~"
        pre = false
      else
        d << s
      end
      next
    end

    s.gsub!(/(^|\s)\*([^\s\*].*?)\*(\s|$)/, " **\\2** ")
    s.gsub!(/(^|\s)@([^\s].*?)@(\s|$)/, " `\\2` ")
    s.gsub!(/(^|\s)-([^\s].*?)-(\s|$)/, " ~~\\2~~ ")
    s.gsub!(/"(.*?)":(.*?)\.html/, " [\\1](\\2.html) ")

    d << ""  if text_line
    text_line = false

    case s
    when /^<pre>/
      d << "~~~"
      pre = true
    when /^\*\*\* (.*)$/
      d << "      * " + $1
    when /^\*\* (.*)$/
      d << "   * " + $1
    when /^\* (.*)$/
      d << "* " + $1
    when /^\#\#\# (.*)$/
      d << "      1. " + $1
    when /^\#\# (.*)$/
      d << "   1. " + $1
    when /^\# (.*)$/
      d << "1. " + $1
    when /^h(\d)\. (.*)$/
      d << "#" * $1.to_i + " " + $2
    when /^!(.*?)!/
      d << "![](#{$1})"
    when /^\|_\./
      d << s.gsub("|_.", "| ")
      table_header = true
    when /^\|/
      d << s.gsub(/\=\..+?\|/, ":---:|").gsub(/\s+.+?\s+\|/, "---|") if table_header
      table_header = false
      d << s.gsub("|=.", "| ")
    when /^\s*$/
      d << s
    else
      d << s
      text_line = true
    end
  end

  d.join("\n") + "\n"
end

このコードは全てのTextileフォーマットをサポートしているわけではありませんが、私が使ってる機能はほぼ網羅しています。 変換されたMarkdownはGitHub拡張やphp Markdown Extraの機能を使っています。

さてRedmineでは色々なところにTextileが書けますが、今回は Wiki, Ticket, Ticketの履歴のコンテンツに対応しました。必要があれば追加して下さい。

def update_content(model, attrbute)
  total = model.count
  step = total / 10
  puts "  #{model}.#{attrbute} : #{total}"
  model.all.each_with_index do |rec, ix|
    n = ix + 1
    puts sprintf("%8d", n)   if n % step == 0
    rec[attrbute] = textile_to_markdown(rec[attrbute])  if rec[attrbute]
    rec.save!
  end
end

update_content(WikiContent, :text)
update_content(Issue, :description)
update_content(Journal, :notes)

コードは Gistにも置きました。

このコードを以下のように runnder で実行するとTextileがMarkdownに変換されます。

$  rails runner -e production tools/textile2md.rb  

注意: このプログラムは全てのTextile形式を変換できるものではありません、またバグ等で正しく変換できない場合もありますので、変換前に必ずRDBのバックアップを取って下さい。