Technology Engineering

178inaba の技術ブログ

Vim Pluginに入門してみた #vim

この記事はVim2 Advent Calendar 2019の19日目の記事です。

Vim Pluginを書こう書こうと思ってずっとできていなかったのでAdvent Calendarをきっかけにhello, world的なPluginを書いてみた。

github.com

:Hello foobar と打つと hello, foobar と返してくれるだけのPluginです。

今回はこのPluginを書くときにわからなくて調べた部分をご紹介します。

Plugin名

特に決まりは無くてvim-foobar、foobar-vim、foobar.vimの3つの流派が居るっぽい。
自分はvim-go、vim-lspを参考にvim-helloと名付けた。

Vim Pluginの開発ディレクトリは決まっている

Vim起動時に読み込まれるディレクトリが決まっている。
そのディレクトリは何個かあるが、ユーザが一番使いやすそうなのは ~/.vim/pack/*/start/ です。
* は1つのディレクトリ。
(複数ディレクトリあると読み込まれないので注意。)

自分は ~/.vim/pack/plugins/start/vim-hello に置いた。

ディレクトリ構成

今度はVim Plugin内のディレクトリ構成。

pluginautoload というディレクトリが必要。

├── autoload
│   └── hello.vim
└── plugin
    └── hello.vim

plugin 配下にあるファイルはVim起動時に読み込まれるファイル。
autoload 配下にあるファイルは必要になった時に読み込まれる。

コマンド定義は plugin で関数は autoload というように覚えておけば良さそう。

-nargs で引数ありコマンドを定義

コマンドの引数を取得したい場合 -nargs を使って引数を何個取るのか設定できます。

-nargs=0        引数なし
-nargs=1        引数 1 個
-nargs=*        いくつでも
-nargs=?        引数なし、もしくは 1 個
-nargs=+        引数 1 個以上

コマンドラインコマンドを定義する

自分は引数1個以上取りたかったのでこんな感じで書きました。

command! -nargs=+ Hello call hello#hello(<q-args>)

また、<q-args>特殊文字が入っていても適切にエスケープしてくれる引数指定です。
クオートの例が紹介されていて、クオートを適切に表示するなら <q-args> の方が良さそうだったので使用しています。

関数の引数は a: でアクセス

Vim Scriptではよく g:l: がついた変数を見ます。
これはスコープを指していて g: ならグローバル、l: ならローカルといった具合です。

thinca.hatenablog.com

しかし、引数はちょっと特殊で a: を使います。
a: をつけないで引数にアクセスしようとすると「未定義の変数です」と言われてしまうので注意。

vim-jp.org

まとめ

というわけで駆け足でしたが自分がhello, world的なPluginを書いた時に調べた箇所をご紹介しました。
一度hello, world的なPluginを作ってしまえばそこを足がかりに色々試したりできそうです。
そして試したことを切り出して新たにPluginを作っていけたらいいな。

Go言語で画像生成のテストを書く #golang

この記事はGo3 Advent Calendar 2019の18日目の記事です。

この記事ではGo言語における画像生成のテスト方法を紹介します。

画像生成というのはロジックに手を入れていなくてもライブラリがアップデートされたタイミング等でバグりやすい箇所です。
テストを用いてデグレの確認を常に行いましょう。

続きを読む

VimConf 2019参加レポートを書いた

自分のブログからもリンクしておきます。

engineer.dena.jp

VimConf 2019に参加した皆さんのブログを書くスピードが速くて自分もなんとか今週中には出したいと思っていたので間に合ってよかったです😎

今夜はVimConfのアイスクリームスプーンとハーゲンダッツで祝杯をあげようと思います。

YubiSwitch を使って YubiKey の誤作動を防ぐ方法

皆さん YubiKey 使ってますか?

私は YubiKey 5C Nano を使っています。

この YubiKey 5C Nano はちょっと触れただけで One Time Password (以下 OTP) を発行するという物でサクッと入力できて便利な反面、困ったこともあります。

f:id:i178inaba:20190824214654j:plain
金属部分に触れるだけでOTPを入力してしまう。

具体的には

  • Slack に OTP を投稿してしまう。
  • Jira のキーボードショートカットに引っかかり、新しいチケットを作成してしまう。
  • Twitter のキーボードショートカットに引っかかり、通知に飛んでしまう。

などです。

今回はそれを解決します。

続きを読む

AWS CloudFormation の dry-run を cli でやるコマンド

AWS CloudFormation で dry-run して差分が見たい場合、以下のように create-change-setdescribe-change-set のコマンドで見れる。

$ aws cloudformation create-change-set --stack-name test-stack --change-set-name test-stack-dry-run --template-body file://test-stack.yml
$ aws cloudformation describe-change-set --stack-name test-stack --change-set-name test-stack-dry-run

以下に例を示す。

続きを読む

Makefile のシェル代入演算子は macOS 標準の make では使えない

postd.cc

上記記事で != (ビックリイコール) という演算子を知った。
これはシェル代入演算子といい右辺をシェルのコマンドとして実行し、実行結果を左辺に保存するという演算子である。

今回はシェル代入演算子macOS で動かない現象があったため原因と対策をメモとして残しておく。

続きを読む

gRPC(Go)でタイムアウト時にio.EOFエラーになる件を調査した

WEB+DB PRESS Vol.110のgRPC特集がおもしろくて手を動かしながら読んでいる。

タイムアウト設定の事も書かれていて、そこに

設定した期限を過ぎた場合、クライアントスタブは応答を待ち受けるのをやめてステータスコードDeadlineExceeded(4)として処理を終了します。

とあったので試したところ、想定と異なった挙動になったのでメモしておく。

現象

一部省略するが、メインとなるコードは下記である。

    c := pb.NewFileServiceClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    stream, err := c.Upload(ctx)
    if err != nil {
        log.Fatalf("Could not upload file: %v.", err)
    }

    buf := make([]byte, 1000*1024)
    for {
        n, err := fs.Read(buf)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Could not read file: %v.", err)
        }

        if err := stream.Send(&pb.FileRequest{Name: filepath.Base(name), Data: buf[:n]}); err != nil {
            log.Fatalf("Could not send file: %v.", err)
        }
    }

    res, err := stream.CloseAndRecv()
    if err != nil {
        log.Fatalf("Could not receive response file: %v.", err)
    }

この時、私は stream.Send の部分で DeadlineExceeded エラーが返ると期待していたが実際は io.EOF エラーだった。

調査

気になったのでgrpc-goのコードを辿って調査した。
長くなるので重要だと思った箇所を記載する。

また、私の解釈としてStreaming RPCには接続保持側とストリーミング通信側があると解釈しているので2つに分けて記載する。
例えば上記コードで言うと Upload() が接続保持側で Send() がストリーミング通信側である。

接続保持側

Contextのハンドリングをしているのはこちら側

ここまでで接続保持側の終了処理が完了。

ストリーミング通信側

上記のように DeadlineExceeded ではないエラーが返り終了する。
ストリーミング通信側はContextを見るのではなく、接続保持側からの操作によって終了する。

解決策

Send でエラーが返ってきた場合、 Fatal 等で終了させずに break させて CloseAndRecv() まで実行してエラーハンドリングを行うと良い。
Send のエラーは警告くらいの気持ちで Print だけしておけば良いと思う。

   for {
        n, err := fs.Read(buf)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Could not read file: %v.", err)
        }

        if err := stream.Send(&pb.FileRequest{Name: filepath.Base(name), Data: buf[:n]}); err != nil {
            log.Printf("Could not send file: %v.", err)
            break
        }
    }

    res, err := stream.CloseAndRecv()
    if err != nil {
        log.Fatalf("Could not receive response file: %v.", err)
    }

変更箇所は2行。

       if err := stream.Send(&pb.FileRequest{Name: filepath.Base(name), Data: buf[:n]}); err != nil {
-          log.Fatalf("Could not send file: %v.", err)
+           log.Printf("Could not send file: %v.", err)
+           break
        }

するとこんな感じでログが出る。

2019/04/29 08:18:57 Could not send file: EOF.
2019/04/29 08:18:57 Could not receive response file: rpc error: code = DeadlineExceeded desc = context deadline exceeded.
exit status 1

これでタイムアウトのときに DeadlineExceeded が返るようになった。

まとめ

  • タイムアウトだった場合でも Send() のエラーは io.EOF が返る。
  • Send() でエラーが出てもそこで終了せずに CloseAndRecv() まで実行してエラーを確認。
  • タイムアウトの場合は CloseAndRecv() のエラーで DeadlineExceeded エラーが取れる。

私はgRPCを仕事でも使ったのだが、protoファイルを書くだけでサーバのインタフェースとクライアントのAPIコール関数を生成してくれる。
サーバはインタフェースの実装を書くだけだし、クライアントは関数を呼び出す感覚でAPIコールができる。
これはすごく楽なのではないだろうか。

特にマイクロサービスアーキテクチャを採用している場合にはおすすめです!