コンテンツに飛ぶ | ナビゲーションに飛ぶ

パーソナルツール
ログイン
セクション
現在位置: ホーム ブログ

ブログ

作者: MURAOKA Taro 最終変更 2011年02月28日 02時16分
思いついたことを書き連ねていきます。

Vimスクリプトを書いてみよう

作者: MURAOKA Taro 最終変更 2012年02月20日 23時17分
単純なVimスクリプトを徐々にできていく様子を見ることで、その書き方を体験&習得してみよう。

目的

  • 機能を持ったVimスクリプト(プラグイン)を徐々に作る様子を見て、書き方を学習してみよう
  • 作るのはよくあるタイプのプラグイン
    • Javaの単体テスト用クラスファイルを開く(作る)コマンドを作るよ
  • 「ね、簡単でしょう(by ボブ)」と言ってみたい

まずは雛形

ひな形としてこんなスクリプトを作ります。

command! -nargs=0 UtestAppend call <SID>UtestAppend()

function! s:UtestAppend()
  echo 'HERE'
endfunction " s:UtestAppend()
  1. UtestAppendというコマンドを追加
  2. UtestAppendコマンドいが実行されたら 'HERE' と表示する

ほら、すごく簡単ですね。

Javaだけに作用させる

以下はdiff形式で変更点だけ。

@@ -1,5 +1,17 @@
 command! -nargs=0 UtestAppend call <SID>UtestAppend()
 
 function! s:UtestAppend()
-  echo 'HERE'
+  let target = s:GetTargetName()
+  if strlen(target) <= 0
+    echomsg 'Not test target file: ' . expand('%')
+    return 0
+  endif
 endfunction " s:UtestAppend()
+
+function! s:GetTargetName()
+  if expand('%:e') ==# 'java'
+    return expand('%')
+  else
+    return ''
+  endif
+endfunction " s:GetTargetName()

バッファがJavaの時だけファイル名を返す関数GetTargetName()を作り、それ以外の時はエラーメッセージを表示して終了するようにしました。

テスト用クラスのファイルを開く

次にテスト用のクラスファイルを決定しバッファとして開いてみます。

@@ -6,6 +6,13 @@
     echomsg 'Not test target file: ' . expand('%')
     return 0
   endif
+
+  let testfile = s:GetTestFileName(target)
+  if strlen(testfile) <= 0 || target ==# testfile
+    echomsg 'Cannot generate test file name: ' . target
+  endif
+
+  call s:OpenFile(testfile)
 endfunction " s:UtestAppend()
 
 function! s:GetTargetName()
@@ -15,3 +22,13 @@
     return ''
   endif
 endfunction " s:GetTargetName()
+
+function! s:GetTestFileName(target)
+  let testfile = a:target
+  let testfile = substitute(testfile, '\.java$', 'Test&', '')
+  return testfile
+endfunction " s:GetTestFileName()
+
+function! s:OpenFile(filepath)
+  execute 'split ' . a:filepath
+endfunction " s:OpenFile()
  1. GetTestFileName関数を作成: クラスのファイル名からテスト用クラスのファイル名を生成する
  2. OpenFile関数を作成: 指定したファイルを開く

テスト用のクラスの名前は私の好みでまずは *Test.java という形式にだけ対応させました。つまりテスト対象クラスがFooBar.javaならばテスト用クラスはFooBarTest.javaとなります。これはあとで拡張します。

これで基本機能は完成です。思ったよりもずっと簡単でしょう。

ディレクトリに対応

最近のJavaはmavenの標準ディレクトリ構成に従っていることも多いので、それに対応してみましょう。

@@ -25,10 +25,16 @@
 
 function! s:GetTestFileName(target)
   let testfile = a:target
+  let testfile = substitute(testfile, '\<src/main/java/', 'src/test/java/', '')
   let testfile = substitute(testfile, '\.java$', 'Test&', '')
   return testfile
 endfunction " s:GetTestFileName()
 
 function! s:OpenFile(filepath)
   execute 'split ' . a:filepath
+
+  let dir = expand('%:h')
+  if !isdirectory(dir)
+    call mkdir(dir, 'p')
+  endif
 endfunction " s:OpenFile()

テスト対象クラスが src/main/java 下にあったらテスト用クラスを src/test/java 下に作るようにし、必要に応じてディレクトリを掘るようにしました。

これは本当に簡単ですね。

Test*.javaにも対応

私の好みで *Test.java だけに対応しましたが、既に Test*.java があるのならそちらを優先して開くようにしましょう。

@@ -16,7 +16,11 @@
 endfunction " s:UtestAppend()
 
 function! s:GetTargetName()
+  let curr = expand('%')
   if expand('%:e') ==# 'java'
+    if curr =~# '\<src/test/java' || curr =~# 'Test.java$' || curr =~# 'Test\k\+.java$'
+      return ''
+    endif
     return expand('%')
   else
     return ''
@@ -26,8 +30,13 @@
 function! s:GetTestFileName(target)
   let testfile = a:target
   let testfile = substitute(testfile, '\<src/main/java/', 'src/test/java/', '')
