かつて Pukiwiki を使っていて、その記事を Growi(旧crowi-plus) へ移行したい人に向けて

# はじめに

プライベートな環境で使う Wiki として PukiWiki はとても便利なのですが、できれば Markdown 記法が使える Wiki を使いたいところです。

(GitHub の Readme は Markdown 形式ですし、YouTrack, Redmine 等のタスク管理ツールも Markdown 形式をサポートし始めている)

私の社内では Markdown 形式で記述する Wiki として Growi(旧crowi-plus) を使っています。
新しく Growi にページを作成する場合はよいのですが、Pukiwiki をメインで使っていた時代に記述したページが残っています。
今回、それらを Growi へ移行する機会が出来ました。

既に [こちらの記事](https://qiita.com/yuki-takei/items/152e20f4421333ae8fd9) にある Pukiwiki 記法から Markdown 形式へ変換するワンライナーのコマンドを使えば、人手を使って移行してもそこまでの日数はかからない見込みでした。
そのため、移行スクリプトを作成するのはコストに見合わないとの考えでしたが、使う人が多ければ、コスト回収が出来るかもしれない、、、
ということで作成することにしてみました。

移行作業を一括で行うスクリプトは [こちら](https://github.com/ryu-sato/conv-pkwk2growi) で公開しています。

本記事内ではスクリプトの一部を引用しつつ、移行するために必要な内容を紹介します。

# Pukiwiki から Growi へ移行する流れ

1. Pukiwiki のページデータと添付ファイルデータを dump ディレクトリ配下にコピーする
    - ディレクトリ構造は Pukiwiki のページ階層と合わせる
    - ページデータは page.txt として保存 (まだ Pukiwiki 形式のまま)
    - 添付ファイルは attachments 配下に保存
2. Pukiwiki のデータを Markdown 形式に変換する
3. Growi の API を使ってページを作成する

# Pukiwiki のページデータと添付ファイルデータを dump ディレクトリ配下にコピーする

Pukiwiki のページは次の構造を持つ。

- ページデータは `${Pukiwikiインストールディレクトリ}/wiki` 配下に `${ページ名}.txt` として保存されている
- ページデータのファイル名は、ページ名を URL エンコードした文字列から `%` を削除したものである
- ページの階層構造はページ名に含まれる `/` で表される

URL エンコードされた文字列は `nkf` コマンドで `--url-input` オプションをつけることでデコードできる。
そこで、`sed` コマンドを使って、一度ファイル名を 2 文字ずつ取り出して `%` をつけて URL エンコードに変換することにする。

例えば1つのファイル `${F}` をデコードするためには次のコマンドを実行する。

```bash:ページデータのファイル名をデコードする
echo ${F} | sed -e 's/\.txt$//' | sed -e 's/\(..\)/%\1/g' | nkf --url-input
```

その他、dump ディレクトリへ保存するために必要となる処理はディレクトリ作成とファイルコピーなので、適宜 `mkdir` コマンド `cp` コマンドを使う。

また、`${Pukiwikiインストールディレクトリ}/wiki` 配下にはページデータの他に Pukiwiki の設定を保存するためのファイルがあるため、コピー対象から除外しておく。(Pukiwiki の設定ファイルはファイル名が `:config` で始まる)

一方で添付ファイルは次の構造を持つ。

- 添付ファイルは `${Pukiwikiインストールディレクトリ}/attach` 配下に `${ページ名}_${添付ファイル名}` として保存されている。
- 添付ファイルの添付ファイル名と添付先ページ名は URL エンコードした文字列から `%` を削除したものである

ページ名と添付ファイル名がアンダースコア `_` でつながっているが、分けて考えればページ名を処理した時と同じ方法で処理できる。

```bash:添付データのファイル名をデコードする
echo ${F} | sed -e 's/^[^_]*_//g' | sed -e 's/\(..\)/%\1/g' | nkf --url-input
```

以上を全てのファイルに対して行い、適宜ディレクトリ作成とコピー処理を行った結果が次のスクリプトである。

```bash:dump-pkwk.sh
#!/bin/bash -ex

# read configs
. config/env.sh

# dump pukiwiki pages
cd ${PUKIWIKI_PAGE_DIR}
for f in *.txt; do

  PAGE_NAME=$(echo ${f} | sed -e 's/\.txt$//' | sed -e 's/\(..\)/%\1/g' | nkf --url-input)

  # ignore settings files
  if [[ "${PAGE_NAME}" =~ ^:config/ ]]; then
    continue;
  fi

  # copy page directory
  PAGE_DIR_NAME=$(echo ${PAGE_NAME} | sed -e 's#[^/]/$##g')
  mkdir -p "${DUMP_DIR}/${PAGE_DIR_NAME}"
  cp -ip "${PUKIWIKI_PAGE_DIR}/${f}" "${DUMP_DIR}/${PAGE_DIR_NAME}/${PAGE_FILE_NAME}"
done

# dump attachments
cd ${PUKIWIKI_ATTACHMENTS_DIR}
for f in *; do

  if [[ ! "${f}" =~ ^[0-9A-F_]+$ ]]; then
    continue;
  fi

  # copy attachments
  PAGE_DIR_NAME=$(echo ${f} | sed -e 's/_.*$//' | sed -e 's/\(..\)/%\1/g' | nkf --url-input)
  ATTACHMENTS_FILE_NAME=$(echo ${f} | sed -e 's/^[^_]*_//g' | sed -e 's/\(..\)/%\1/g' | nkf --url-input)
  mkdir -p "${DUMP_DIR}/${PAGE_DIR_NAME}/${ATTACHMENTS_DIR_NAME}"
  cp -ip "${PUKIWIKI_ATTACHMENTS_DIR}/${f}" "${DUMP_DIR}/${PAGE_DIR_NAME}/${ATTACHMENTS_DIR_NAME}/${ATTACHMENTS_FILE_NAME}"
done
```

```bash:config/env.sh
#!/bin/bash -e

# User configs
## Output directory to execute script
DUMP_DIR="${PWD}/dump"
## Target directory to convert (Files are NOT overrided by script.)
PUKIWIKI_DATADIR="${PWD}/pukiwiki"

# System config
SCRIPT_PATH="${PWD}"
CMD_CREATE_CROWI_PAGE="create-crowi-page.rb"
CMD_ATTACH_FILE_TO_CROWI_PAGE="attach-files-to-crowi-page.rb"
PAGE_FILE_NAME="page.txt"
ATTACHMENTS_DIR_NAME="attachments"

## Target directory 
PUKIWIKI_PAGE_DIR="${PUKIWIKI_DATADIR}/wiki"
PUKIWIKI_ATTACHMENTS_DIR="${PUKIWIKI_DATADIR}/attach"
```

# Pukiwiki のデータを Markdown 形式に変換する

既に [こちらの記事](https://qiita.com/yuki-takei/items/152e20f4421333ae8fd9) にある Pukiwiki 記法から Markdown 形式へ変換するワンライナーのコマンドを使うことにした。

dump ディレクトリ配下に保存した全ての Pukiwiki ページデータに対して Markdown 形式への変換処理を行う。

尚、デコードされたファイルをシェルスクリプトで処理する際の注意点として、ファイル名にスペースが含まれる場合を考慮しないといけない。
`cp`, `mkdir` コマンドでスペースが含まれるファイルを扱うにはダブルクオート `"` で括ればよい。
`for` でループする際は区切り文字列 `$IFS` を変更する。

```bash:conv-pkwk2md.sh
#!/bin/bash -ex

# read variables
. config/env.sh

# see. https://ja.stackoverflow.com/questions/39713/%E4%BB%BB%E6%84%8F%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%AE%E4%B8%AD%E3%82%92%E6%A4%9C%E7%B4%A2%E3%81%97-%E6%9D%A1%E4%BB%B6%E3%81%AB%E4%B8%80%E8%87%B4%E3%81%97%E3%81%9F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E5%87%A6%E7%90%86%E3%82%92%E3%81%99%E3%82%8B%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E6%9B%B8%E3%81%8D%E3%81%9F%E3%81%84
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

# convert pukiwiki pages to markdown
cd ${DUMP_DIR}
for f in `find . -name ${PAGE_FILE_NAME}`; do

  # convert content from pukiwiki to markdown
  TEMPFILE=$(mktemp -t conv-pkwk2growi.XXXXX) || exit 1
  # see. https://qiita.com/yuki-takei/items/152e20f4421333ae8fd9
  cat "${f}" | sed -e 's/ \[#[0-9a-z]\+\]$//g' \
    -e 's/^\*\*\*/###/g' -e 's/^\*\*/##/g' -e 's/^\*/#/g' \
    -e 's/^---/        -/g' -e 's/^--/    -/g' -e 's/^\(\s*\)-\([^ ]\)/\1- \2/g' \
    -e 's/^[\+][\+][\+]/        1./g' -e 's/^[\+][\+]/    1./g' -e 's/^[\+]/1./g' -e 's/^\(\s*\)1\.\([^ ]\)/\11. \2/g' \
    -e 's/&br;/<br>/g' \
    -e 's/^#pre{*/```/g' -e 's/^}}*/```/g' \
    -e 's/%%/~~/g' \
    -e "s/^\#lsx/`echo -ne '\u0024'`lsx()/g" -e 's/^\(#\+\)\([^ #]\)/\1 \2/g' \
    > "${TEMPFILE}"
  mv "$TEMPFILE" "${f}"
done
```

# Growi の API を使ってページを作成する

dump に保存した Markdown 形式のファイルを Growi のページとして保存する。
Growi は Crowi 互換の API があるため、これを使う。

今回は API を使ってページを作成するための Ruby 用 gem の [crowi-client](https://rubygems.org/gems/crowi-client) があるのでそれを使うことにした。

2021/05/30追記：本記事執筆後、GROWI の API のみに対応するクライアントを作る目的で [growi-client](https://rubygems.org/gems/growi-client) を作りました。crowi-client より多少新しいです。

crowi-client の使い方は次のとおり。

- gem パッケージをインストールする (※今回は bundle install する前提)
- `config/settings.yml` に API token と Growi の URL を記述する
- Ruby ファイルを用意して `require 'crowi-client'` で呼び出す
- 各種操作用リクエスト `CPApiRequestPagesCreate` 等のインスタンスを作成する
- crowi-client の `request` メソッドを使ってリクエストを Growi へ送信する

ページを新規作成するためには `CPApiRequestPagesCreate` インスタンスを使えばよい。

既存ページが Growi にあればページ作成はせず、存在しなかった場合のみ Pukiwiki から移行したページを作成することとして、次の Ruby スクリプトを作成した。

```ruby:create-crowi-page.rb
require 'bundler'
Bundler.require

require 'crowi-client'

if ARGV.length < 1
  puts "$0 CROWI_PAGE_NAME"
  exit 1
end
page_name = ARGV[0]

if CrowiClient.instance.page_exist?(path_exp: page_name)
  puts "Cannot create page. Because page '#{page_name}' is already exists. Ignore this page."
  exit 0
end

# Create page files
body = STDIN.read
req = CPApiRequestPagesCreate.new path: page_name, body: body
puts CrowiClient.instance.request(req)
```

また、全てのファイルに対して上記 Ruby スクリプトを実行するよう、次のシェルスクリプトを作成した。

```bash:create-crowi-page.sh
#!/bin/bash -ex

# read variables
. config/env.sh

# see. https://ja.stackoverflow.com/questions/39713/%E4%BB%BB%E6%84%8F%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%AE%E4%B8%AD%E3%82%92%E6%A4%9C%E7%B4%A2%E3%81%97-%E6%9D%A1%
E4%BB%B6%E3%81%AB%E4%B8%80%E8%87%B4%E3%81%97%E3%81%9F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E5%87%A6%E7%90%86%E3%82%92%E3%81%99%E3%82%8B%E3%82%B7%
E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E6%9B%B8%E3%81%8D%E3%81%9F%E3%81%84
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

# register markdown data to Growi
cd ${DUMP_DIR}
for f in `find . -name ${PAGE_FILE_NAME}`; do
  RELATIVE_FILE_PATH=$(realpath --relative-to="${SCRIPT_PATH}" "${f}")
  PAGE_NAME=$(echo ${f} | sed -e 's#/page.txt##' | sed -e 's#^\./##')
  cd "${SCRIPT_PATH}"
  cat "${RELATIVE_FILE_PATH}" | ruby "${CMD_CREATE_CROWI_PAGE}" "/${PAGE_NAME}"
  cd "${DUMP_DIR}"
done
```

# スクリプトを実行する

以上、全ての流れを行えば Pukiwiki のページを Growi へ移行できます。

スクリプトを使って移行する場合は次の操作を行います。(詳細はREADMEを参照して下さい)

## 実行環境準備

1. リポジトリをクローンする
    `git clone https://github.com/ryu-sato/conv-pkwk2growi.git`
1. `config/env.sh` の User Config 箇所を適宜設定する
1. `config/settings.yml` を作成して Growi の API_TOKEN と URL を設定する
1. Gem ファイルをインストールする
    `bundle install`

## コンバート方法

1. Pukiwiki データをディレクトリへ dump する (`dump-pkwk.sh`)
1. dump したファイルを Markdown 形式へ変更(`conv-pkwk2md.sh`)
1. Growi へ dump したファイルをアップロードする(`create-crowi-page.sh`)

```bash
bash dump-pkwk.sh
bash conv-pkwk2md.sh
bash create-crowi-page.sh
```

# 最後に

実際にスクリプトを使って移行はできたものの困った点が2つあったため共有します。

- crowi-client を使って添付ファイルを Growi のページに添付できなかった
    - 添付ファイルは手動で移行する必要があります
- crowi-client が Basic 認証に対応していないため Growi のページに Basic 認証を設けている場合は Authentication Error となる
    - 移行時に一時的に Basic 認証を解除する等の対応をする必要があります

多くの人にスクリプトを使って楽に Growi へ移行してもらえれば幸いです。