VimConf 2023 Tiny に行ってきた
昨日(11/18)はVimConfに行ってきました。
前回が2019年だったので実に4年ぶりの開催でした。
2017年から2019年は3回ともスピーカーでしたが、今回は久しぶりの一聴衆でした。
なので当日は全く緊張する必要がなく、とても穏やかな気持ちで参加することができました。
また、休憩時間や懇親会などで久しぶりに会えた人と話をすることができ、とても懐かしい気持ちになりました。
そして古巣の皆さんのVim愛は変わらないどころか、さらに強まっていて感慨深かったです。
ただ後になってみると、新しい所属でのVim活なんかをLTしても良かったかなと思ったりもしました。
この感覚はVimConf 2016に参加した時に似ていて、あの時も「自分も登壇したい!」という気持ちになったことを覚えています。
最後に、発表者とスタッフの方に改めて大きな感謝を伝えたいです。
特にスタッフの方は限られたリソースの中でとても大変だったと思いますが、今までのVimConfと変わらない体験を得ることができました。
来年も楽しみにしています。
gnosticでOpenAPIをProtocol Buffersに変換する
gnosticとgnostic-grpcを使えばOpenAPIの.yaml(.json)を.protoに変換できる。
1. gnosticをインストールする
go install github.com/google/gnostic@latest
2. gnostic-grpcをインストールする
git clone https://github.com/google/gnostic-grpc
cd gnostic-grpc
./COMPILE-PROTOS.sh
./plugin-creation.sh
※ protocコマンドがないとエラーになるので Protocol Buffer Compiler Installation | gRPC あたりを参考にインストールしておく必要がある。
3. 変換する
gnostic --grpc-out=proto openapi.yaml
- openapi.yaml
openapi: 3.0.3 info: description: Foo API Document. title: Foo API version: "1.0" paths: /foo: post: operationId: PostFoo requestBody: content: application/json: schema: $ref: '#/components/schemas/PostFooRequest' responses: "201": content: application/json: schema: $ref: '#/components/schemas/Foo' description: Created "400": content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' description: Bad Request /foo/{id}: get: operationId: GetFoo parameters: - in: path name: id required: true schema: type: string responses: "200": content: application/json: schema: $ref: '#/components/schemas/Foo' description: OK "404": content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' description: Not Found components: schemas: ErrorResponse: properties: code: type: string message: type: string type: object Foo: properties: data: type: string id: type: string type: object PostFooRequest: properties: data: type: string type: object
次のようなprotoになる。
- proto/openapi.proto
syntax = "proto3"; package openapi; import "google/api/annotations.proto"; import "google/protobuf/descriptor.proto"; import "google/protobuf/empty.proto"; option go_package = ".;openapi"; message PostFooRequest { string data = 1; } //PostFooParameters holds parameters to PostFoo message PostFooRequest { PostFooRequest post_foo_request = 1; } //GetFooParameters holds parameters to GetFoo message GetFooRequest { string id = 1; } service Openapi { rpc PostFoo ( PostFooRequest ) returns ( google.protobuf.Empty ) { option (google.api.http) = { post:"/foo" body:"post_foo_request" }; } rpc GetFoo ( GetFooRequest ) returns ( google.protobuf.Empty ) { option (google.api.http) = { get:"/foo/{id}" }; } }
※ OpenAPIの構成によっては変換できない場合がある。
また、OpenAPI 3.1には対応していない。
github.com/swaggest/openapi-goを使ってOpenAPIを生成する
github.com/swaggest/openapi-goを使うとGoのコードからOpenAPIの定義(json/yaml)を生成できる。
例:
package main import ( "encoding/json" "fmt" "log" openapi "github.com/swaggest/openapi-go" "github.com/swaggest/openapi-go/openapi3" ) type endpoint struct { id string method string path string request any responses []response } type response struct { status int body any } func main() { r := openapi3.Reflector{ Spec: &openapi3.Spec{ Openapi: "3.0.3", Components: &openapi3.Components{}, }, } r.Spec.Info. WithTitle("Foo API"). WithDescription("Foo API Document."). WithVersion("1.0") list := []endpoint{ { id: "PostFoo", method: "POST", path: "/foo", request: new(PostFooRequest), responses: []response{ {status: 201, body: new(Foo)}, {status: 400, body: new(ErrorResponse)}, }, }, { id: "GetFoo", method: "GET", path: "/foo/{id}", request: new(GetFooRequest), responses: []response{ {status: 200, body: new(Foo)}, {status: 404, body: new(ErrorResponse)}, }, }, } for _, v := range list { oc, err := r.NewOperationContext(v.method, v.path) if err != nil { log.Println(err) continue } oc.SetID(v.id) oc.AddReqStructure(v.request) for _, resp := range v.responses { oc.AddRespStructure(resp.body, openapi.WithHTTPStatus(resp.status)) } if err := r.AddOperation(oc); err != nil { log.Println(err) continue } } // b, err := r.Spec.MarshalYAML() b, err := json.MarshalIndent(r.Spec, "", " ") if err != nil { log.Println(err) return } fmt.Println(string(b)) } type PostFooRequest struct { Data string `json:"data"` } type GetFooRequest struct { ID string `json:"id" path:"id"` } type Foo struct { ID string `json:"id"` Data string `json:"data"` } type ErrorResponse struct { Code string `json:"code"` Message string `json:"message"` }
list
の中身を既存コードから生成すれば途中からでも比較的簡単にOpenAPIを始められる。
macvimをビルドする(2023/08)
だいぶ前にも書いていた。
ここ最近は毎朝UpdateMacVim
を実行している。*1
UpdateMacVim() { cd $HOME/go/src/github.com/macvim-dev/macvim git fetch origin master if [ -z "`git diff FETCH_HEAD --shortstat`" ]; then cd - return 0 fi git merge FETCH_HEAD make distclean && ConfigureMacVim && make rm -rf $HOME/.local/MacVim.app cp -R src/MacVim/build/Release/MacVim.app $HOME/.local/ cd - }
ConfigureMacVim() { ./configure \ --enable-fail-if-missing \ --with-features=huge \ --enable-terminal \ --enable-multibyte \ --enable-python3interp \ --enable-luainterp \ --with-lua-prefix="$(brew --prefix lua)" \ --enable-cscope \ --with-tlib=ncurses \ --with-compiledby="daisuzu <daisuzu@gmail.com>" \ CFLAGS="-I$(brew --prefix)/include" \ LDFLAGS="-L$(brew --prefix)/lib" \ --prefix=$HOME/.local "$*" }
あとは$HOME/.local/MacVim.app/Contents/bin
にパスを通しておけばOK。
Goの外部パッケージに独自の変更を加える
外部パッケージを使っていて、ちょっとした修正を試したい時は以下のような方法があります。
- 1. 外部パッケージをForkしてgo.modで置き換える
- 2. 外部パッケージのコピーをリポジトリに追加してgo.modで置き換える
- 3. 変更したファイルをリポジトリに追加してoverlayで書き換える
1. 外部パッケージをForkしてgo.modで置き換える
最も基本的なやり方なので特に理由がなければこちらの方法にするのが良いでしょう。
対象のパッケージのForkに変更を加え、以下のようにしてgo.modで置き換えます。
go mod edit -replace github.com/EXTERNAL_/PACKAGE@v1.0.0=github.com/daisuzu/PACKAGE@development go mod tidy
なお、Forkにコミットを追加する場合はその度にgo mod tidy
で擬似バージョンを更新していく必要があります。
2. 外部パッケージのコピーをリポジトリに追加してgo.modで置き換える
Forkを作りたくなかったり、試行錯誤したい場合などはリポジトリの中に対象の外部パッケージをコピーする方がやりやすいかもしれません。
その場合も同様にgo.modで置き換えます。
go mod edit -replace github.com/EXTERNAL_/PACKAGE@v1.0.0=./PATH_TO_COPY
3. 変更したファイルをリポジトリに追加してoverlayで書き換える
変更が数行程度だと上記の方法が面倒だと感じることがあるかもしれません。
その場合は変更したファイルのみをリポジトリに追加し、goコマンドのoverlayフラグで書き換えることも可能です。
以下のようなoverlay.json
を用意してgo build -overlay=overlay.json
のように指定します。
{ "Replace": { "/GO_MOD_CACHE/PATH_TO_EXTERNAL_PACKAGE/TARGET.go": "/PATH_TO_COPY/TARGET.go" } }
いずれの方法もうまくいったら本家に還元しましょう。
Vimの極意
この記事はVim Advent Calendar 2022の1日目の記事です。
今年でVimをメインエディタにして15年になります。
最近どうすれば思考する速度でテキストを編集できるようになる*1のか考えたりすることがあったので、この機会に軽くまとめてみます。
簡単な操作であれば「○○をしたい」と思った瞬間にそうなっていることもありますが、実際はそうならないことがの方が多いです。
それが何故なのかというと、複雑な編集をする際には自分のやりたいことをVimの操作に変換する必要があり、そこに時間がかかっているからだと考えました。
そこで思いついたのが、やろうとしていること自体をVimのコマンド群として捉えられるようになればさらに高速にテキストを編集できるのではないか、ということです。
具体例をあげてみると、以下のようなGoのコードでカーソルがfuncのf
にある時に、戻り値の型をClientInterface
から*Client
に変更したいと思ったら、
func NewClient() ClientInterface { return newDefaultClient() }
戻り値の位置にカーソルを移動して、カーソル下の単語を*Client
に書き換えよう、と考えてVimを操作するのではなく、
最初から$b
、cw*Client
と考えてVimを操作します。
つまり、「あらゆる編集操作をVimのコマンドで表現できるようになる」ことがVimの極意ということになります。
実際に文章に書いて読んでみるとかなり難しそうな気がしてきましたが、
これを会得する方法としては以下の7つが考えられます。
- 極意のことを意識しながらVimを使い続ける
- 定期的にVimのヘルプを読み返してコマンドとしての語彙を増やす
- 指に負担を感じたら少し立ち止まってより良い方法がないか調べる
- VimGolfをやる
- 覚えにくい処理をユーザー定義コマンドやマッピングにする
- vimrcを育てる
- 4で汎用性が高いものをプラグインにする
- 既に似たようなプラグインがあればそれを使っても良い
- 5で有用なものをVimの本体に組み込む
- Vim本体の実装に対する知識が必要
ある程度身に付いてくれば先のサンプルコードで戻り値の型がわからなくてもすぐに
/newD<CR><C-]>Wyiw<C-T>j%bcw<C-R>0<ESC>
が出てくるようになることでしょう。
ぜひ試してみてください。
goplsに独自Analyzerを組み込む
internal/lsp/source/options.go
のdefaultAnalyzers()が返すmapに自作のAnalyzerを追加してgo install
すれば使えるようになる*1。
用途としてはチームのコーディング規約をtextDocument/diagnostic
でチェックしたり、チェックに引っかかったコードの修正や一部だけ実装したコードの続きを生成するtextDocument/codeAction
を実行したりなど。
今まではCIで独自Analyzerとreviewdog + action-suggesterを用いてチェックやコード修正を行っていたものを、goplsに組み込むことによってより早いコーディングのタイミングで行えるようになった。
もちろん補完やコードジャンプ、コード生成(textDocument/codeLens
)などをカスタマイズしてさらに便利にすることもできるけど、本体の変更に追従するのが大変になりそうなので今のところはやっていない。
Analyzerの追加だけであればコンフリクトのことはほとんど考えなくて良いので定期的にupstreamを取り込むように設定するくらいでメンテナンスに関しては特に問題なさそう。
それにgoplsのCLI版で使えない機能を増やしすぎてしまうと、GoLandなどLSPに対応していないツールと差が開きすぎてしまうのも理由の一つ*2。