vimからlivedoorwikiを更新する
以前からVimスクリプトに挑戦しよう、挑戦しようとは思ってたんですが、なかなかできなかった。
その一つの理由にVimスクリプトの文法がよくわからんってのがあったので、今回はこれからVimスクリプト書こうって人のために、できるだけ詳細に説明しようと思います。
もちろん、こちとらVimスクリプト歴1週間なので、わからんとこはいっぱいあるんですが、、、
で、今回作ろうと思ったのがLivedoor wikiをVimから更新するスクリプト。
まあ、ほぼほぼhatena.vimをパクったんですが、かなり勉強にもなりました。
構成・機能
構成は以下のような感じです。
もう全部hatena.vimを丸パクリです。笑
使いかたはplugin/livedoor.vimのs:ld_userとs:ld_wiki_idに自分のIDをセットし、.vimrcに「set runtimepath+=$VIM/livedoor」とかランタイムパスを通してやればOKです。
ただ、今すぐにでも直すべきところはてんこもり。笑
セキュリティとか微塵も考えてない。笑
例外処理も皆無、、、あとたぶんUnixの人しか動かない気がします。(Windows試してません。笑)
という状態なので、自己責任でお願いいたします。
土日にしかブログ書けないので、説明も超走り書きですが、今回は僕自身vimスクリプトがよくわかっていないので、アドバイスとかを早めにもらえたら、、、
とか、プログラムを公開することで何かいいことありそうって気がしたので、かなり不完全ですが、公開することにしました。
livedoor.vimその1
全部一気に説明するとかなり見にくいので、少しずつ分割して説明していきます。
scriptencoding utf-8 " ログイン command! -nargs=0 LivedoorLogin call LivedoorLogin() " 編集 wiki内容を取得 command! -nargs=1 LivedoorEdit call LivedoorEdit('<args>') " 更新 wikiへ内容を送る command! -nargs=0 LivedoorEditCommit call LivedoorEditCommit() " カーソル上の単語のwikiページを編集 command! -nargs=? LivedoorCustomEdit call LivedoorCustomEdit('<args>') " LivedoorCustomEditを「\le」で使用できるように nnoremap <Leader>le : LivedoorCustomEdit<CR> " livedoor.vimのベースディレクトリを取得 if !exists('g:ld_base_dir') let g:ld_base_dir = substitute(expand('<sfile>:p:h'), '[/\\]plugin$', '', '') endif " curlのコマンド let s:curl_cmd = 'curl -k --silent' " LivedoorID let s:ld_user = "hogehoge" " LivedoorwikiのユーザID let s:ld_wiki_id = '123456' " LivedoorwikiのURL let s:ld_wiki_url = 'http://wiki.livedoor.jp/' " Livedoorwikiの管理画面のURL let s:ld_cms_url = 'http://cms.wiki.livedoor.com/wiki/' " LivedoorのログインURL let s:ld_login_url = 'https://member.livedoor.com/login/index'
command
まずcommandですが、これでユーザ定義の関数をコマンドラインから使えるようにしています。
コマンドラインで「:LivedoorEdit 'hoge'」みたいな。
「-nargs」は引数の数を定義しています。
http://www.ac.cyberhome.ne.jp/~yakahaira/vimdoc/usr_40.html#40.2
「
「'
つまり、
command! -nargs=1 LivedoorEdit call LivedoorEdit(<args>) は、コマンドラインより :LivedoorEdit 'hoge' としてやらんとだめなんですが、 command! -nargs=1 LivedoorEdit call LivedoorEdit('<args>') :LivedoorEdit hoge でいいわけです。
nnoremap
ノーマルモードの時のマップです。
ここでは「\ld」をコマンドラインのLivedoorCustomEditにマッピングしています。
「
nnormapとnmapの違いはどうも再マップされないようにする? よくわかりません。。。
http://www.ac.cyberhome.ne.jp/~yakahaira/vimdoc/usr_40.html#40.1
変数
変数とか基本的なことは下記に書いてありますので、参照下さい。
http://nanasi.jp/code.html
上記のlet ...というやつはこのスクリプトで使いまわす変数を定義しています。
expand
expandは特殊文字を展開します。
例えばコマンドラインで「:echo expand(%)」とすると、現在開いているファイル名が表示されると思います。そんな感じです。
組込み関数は下記参照。
http://www.ac.cyberhome.ne.jp/~yakahaira/vimdoc/eval.html#functions
livedoor.vimその2
" livredoorにログインする function! LivedoorLogin() let ld_user = s:ld_user " クッキーを保存するファイル if has('win32') let cookie_file = g:ld_base_dir . '\cookies\' . ld_user else let cookie_file = g:ld_base_dir . '/cookies/' . ld_user endif " パスワードをプロンプトで入力 let password = inputsecret('Password: ') if !len(password) echo 'キャンセルしました' return [] endif " ログイン let g:content = system(s:curl_cmd . ' ' . s:ld_login_url . ' -d livedoor_id=' . ld_user . ' -d password=' . password . ' -c "' . cookie_file . '" -D -') " ログインできたらクッキーファイルのパスを返す if g:content =~ 'Location: ' echo 'ログインしました' return cookie_file else echoerr 'ログインに失敗しました' endif endfunction
inputsecret
inputsecretでユーザ対話型で入力を促せます。
secretなので、と同じように文字が「*」になります。
↑というかこれ通ってしまっていいのか?笑
system
外部コマンドを呼び出します。
ここではcurlを使用しています。
curl
- -k
- 証明書がちゃんとしていることを前提としてSSL接続
- -d
- Postとしてデータを送る
- データはURLエンコードしてくれる。
- 実はこれを知らなくて、printfとか使ってURLエンコードをがんばってたんですが、どうしてもUTF-8からEUC-JPに変換して、エンコードというのができなくて2日ほど消費しました...
- Livedoorwikiでは該当ページの編集用URLを取得するのにどうしても編集ページ名をURLエンコードする必要が合ったので。
- -c
- クッキーデータをファイルに保存する
- -D
- ヘッダー情報をファイルに保存する。「-D -」で標準出力にだす。
=~
左辺が右辺に正規表現でマッチすればTrue、やと思う。笑 上記はログインできた時のみ「Location:」でリダイレクトするので、それをログインの成功判定条件としている。
livedoor.vimその3
function! LivedoorCustomEdit(...) if exists(a:0) call LivedoorEdit(a:0) else let cursor_word = matchstr(expand('<cWORD>'), '[^\[].*[^\]]') call LivedoorEdit(cursor_word) endif endfunction
a:0
変数にはスコープがあります。
「a:hoge」は関数の引数の変数という意味です。
この関数ではLivedoorCustomEdit(...)というように引数が何個も取れるようになっています。
「a:0」というのは、その何個も取れる引数のうちの一つめという意味です。
ちなみにLivedoorCustomEditは引数を必要としないので、そもそも上記はおかしいのですが。笑
expandで展開できる特殊な単語の内の一つです。
詳しくは下記参照。
http://www.ac.cyberhome.ne.jp/~yakahaira/vimdoc/cmdline.html#:%3Ccword%3E
適当な単語の上にカーソルをあわせた後、コマンドラインで「:echo expand('
livedoor.vimその4
" 編集 function! LivedoorEdit(wiki_name) " クッキーファイルのパスを取得していない時のみログイン if !exists('b:ld_login_cookie') let ld_login_cookie = LivedoorLogin() if !len(ld_login_cookie) return endif else let ld_login_cookie = b:ld_login_cookie endif " ログインし直さないでいいように let b:ld_login_cookie = ld_login_cookie " 該当ページのページID, wikiのバージョン, 編集コンテンツ、編集クッキー情報取得 let [page_id, wiki_version, content, ld_edit_cookie] = LivedoorLoadContent(a:wiki_name, ld_login_cookie) " 編集コンテンツをVimに表示するための一時ファイル let tmpfile = tempname() " 一時ファイルを開く execute 'edit!' tmpfile " バックアップファイルは作らない setlocal noswapfile " LivedoorwikiはEUC-JPなので let &fileencoding = 'euc-jp' " 一時ファイルのタイトル let &titlestring = 'livedoor wiki ' . a:wiki_name " Livedoorwiki記法のsyntax set filetype=livedoor " 必要な情報を新らしく開いたバッファに渡す let b:ld_login_cookie = ld_login_cookie let b:page_id = page_id let b:wiki_version = wiki_version let b:ld_edit_cookie = ld_edit_cookie " これよくわからんけど必要 let nopaste = !&paste set paste " 取得したコンテンツを貼り付け execute 'normal i' . content if nopaste set nopaste endif " 貼り付けた状態では、まだ編集していない状態にする set nomodified endfunction
b:hoge
「b:hoge」はバッファをスコープとした変数です。
このスコープがバッファの意味ですが、要はVimのウィンドウをスコープとしているようです。(たぶん)
gvimだと「:split」とかでウィンドウを分割できますが、ウィンドウ分割すると、分割する前に参照できたb:hogeは参照できなくなります。
ここではクッキーファイルのパスをバッファ変数とし、その変数がある限りはログインする必要のないようにしています。
tmpfile
LivedoorLoadContentで取得してきた内容を書き込むために一時ファイルを作成しています。
「execute 'edit! tmpfile'」でVimで開いています。
これはコマンドらインで「:e! tmpfile」とかしたのと同じことをしています。
で、この時点で新しいウィンドウを開いたので、b:ld_login_cookieとかに再度セットして、新たに開いたファイルで変数が参照できるようにしています。
nomodified
これは現在の開いているバッファを変更なしの状態とします。
gvimだと最初にファイルを開いたときから何か変更を加えると、コマンドラインの上のところに「[+]」というマークが出ると思います。
これが出ると保存しないで「:q」でウィンドウを閉じようとすると、保存してから閉じるべし、といわれるんですが、nomodifiedをセットするとあたかも変更をしていないかのようにできます。
livedoor.vimその5
" 編集するコンテンツを取得 function! LivedoorLoadContent(wiki_name, login_cookie) " LivedoorwikiはEUC-JPなので let enc_wiki_name = iconv(a:wiki_name, &enc, 'euc-jp') " まず、編集ページのURLを取得するためのHTMLを取得 let content = system(s:curl_cmd . ' "' . s:ld_wiki_url . s:ld_user . '/d/' . enc_wiki_name . '" -b "' . a:login_cookie . '"') let enc_content = iconv(content, 'euc-jp', &enc) " 編集ページのURLを取得 let edit_url = matchstr(enc_content, '<li class="menu-02"><a href="\zs.\{-}\ze">') " 騙取するページIDを取得 let page_id = matchstr(edit_url, '&id=\zs.\{-}\ze$') let cookie_file = g:ld_base_dir . '/cookies/' . s:ld_user . '_edit' " 編集するページのHTMLを取得 let edit_content = system(s:curl_cmd . ' "' . edit_url . '" -b "' . a:login_cookie . '" -c "' . cookie_file . '" -D -') let enc_edit_content = iconv(edit_content, 'euc-jp', &enc) " 編集するページのバージョン管理番号を取得 let wiki_version = matchstr(enc_edit_content, 'name="id" type="HIDDEN">\n<input value="\zs.\{-}\ze" name="ver" type="HIDDEN">') " 編集するコンテンツを取得 let edit_body = matchstr(enc_edit_content, '<textarea id="inputBody".\{-}>\zs.\{-}\ze</textarea>') return [page_id, wiki_version, edit_body, cookie_file] endfunction
iconv
LivedoorwikiはEUC-JPなので、文字コードを変更します。
「&enc」は現在の文字コードです。(たぶん正確には違いますが。。。)
curl
- -b
- 保存してあるログインクッキーを参照させます
\zs.\{-}\ze
- \zs \ze
- このふたつで挟まれた文字列をで取得します。
- \{-}
- 最短一致の0回以上の繰り返し
- 上記では、「.\{-}」ということで、任意の文字列の0回以上の繰り返しという意味です。
http://www.ac.cyberhome.ne.jp/~yakahaira/vimdoc/pattern.html#pattern
livedoor.vimその5
" Livedoorwikiへ更新する function! LivedoorEditCommit() " 変更されていれば、保存する if &modified write endif " 今開いている一時ファイルのフルパスを取得 let body_file = expand('%') " 更新 let result = LivedoorPost(b:ld_login_cookie, b:page_id, b:wiki_version, body_file, b:ld_edit_cookie) let enc_result = iconv(result, 'euc-jp', &enc) if result =~ 'Location: http://wiki.livedoor.jp/' echo '更新しました' endif endfunction
&modified
バッファが変更されたかどうかのフラグです。
上記は変更があれば保存という処理(:w)です。
livedoor.vimその6
" LivedoorwikiへPostする function! LivedoorPost(ld_login_cookie, page_id, wiki_version, body_file, ld_edit_cookie) " multipart対応の-FでPostする let post_data = ' -F wiki_id=' . s:ld_wiki_id \ . ' -F id=' . a:page_id \ . ' -F ver=' . a:wiki_version \ . ' -F reload=0' \ . ' -F full=' \ . ' -F "content=<' . a:body_file . '"' \ . ' -F tags= -F file= -F description= ' \ . ' -F .save="' . iconv('保存する', &enc, 'euc-jp') . '"' " リファラ let referer = s:ld_cms_url . 'edit?wiki_id=' . s:ld_wiki_id . '&id=' . a:page_id " Post return system(s:curl_cmd . ' ' . s:ld_cms_url . 'edit -b "' . a:ld_login_cookie .'" -e "' . referer . '"' . post_data . ' -D -') endfunction
curl
- F
- multipartでフォームデータをPostする
- ' -F "content<' . a:body_fiele . '"'
- a:body_fileにはファイルパスが入っており、それを標準入力として、contentにいれています。
- e
- リファラをセットする
- Livedoorwikiでは別にリファラがなくてもPostできるっぽいですが、セキュリティ対策でそのうちリファラがないと送信できなくなるかもしれないので念のため。
ちなみにめちゃめちゃはまったんですが、Livedoorwikiは「.save」というsubmitボタンもセットしてやらないとちゃんと更新してくれませんでした。
syntax/livedoor.vim
こっちは上記よりも精査できていません(というかほぼ名前変えただけ。笑)が、一応ほぼ使えるっぽいです。
syntax include @Html syntax/html.vim syn match LDHeading +^\*\{1,3}\(\w\+\*\)\=.\+$+ contains=LDHeadingName,LDLink syn match LDHeadingName +^\*\{1,3}[^*]\{-1,}\*\|^\*\{1,3}+ contained nextgroup=LDCategory syn match LDCategory +\(\[.\{-}\]\)\++ contained syn match LDList +^[+-]\++ syn match LDDefinition +^:.\{-1,}:.\+$+ contains=LDDefColon syn match LDDefColon +:+ contained syn match LDTable +^|\(.\{-}|\)\++ contains=LDTableHeader,LDTableSeparator syn match LDTableHeader +\*[^|]\++ contained syn match LDTableSeparator +|+ contained syn match LDLink +\[\(http\|google\(:news\|:image\)\=\|amazon\|rakuten\):.\{-}\]+ contains=LDLinkSpecial syn match LDLinkURL +https\=://[-!#$%&*+,./:;=?@0-9a-zA-Z_~]\++ syn match LDLinkSpecial +:title\(=[^]]*\)\=\ze\]\|:barcode\|:image+ syn match LDKeyword +\[\[.\{-}\]\]+ syn region LDCancelLink matchgroup=LDBlockDelimiter start=+\[\]+ end=+\[\]+ oneline syn match LDFootNote +(\@<!(((\@!.\{-}))+ syn match LDCancelFootNote +)((+ contains=LDCanceledParen syn match LDCanceledParen +(+ contained syn match LDReadMore +=====\?+ syn match LDTex +\[tex:.\{-}\]+ syn match LDUkulele +\[uke:.\{-}\]+ syn region LDCancelP matchgroup=LDBlockDelimiter start=+>\ze<+ end=+<$+ contains=@Html syn match LDHTMLTag +<+ contains=@Html syn cluster LDSpecials contains=LDDefColon,LDTableSeparator,LDCanceledParen " 自動リンク syn match LDAutoLinkDiary +\(d:\)\=id:[A-Za-z]\w*\(:\d\{6}\|:\d\{8}\|:archive\(:\d\{6}\)\=\|:about\)\=+ syn match LDAutoLinkQuestion +question:\d\{10}\(:title\|:detail\|:image\(:small\)\=\)\=+ syn match LDAutoLinkQuestion +\[question:\d\{10}:title=.\{-}\]+ syn match LDAutoLinkSearch +\[search:\(keyword:\|question:\|asin:\)\=.\{-}\]+ syn match LDAutoLinkAntenna +a:id:[A-Za-z]\w*+ syn match LDAutoLinkBookmark +b:id:[A-Za-z]\w*\(:\d\{8}\|:favorite\|:asin\)\=+ syn match LDAutoLinkBookmark +\[b:id:[A-Za-z]\w*:t:.\{-}\]+ syn match LDAutoLinkBookmark +\[b:\(keyword\|t\):.\{-}\]+ syn match LDAutoLinkFotolife +f:id:[A-Za-z]\w*\(:favorite\|:.\{-}:image\(:small\|:[wh]\d\+\)\=\)\=+ syn match LDAutoLinkGroup +g:[A-Za-z]\w*\(:id:[A-Za-z]\w*\(:\d\{6}\|:\d\{8}\|:archive\)\=\)\=+ syn match LDAutoLinkGroup +\[g:[A-Za-z]\w*:keyword:.\{-}\]+ syn match LDAutoLinkIdea +idea:\d\+\(:title\)\=+ syn match LDAutoLinkIdea +i:id:[A-Za-z]\w*+ syn match LDAutoLinkIdea +\[i:t:.\{-}\]+ syn match LDAutoLinkRSS +r:id:[A-Za-z]\w*+ syn match LDAutoLinkGraph +graph:id:[A-Za-z]\w*+ syn match LDAutoLinkGraph +\[graph:id:[A-Za-z]\w*\(:.\{-}\(:image\)\=\)\=\]+ syn match LDAutoLinkGraph +\[graph:t:.\{-}]+ syn match LDAutoLinkMap +map:x[0-9.]\+y[0-9.]\++ syn match LDAutoLinkMap +\[map:\(t:\)\=.\{-}\]+ syn match LDAutoLinkKeyword +\[keyword:.\{-}\(:graph\(\(:refcount\|:refrank\|:accessrank\)\(:\d\+[dwmy]\)\=\)\=\)\=\]+ syn match LDAutoLinkISBN +isbn:\d\{10}\(:title\|\(:image\|:detail\)\(:small\|:large\)\=\)\=+ syn match LDAutoLinkASIN +asin:[0-9A-Z]\+\(:title\|\(:image\|:detail\)\(:small\|:large\)\=\)\=+ syn match LDAutoLinkJANEAN +\(jan\|ean\):\d\+\(:title\|\(:image\|:detail\)\(:small\|:large\)\=\|:barcode\)\=+ syn match LDAutoLinkJANEAN +\[\(jan\|ean\):\d\+:title=.\{-}\]+ syn cluster LDAutoLinks contains=LDAutoLinkDiary,LDAutoLinkQuestion,LDAutoLinkSearch,LDAutoLinkAntenna,LDAutoLinkBookmark,LDAutoLinkFotolife,LDAutoLinkGroup,LDAutoLinkIdea,LDAutoLinkRSS,LDAutoLinkGraph,LDAutoLinkMap,LDAutoLinkKeyword,LDAutoLinkISBN,LDAutoLinkASIN,LDAutoLinkJANEAN syn match LDBlockQuoteCite +>\@<=https\?:.\{-}>$+ contained contains=LDLinkURL syn region LDBlockQuote matchgroup=LDBlockDelimiter start=+^>>$+ end=+^<<$+ contains=ALLBUT,@LDSpecials,LDBlockQuoteCite syn region LDBlockQuote matchgroup=LDBlockDelimiter start=+^>\zehttps\?:.\{-}>$+ end=+^<<$+ contains=ALLBUT,@LDSpecials syn region LDPre matchgroup=LDBlockDelimiter start=+^>|$+ end=+^|<$+ contains=ALLBUT,@LDSpecials,LDBlockQuoteCite syn region LDSuperPre matchgroup=LDBlockDelimiter start=+^=|[^|]*|$+ end=+^||=$+ " コメント syn region LDComment start=+<!--+ end=+-->+ hi link LDHeading Title hi link LDCategory Label hi link LDHeadingName Identifier hi link LDList Statement hi link LDDefColon Statement hi link LDTableSeparator Statement hi link LDTableHeader Title hi link LDKeyword Underlined hi link LDLink Underlined hi link LDLinkURL Underlined hi link LDLinkSpecial Special hi link LDAutoLinkDiary Underlined hi link LDAutoLinkQuestion Underlined hi link LDAutoLinkSearch Underlined hi link LDAutoLinkAntenna Underlined hi link LDAutoLinkBookmark Underlined hi link LDAutoLinkFotolife Underlined hi link LDAutoLinkGroup Underlined hi link LDAutoLinkIdea Underlined hi link LDAutoLinkRSS Underlined hi link LDAutoLinkGraph Underlined hi link LDAutoLinkMap Underlined hi link LDAutoLinkKeyword Underlined hi link LDAutoLinkISBN Underlined hi link LDAutoLinkASIN Underlined hi link LDAutoLinkJANEAN Underlined hi link LDFootNote Identifier "hi link LDCanceledParen Special "hi link LDCancelLink Special hi link LDError Error "hi link LDBlockQuote String "hi link LDPre String "hi link LDSuperPre String hi link LDBlockQuoteCite Delimiter hi link LDBlockDelimiter Delimiter hi link LDReadMore Special hi link LDComment Comment hi link LDCancelP Delimiter
今回のブログは
もちろんhatena.vimを使って更新しました!!
ここまで一週間。もう毎日こればっかりやってて、昨日とかパソコンの前に半日以上座ってがんばってここまで。
そこまで時間かけてこれってものも悲しいのですが、色々あるとは思いますが、アドバイス等頂けたら幸いです。
ぱっと見すぐ直せる場所もたくさんあるので、明日から地味に直していきます。。。