Rubyのソース中のダブルクォート文字列をシングルクォートに置換する

これは備忘録。

id:eel3:20090326:1238028836 でgcovのログファイルをマージするgccr.plを手に入れたので、調子に乗って単体テストアプリを幾つも作っていたら、Makefileのメンテが微妙に面倒になってきた。

そこでRubyMakefile生成スクリプト*1をでっちあげたら、文字列リテラルが全部ダブルクォートで括られていた。どう見てもC/C++/Java系統のプログラマの血がにじみ出たコードだ。

しかし幾ら何でも「全部ダブルクォート」ってのはどうだろう、ということで式展開とか全く関係ない文字列をシングルクォートで囲むように置換しようと思ったわけだ、vimで。

……いや、RubyスクリプトNTEmacsで書いたのだけど、emacs正規表現で置換する方法を全く知らないので、知っているツールを使ったのだ。何て不勉強な私。

式展開したい文字列もあったけど、そこまでフォローできる正規表現を書ける自信なんて欠片も無いし、変換対象の量が少なかったので、そこら辺は全て手動で確認するヘタレ仕様で書いてみた。

:%s/"\([^"]*\)"/'\1'/gc

ちなみにシングルクォートからダブルクォートにする場合は多分こんな感じ。

:%s/'\([^']*\)'/"\1"/gc

とはいえこのままではヘタレ仕様すぎて悔しいので、後でエスケープ文字や簡単な式展開が含まれていても正しくマッチするように頑張ってみた。もっとも式展開へのマッチングが適当なので、多分{}が入れ子になっていたり式展開中の文字列リテラルなどに}が含まれていたりするとダメなはず。あと式展開中の式に改行が含まれている場合とか*2

:%s/"\(\(#{[^}]*}\|\\.\|[^"]\)*\)"/'\1'/gc

テスト結果は次の通り。

テストデータ マッチした部分 評価
"hoge" "hoge" OK
hoge "hoge" "hoge" OK
"hoge" "hoge" "hoge"と"hoge" OK
"foo\tbar" "foo\tbar" OK
"\tfoobar\n" "\tfoobar\n" OK
"hello, \"world" "hello, \"world" OK
"hello, \"world" "hello, \"world" "hello, \"world"と"hello, \"world" OK
"\"hello, world\"" "\"hello, world\"" OK
"#{user["name"]}" # "user-name" "#{user["name"]}"と"user-name" OK
"foo#{user["name"]} bar #{baz["nome"]}" "foo#{user["name"]} bar #{baz["nome"]}" OK

本当は「エスケープ文字や式展開が含まれている文字列にはマッチしない」ということをやりたかったのだけど、スキル不足でダメだった。現時点で辿りついた正規表現はこんな感じ。

:%s/\(^\|[^\\]\)"\(\(\(#{[^}]*}\|\\.\)\@![^"]\)*\)"/\1'\2'/gc

テスト結果は次の通り。エスケープ文字や式展開を含む文字列の後にダブルクォートがあるとNG。

テストデータ マッチした部分 評価
"hoge" 「"hoge"」 OK
hoge "hoge" 「 "hoge"」 OK
"hoge" "hoge" 「"hoge"」と「 "hoge"」 OK
"foo\tbar" マッチしない OK
"\tfoobar\n" マッチしない OK
"hello, \"world" マッチしない OK
"hello, \"world" "hello, \"world" 「d" "」 NG
"\"hello, world\"" マッチしない OK
"#{user["name"]}" # "user-name" 「["name"」と「}" # "」 NG
"foo#{user["name"]} bar #{baz["nome"]}" 「["name"」と「["nome"」 OK

正規表現の挙動についてあまり理解していないのが敗因だと思う。もっと精進しないと。

*1:といってもMakefileのテンプレートをヒアドキュメントで抱えていて、必要な部分のみ式展開でパラメータを設定して書き出すだけ。

*2:こちらはvim側の制限もからんでくる気がする。