-  let testfile = substitute(testfile, '\.java$', 'Test&', '')
-  return testfile
+  let testfile1 = substitute(testfile, '\.java$', 'Test&', '')
+  let testfile2 = substitute(testfile, '\k\+.java$', 'Test&', '')
+  if getfsize(testfile2) >= 0
+    return testfile2
+  else
+    return testfile1
+  endif
 endfunction " s:GetTestFileName()
 
 function! s:OpenFile(filepath)

ついでにテスト用クラスのテストは作らないようにしてみました。

最後に全体

最後に完成したファイルの全体を見てみましょう。

command! -nargs=0 UtestAppend call <SID>UtestAppend()

function! s:UtestAppend()
  let target = s:GetTargetName()
  if strlen(target) <= 0
    echomsg 'Not test target file: ' . expand('%')
    return 0
  endif

  let testfile = s:GetTestFileName(target)
  if strlen(testfile) <= 0 || target ==# testfile
    echomsg 'Cannot generate test file name: ' . target
  endif

  call s:OpenFile(testfile)
endfunction " s:UtestAppend()

function! s:GetTargetName()
  let curr = expand('%')
  if expand('%:e') ==# 'java'
    if curr =~# '\<src/test/java' || curr =~# 'Test.java$' || curr =~# 'Test\k\+.java$'
      return ''
    endif
    return expand('%')
  else
    return ''
  endif
endfunction " s:GetTargetName()

function! s:GetTestFileName(target)
  let testfile = a:target
  let testfile = substitute(testfile, '\<src/main/java/', 'src/test/java/', '')
  let testfile1 = substitute(testfile, '\.java$', 'Test&', '')
  let testfile2 = substitute(testfile, '\k\+.java$', 'Test&', '')
  if getfsize(testfile2) >= 0
    return testfile2
  else
    return testfile1
  endif
endfunction " s:GetTestFileName()

function! s:OpenFile(filepath)
  execute 'split ' . a:filepath

  let dir = expand('%:h')
  if !isdirectory(dir)
    call mkdir(dir, 'p')
  endif
endfunction " s:OpenFile()

プラグインとして仕立てる、エラーハンドリングする、などなどの余地はかなりありますが、だいたい動くという意味ではこんな感じで良いのです。

「ね、簡単でしょう?」

(満面のドヤ顔で)

間違い訂正

2012/02/20 09:40

"バッファがJavaの時だけファイル名を返す関数GetTargetName()を作り" GetTargetName()がエラー時の処理だけでファイル名返してない気がします。 --- tyru

最後のファイル全体がなんかコピペ時にイロイロおかしくなってたみたいです。訂正しました。

2012/02/20 23:15

  • diffが小さくなるようにしました
  • <, >, " といった文字が正しく表示されていないのを訂正しました

VimだけでXML Reformat

作者: MURAOKA Taro 最終変更 2012年02月09日 23時08分
Vim「だけ」でなんちゃってXML Reformat

まっつんさんの記事のパクリです。

たとえばこういう XML があったとして…

<status><created_at>Mon Feb 06 21:07:52 +0000 2012</created_at><id>166629198054690816</id><text>Post-Bowl Twitter analysis http://t.co/OYYSRSew http://t.co/M0AtLQVd</text><source>web</source><truncated>false</truncated><favorited>false</favorited><in_reply_to_status_id></in_reply_to_status_id><in_reply_to_user_id></in_reply_to_user_id><in_reply_to_screen_name></in_reply_to_screen_name><retweet_count>454</retweet_count><retweeted>false</retweeted><user><id>783214</id><name>Twitter</name><screen_name>twitter</screen_name><location>San Francisco, CA</location><description>Always wondering what's happening. </description><profile_image_url>http://a0.twimg.com/profile_images/1124040897/at-twitter_normal.png</profile_image_url><profile_image_url_https>https://si0.twimg.com/profile_images/1124040897/at-twitter_normal.png</profile_image_url_https><url>http://blog.twitter.com/</url><protected>false</protected><followers_count>7625563</followers_count><profile_background_color>ACDED6</profile_background_color><profile_text_color>333333</profile_text_color><profile_link_color>038543</profile_link_color><profile_sidebar_fill_color>F6F6F6</profile_sidebar_fill_color><profile_sidebar_border_color>EEEEEE</profile_sidebar_border_color><friends_count>822</friends_count><created_at>Tue Feb 20 14:35:54 +0000 2007</created_at><favourites_count>16</favourites_count><utc_offset>-28800</utc_offset><time_zone>Pacific Time (US &amp; Canada)</time_zone><profile_background_image_url>http://a1.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png</profile_background_image_url><profile_background_image_url_https>https://si0.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png</profile_background_image_url_https><profile_background_tile>true</profile_background_tile><profile_use_background_image>true</profile_use_background_image><notifications>false</notifications><geo_enabled>true</geo_enabled><verified>true</verified><following>true</following><statuses_count>1266</statuses_count><lang>en</lang><contributors_enabled>true</contributors_enabled><follow_request_sent>false</follow_request_sent><listed_count>68708</listed_count><show_all_inline_media>true</show_all_inline_media><default_profile>false</default_profile><default_profile_image>false</default_profile_image><is_translator>false</is_translator></user><geo/><coordinates/><place/><possibly_sensitive>false</possibly_sensitive><contributors><user_id>7694352</user_id></contributors></status>

