Ruby で Redmine の Wiki を巡回

昨日、「Redmine 内の Wiki データをエクスポートする方法」を紹介しましたが、以下の点で気に入りませんでした。

  • Redmine 標準のエクスポート機能
    • ファイルが単一なのが嫌
    • レイアウト・スタイルが通常と異なる
    • 画像ファイルなど添付ファイルを取得するのが面倒
  • 既存の巡回ソフト
    • wget ではうまく巡回できなかった (設定による?)
    • 各ページのファイル名に拡張子「.html」を付けたい
    • 画像のファイル名に拡張子「.png」を付けたい

結局、標準のエクスポート機能や、既存の巡回ソフトに満足できなかったので、 Ruby で作ってみました。ライブラリは、「Simple Crawler」を使用しています。 (scRUBYt! や Nokogiri、Hpricot、Mechanize なども検討したのですが、結局一番シンプルな形に落ち着きました。)

以下、ソースです。 Windows 環境で動かすことを前提にしているため、ファイル名を Shift-JIS に変換しています。 (ソースコードUTF-8 です。) 取得したい Wiki のプロジェクト名を「project_name」変数に設定して使用してください。

# coding: utf8
require 'rubygems'
require 'kconv'
require 'uri'
require 'fileutils'
require 'net/http'
require 'simplecrawler'

server_name = "127.0.0.1"
project_name = "project_name_here"

crawler = SimpleCrawler::Crawler.new("http://#{server_name}:3000/projects/#{project_name}/wiki/Page_index")
crawler.load_binary_data = true
crawler.include_patterns = ["\\/projects\\/#{project_name}\\/wiki\\/", "\\/attachments\\/"]
crawler.skip_patterns = ["\\/wiki\\/export", "\\/attachments\\/download\\/", "\\/history$", "\\/rename$", "\\/edit$", "\\?format"]

attachments = {}
output_dir = project_name
HtmlPostfix = ".html"

FileUtils.mkdir_p(output_dir)

crawler.crawl do |document|
  next if document.http_status.nil?

  output_filename = File.join(output_dir, File.basename(URI.unescape(document.uri.to_s).tosjis))
  data = document.data

  if document.headers["content-type"].include?("text/html")
    output_filename << HtmlPostfix
    data = URI.unescape(data)

    data.grep(%r|"/attachments/(\d+)/(.*?)"|) do
      attachments[$1] = $2
    end

    # リンクを .html 形式に変換・階層の調整など
    data.gsub!(%r|/stylesheets/|, "")
    data.gsub!(%r|/javascripts/|, "")
    data.gsub!(%r|\?[\w\d=&;]+|, "")
    data.gsub!(%r|"/projects/#{project_name}(?:/wiki)?/(.*?)"|) { %("#{$1}#{HtmlPostfix}") }
    data.gsub!(%r|"/attachments(?:/download)?/(\d+)(?:.*?)"|) { %("#{attachments[$1]}") }
  end

  open(output_filename, "wb") { |f| f.write data }
end

# CSS などを別途取得
option_files = [
  "/stylesheets/application.css",
  "/javascripts/prototype.js",
  "/javascripts/effects.js",
  "/javascripts/dragdrop.js",
  "/javascripts/controls.js",
  "/javascripts/application.js",
  "/stylesheets/jstoolbar.css",
  "/stylesheets/csshover.htc"]

Net::HTTP.start(server_name, 3000) do |http|
  option_files.each do |file|
    response = http.get(file)
    open(File.join(output_dir, File.basename(file)), "wb") { |f| f.write response.body }
  end
end

Ruby らしくない書き方や、よりよい書き方があれば教えてください。