Vim のバッファにコピペで貼りつけた上で次のようにコマンドを叩くと…

:set ft=xml
:%s/></>^M</g
:%s/>\(\S\)/>^M\1/g
:%s/\(\S\)</\1^M</g
gg=G

こんなんなります。^Mは<CTRL-V><ENTER>で入力しましょう。めんどくさければマクロ化や関数化しても良いですが、まぁその場で組み立てれば十分です。

<status>
  <created_at>
    Mon Feb 06 21:07:52 +0000 2012
  </created_at>
  <id>
    166629198054690816
  </id>
  <text>
    Post-Bowl Twitter analysis http://t.co/OYYSRSew http://t.co/M0AtLQVd
  </text>
  <source>
    web
  </source>
  <truncated>
    false
  </truncated>
  <favorited>
    false
  </favorited>
  <in_reply_to_status_id>
  </in_reply_to_status_id>
  <in_reply_to_user_id>
  </in_reply_to_user_id>
  <in_reply_to_screen_name>
  </in_reply_to_screen_name>
  <retweet_count>
    454
  </retweet_count>
  <retweeted>
    false
  </retweeted>
  <user>
    <id>
      783214
    </id>
    <name>
      Twitter
    </name>
    <screen_name>
      twitter
    </screen_name>
    <location>
      San Francisco, CA
    </location>
    <description>
      Always wondering what's happening. </description>
    <profile_image_url>
      http://a0.twimg.com/profile_images/1124040897/at-twitter_normal.png
    </profile_image_url>
    <profile_image_url_https>
      https://si0.twimg.com/profile_images/1124040897/at-twitter_normal.png
    </profile_image_url_https>
    <url>
      http://blog.twitter.com/
    </url>
    <protected>
      false
    </protected>
    <followers_count>
      7625563
    </followers_count>
    <profile_background_color>
      ACDED6
    </profile_background_color>
    <profile_text_color>
      333333
    </profile_text_color>
    <profile_link_color>
      038543
    </profile_link_color>
    <profile_sidebar_fill_color>
      F6F6F6
    </profile_sidebar_fill_color>
    <profile_sidebar_border_color>
      EEEEEE
    </profile_sidebar_border_color>
    <friends_count>
      822
    </friends_count>
    <created_at>
      Tue Feb 20 14:35:54 +0000 2007
    </created_at>
    <favourites_count>
      16
    </favourites_count>
    <utc_offset>
      -28800
    </utc_offset>
    <time_zone>
      Pacific Time (US &amp; Canada)
    </time_zone>
    <profile_background_image_url>
      http://a1.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png
    </profile_background_image_url>
    <profile_background_image_url_https>
      https://si0.twimg.com/profile_background_images/378245879/Twitter_1544x2000.png
    </profile_background_image_url_https>
    <profile_background_tile>
      true
    </profile_background_tile>
    <profile_use_background_image>
      true
    </profile_use_background_image>
    <notifications>
      false
    </notifications>
    <geo_enabled>
      true
    </geo_enabled>
    <verified>
      true
    </verified>
    <following>
      true
    </following>
    <statuses_count>
      1266
    </statuses_count>
    <lang>
      en
    </lang>
    <contributors_enabled>
      true
    </contributors_enabled>
    <follow_request_sent>
      false
    </follow_request_sent>
    <listed_count>
      68708
    </listed_count>
    <show_all_inline_media>
      true
    </show_all_inline_media>
    <default_profile>
      false
    </default_profile>
    <default_profile_image>
      false
    </default_profile_image>
    <is_translator>
      false
    </is_translator>
  </user>
  <geo/>
  <coordinates/>
  <place/>
  <possibly_sensitive>
    false
  </possibly_sensitive>
  <contributors>
    <user_id>
      7694352
    </user_id>
  </contributors>
</status>

手元の環境にPythonが入ってなくても安心です。そうスパルタンVimならね。


スパルタンVimのPDF公開

作者: MURAOKA Taro 最終変更 2012年01月20日 11時07分
スパルタンVimのPDFを公開します。

C81での頒布からそろそろ三週間になろうとしていますので、スパルタンVimのPDFを公開します。以下からダウンロードしてください。

http://files.kaoriya.net/goto/c81pdf

このPDFは表紙とそれ以外を別々のPDFとして出力し、MERGE PDFというWebサービスを使って1つのPDFにしました。最近はほんとうに便利になりましたね。

2012/01/20追記

スパルタンVimに対してtwitterでいただいた誤りの指摘をまとめました。 http://togetter.com/li/244330 その他に見つけた場合はtwitterならば @kaoriya にお願いします。その他の手段は…まかせます。私の目に入りそうなところに書いてください。