Tmuxinatorの代替ツールをDenoで作った
Tmuxinator の代替CLIツール qtmut *1 を作りました。
- 1. これはなに
- 2. 使い方
- 3. なぜ自作したか
- 3.1. 普段のTmuxの利用方法
- 3.2. Tmuxinator の導入
- 3.3. Tmuxinator と qtmut の思想の違い
- 2. なぜDenoを選択したか
- 3. まとめ
*1:qtμt というマンガから取りました。https://manga.line.me/product/periodic?id=Z0000153
Git 2.0.0から2.33.1までのDockerイメージを作った
Git 2.0.0から2.33.1までの各バージョンのDockerイメージを作りました。
Git のバージョンごとの挙動を検証したかったのですが、丁度いいDockerイメージが無かったので作りました。
ベースイメージは扱いやすいようにUbuntuを採用し、マルチステージビルドを活用することで1つあたりの容量が小さくなっています (約130MB)。
イケてないところ
イメージのビルドをローカルマシン上で行っています。 1バージョンあたり2~4分かかるため、全191バージョンで7時間かかりました。
文脈を指定可能な自動展開されるglobal aliasを実現するZSHプラグイン zabrze を作った
ZSHにはglobal aliasという機能があります。 これは、行頭以外に入力された場合にも展開されるaliasです。
$ alias -g B='"$(git symbolic-ref --short HEAD)"' $ git push -u origin B # → git push -u origin [現在のブランチ名]
使いこなせれば便利そうな一方で、global aliasはグローバル名前空間を汚染するため、導入には慎重さを要します*1 。
また、コマンドヒストリーには通常のaliasと同様にglobal aliasの展開前の入力が保存されます。
これら2つの問題点を解決することを目的に、zabrze というZSHプラグインを作成しました。
これはなに
zabrze *2 は aliasをYAMLで記述できるようにするZSHプラグインです。
1. aliasの自動展開
例えば下のような設定ファイルを ~/.config/zabrze/config.yaml
に記述した場合、行頭で g<スペース>
と入力したとき、即座に git
と展開されます *3 。
また、null<スペース>
と入力した場合は行頭以外でも >/dev/null
に展開することができます。
# ~/.config/zabrze/config.yaml abbrevs: # alias - name: git abbr: g snippet: git # global abbrev - name: '>/dev/null' abbr: null snippets: '>/dev/null' global: true
はてなブログ、動画があげられん pic.twitter.com/tFOIJ7CZgg
— 真の虚無感 (@Ryooooooga) 2021年8月15日
2. 文脈を指定した global alias
さて、ここまでであれば後述する既存のZSHプラグインによってすでに同様の機能が実現されています。
zabrze ではaliasの自動展開に加え、展開の有無をその文脈によって切り替えられるようになっています。
# git コマンドを入力しているときのみ展開されるglobal alias - name: branch abbr: B snippet: $(git symbolic-ref --short HEAD) evaluate: true # snippetの文字列をスクリプトとして解釈する global: true context: '^git ' # 展開される文脈を正規表現で指定する
上のような global alias B
は、git
コマンドを入力しているときのみ展開されます。
$ git push -u origin B<エンター> # ↓ に展開され、実行される # git push -u origin [現在のブランチ名] $ echo B<スペース> # ↑ は展開されない
これにより、global alias自体の有用性を保ったままグローバル名前空間の汚染を避けることができます。
類似のZSHプラグイン
zsh-abbrev-alias
zsh-abbrev-alias は自動展開される alias コマンド、abbrev-alias
コマンドを追加するシンプルなプラグインです。
zeno.zsh
zeno.zsh は Deno 製のZSHプラグインです。 abbrev-alias相当の機能に加え、pmyのような補完機能を有しています。
一方、実行にはDenoのインストールを要求し、またスペースを押したときの動作が重い印象があります (おま環かも)。
開発のモチベーション
以前は zsh-abbrev-alias を利用していましたが、Gitなどのサブコマンドにabbrev-aliasを適用できないことを不満に思っていました。
$ abbrev-alias g=git $ g<SP> # git に展開される $ abbrev-alias -g c=commit $ git c<SP> # git commit に展開されるがグローバル名前空間の汚染がきびしい $ git config alias.c commit $ git c # history には `git commit` で記録されてほしい
現在は以下のような zabrze の config.yaml によって Git aliases の自動展開を実現しています。
Sublime Text 4で追加された設定など
昨日 (2021/05/21)、Sublime TextのメジャーアップデートであるSublime Text 4がリリースされました。
www.sublimetext.com forest.watch.impress.co.jp
デフォルトのカラースキームがMonokaiからMarianaに変わったり、Open GLによるGPUアクセラレーションが有効になったりしましたが、 他にもちょっとした設定項目の追加などがあったため、便利だと感じたものを紹介します。
目次
互換性
Sublime Text 4は、メジャーアップデートではあるものの設定ファイルやパッケージなどの互換性は維持されています。 私のインストールしている以下のようなパッケージは特に問題なく動作しています。
{ "bootstrapped": true, "in_process_packages": [ ], "installed_packages": [ "A File Icon", "AdvancedNewFile", "All Autocomplete", "BracketHighlighter", "Case Conversion", "Clang Format", "CMake", "DoxyDoxygen", "EditorConfig", "FindKeyConflicts", "Git", "GitGutter", "Increment Decrement", "Increment Selection", "LSP", "Origami", "Package Control", "PackageResourceViewer", "ProjectManager", "SideBarEnhancements", "Smart Delete", "Sublime Files", "SublimeGHQ", "SyncedSideBar", "TodoReview", "TOML" ], "repositories": [ "https://github.com/Ryooooooga/SublimeGHQ" ], }
また、macOSの場合設定ファイルの保存先は ~/Library/Application Support/Sublime Text 3/
から ~/Library/Application Support/Sublime Text/
に変更されました。
ただし、~/Library/Application Support/Sublime Text/
が存在しない場合は ~/Library/Application Support/Sublime Text 3/
以下の設定が読み込まれるため、
移行にあたっての対応は特に必要ありません。
オートコンプリートの強化
オートコンプリート機能の刷新に伴ってオートコンプリートに関する設定項目が複数追加されました。
使用感としてはSublime Text 3とほぼ変わりがないものの、補完候補の種類 (スニペット、関数、型など) に対応したアイコンが表示されるようになり、単純にわかりやすくなりました。 また、補完候補の定義へのジャンプなどが可能になりました。
便利な設定の追加
Sublime Text 4になり、いくつか設定項目が追加されました。 うち、特に便利だと感じたものを紹介します。
draw_white_space
のカスタマイズ性の向上
draw_white_space
は空白文字の可視性を設定するための項目です。
draw_white_space
自体は Sublime Text 3から存在していましたが、以下の3種類の設定のいずれかしか指定できませんでした。
"none"
: 空白文字を表示しない"all"
: すべての空白文字を常に可視化する"selection"
: 選択範囲のみ空白文字を可視化する
Sublime Text 4 ではより複雑な可視性の制御が行えるようになりました。
// Controls when white space is drawn. Any of the following options may be // combined: // - "selection": Draw white space under the current selection. // - "leading": Draw any white space between the beginning of a line and the // first character. // - "enclosed": Draw white space enclosed by other characters. // - "trailing": Draw white space following the last character on a line. // - "isolated": Draw white space on lines containing no other characters. // - "all": All of the above, ie. always draw white space. // // These options may be further refined by appending any of the following // separated by an underscore: // - "none": Don't draw this kind of white space. // - "tabs": Only draw tabs here. // - "spaces": Only draw spaces here. // - "mixed": Only draw white space that does not match the indentation // style. For example if "translate_tabs_to_spaces" is true only // draw tabs. // - "mixed_tabs": Like "mixed" but only draw tabs. // - "mixed_spaces": Like "mixed" but only draw spaces. // - "all": Draw both tabs and spaces. This is the default. // // Note that options are applied in sequence. So a later option may override // an earlier one. // // Examples: // - ["selection", "trailing", "isolated"]: // Draw white space at the end of any lines and under the selection. // // - ["all_tabs", "selection"]: // Draw tabs anywhere and any white space under the selection. // // - ["all_mixed"]: // Draw any white space that does not match the indentation style. // // - ["leading_mixed", "isolated_mixed"]: // Draw any indentation that does not match the indentation style. // // - ["selection_mixed_tabs"]: // Draw only tabs under the selection and only if the indentation style // is spaces. // // - ["all_tabs", "selection"]: // Draw all tabs and any white space under the selection. // // - ["all", "selection_none"]: // Inverse of the default. Draw white space everywhere except under the // selection. "draw_white_space": ["selection"],
例えば、私の場合は以下のように設定しています。
"draw_white_space": [ "all_tabs", // すべてのタブ文字を表示する "selection", // 選択範囲の空白文字を可視化する "trailing", // 行末の空白文字を可視化する ],
ただし、設定の記述順によって表示が変わったり、そもそもの指定方法がわかりにくかったりとなかなかに難解です。(ちょっとだめなところがSublime Textっぽくてかわいいね)
"ignored_snippets"
の追加
"ignored_snippets"
はSublime Text 4から追加された、スニペットを無効化するための項目です。
// A list of wildcard patterns specifying which snippet files to ignore. // For example, to ignore all the default C++ snippets, set this to // ["C++/*"] "ignored_snippets": [],
Sublime Text 3でデフォルトで組み込まれているパッケージ (C++
パッケージなど) のスニペットを無効化したい場合、
PackageResourceViewer
などを使ってデフォルトスニペットファイルを抽出し、
それらのスニペットファイルの中身を削除する などの作業が必要でした。
Sublime Text 4では、"ignored_snippets"
にスニペットのファイル名を追加することで無効化できるようになりました。(なにげにこれが一番うれしかった)
// デフォルトのC++スニペットをすべて無効化する "ignored_snippets": [ "C++/*", ],
"hardware_acceleration"
の追加
"hardware_acceleration"
はGPUによるハードウェアアクセラレーションの有効化に関する設定項目です。
デフォルトではmacOSでのみOpenGLによるハードウェアアクセラレーションが有効になっています。
// Enables hardware accelerated rendering. This moves rendering to your GPU, // allowing for faster rendering at higher resolutions. Changing this // setting requires an application restart to take effect. // - "none": Performs CPU rendering. // - "opengl": Uses OpenGL for rendering. Minimum required version is 4.1 // // On Mac, this value is overridden in the platform specific settings. "hardware_acceleration": "none",
"relative_line_numbers"
の追加
"relative_line_numbers"
を true
に設定すると、行番号が相対表示になります。 (Vimのset relativenumber
のような感じ)
// relative_line_numbers will draw each line number as the distance from // the current line. Useful in conjunction with Vintage. "relative_line_numbers": false,
ST4のbeta版を使っていた身としては正式リリースは嬉しい限りですが、いくらなんでも遅すぎませんかね? 日本でSublime Text使ってる人はもはや私しかおらないぞ。
Dockerでカスタムsubcommandをあれする
Gitでは git-foo
のような名前のスクリプトファイルを PATH
の通っている位置に置いておくと、 git foo
とするだけで呼び出せる便利な仕組みがあります。
本記事の趣旨はDockerでもそんな感じのことをしたいよね。というあれです。
実装
で、以下のような関数を .zshrc
なりに書いて置くとGitのような感じでsubcommandを探索してくれます。
docker() { if [ "$#" -eq 0 ] || ! command -v "docker-$1" > /dev/null; then command docker "$@" elif (( ${+aliases[docker-$1]} )); then eval "${aliases[docker-$1]} ${(q)@:2}" else "docker-$1" "${@:2}" fi }
この関数 docker
を用いて docker foo
のようにサブコマンドを呼び出した場合、
docker-foo
」のようなコマンドやエイリアスが存在していない場合は生のdocker
コマンドに引数を渡すdocker-foo
というエイリアスが存在していればdocker-foo
に残りの引数を渡す1docker-foo
というスクリプトか関数が存在していればdocker-foo
に残りの引数を渡す
というような感じであれしてくれます。
自分は以下のようなエイリアスを使っています。
# docker run のエイリアス alias docker-ri='command docker run -it' alias docker-rrm='command docker run --rm' alias docker-rrmi='command docker run --rm -it' # exitedなプロセスを全部 rm する docker-clean() { command docker ps -aqf status=exited | xargs -r docker rm -- } # タグ付けされていないイメージを全部 rmi する docker-cleani() { command docker images -qf dangling=true | xargs -r docker rmi -- } # 引数なしで docker rm すると削除するプロセスを fzf で選択できる docker-rm() { if [ "$#" -eq 0 ]; then command docker ps -a | fzf --exit-0 --multi --header-lines=1 | awk '{ print $1 }' | xargs -r docker rm -- else command docker rm "$@" fi } # 引数なしで docker rmi すると削除するイメージを fzf で選択できる docker-rmi() { if [ "$#" -eq 0 ]; then command docker images | fzf --exit-0 --multi --header-lines=1 | awk '{ print $3 }' | xargs -r docker rmi -- else command docker rmi "$@" fi }
余談
docker HOGE で docker-HOGE が起動するようにしてるのでdocker composeができる
— りょがまや (@Ryooooooga) 2020年12月16日
それでいいのか?
テンプレートから新規ファイルを作成するCLIツール zouch を作った
例えば新しいプロジェクトを作成するとき、何かと新しいファイルを作成する必要があります。
README.md, LICENSE, .editorconfig, あるいはC/C++ならCMakeLists.txtやmain.cppなどをプロジェクトに含める必要があるでしょうが、 そのうちいくつかは毎度似たようなファイルを「お約束」として用意することでしょう。
私の場合はそれらを既存のプロジェクトからコピーしてきたり、もしくは touch
で空のファイルを作成したあとにVSCodeやSublime Textといったエディタのスニペット機能を用いて中身を入力することが多いです。
しかし、touch
して〜、エディタで開いて〜、というのも結構めんどくさいので touch
した時点でファイルの中身も自動生成してくれればなにかと便利そうです。
これはなに
zouchを使用すると、予め登録しておいたテンプレートからファイルを作成できます。
$ cat LICENSE.md cat: LICENSE.md: No such file or directory $ zouch LICENSE.md $ cat LICENSE.md # MIT License (MIT) Copyright (c) 2021 Ryooooooga Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal ...
上のようなMITライセンスのテキストファイルは、~/.config/zouch/templates
に配置された以下のようなテンプレートから作成されます。
# MIT License (MIT) Copyright (c) {{Now.Year}} {{Shell `git config user.name`}} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal ...
zouchではGo言語のテンプレートエンジンである text/template の記法を用いて出力内容をある程度動的に変更できるようになっています。
上の例で言うと {{ ... }}
に囲まれている部分 (西暦N年、ユーザ名) が出力時にはいい感じに展開されているのがわかると思います。
また、touch
本来の機能であるタイムスタンプの更新もできるようになっています。1
テンプレートファイルの探索
zouchでは /path/to/foo.txt
というファイルを生成する際にまず ~/.config/zouch/templates/foo.txt
というファイルを探し、
これが存在していればそれをテンプレートファイルとして用います。
foo.txt
が ~/.config/zouch/templates/
以下に存在しないときは、代わりに _.txt
という名前のファイルを探し、これを代わりにテンプレートファイルとして用います。
これによって、"特定の拡張子のファイルはすべて同じテンプレートから生成したい" という要求を解決します。
以下に例として、.go
ファイルのテンプレートを示します。
{{ if HasSuffix .Filename "_test.go" -}} package {{.Filename | Abs | Dir | Base}}_test import ( "testing" ) func Test{{RegexReplaceAll .Filename `_test\.go$` "" | Base | UpperCamelCase}}(t *testing.T) { } {{ else -}} package {{.Filename | Abs | Dir | Base}} {{ end -}}
_.go
の例
このテンプレートは、foo/bar.go
というファイルを生成しようとした場合は
package foo
というファイルを、foo/bar_test.go
というファイルを生成しようとした場合は
package foo_test import ( "testing" ) func TestBar(t *testing.T) { }
というファイルをそれぞれ出力します。
インストール方法
go get
$ go get github.com/Ryooooooga/zouch
- zinit (プリビルトバイナリのダウンロード)
# ~/.zshrc zinit ice lucid wait"0" as"program" from"gh-r" \ pick"zouch*/zouch" zinit light 'Ryooooooga/zouch'
類似のツール
類似のツールに touch-alt と touch_erb があります。
touch-alt
touch-alt は Node.js 製のCLIツールです。 テンプレートエンジンなどは使用せず、単にテンプレートファイルをコピーしてくるだけのシンプルなツールです。
touch_erb
touch_erb は Ruby 製のCLIツールです。 ERBを採用することで柔軟なファイルの生成が可能になっています。
zouchはこのtouch_erbから着想を得て (というか丸パクし) 作成しました。
touch_erbを用いず、わざわざ自作したのは以下のような理由のためです。
zouch の作成にあたって
zouchの実装言語にはGoを選択しました。
以下のような言語も選択肢には入っていましたが、シングルバイナリにコンパイルできること、
テンプレートエンジンの言語標準として text/template
ライブラリが存在していることが選定の決め手となりました。3
- Shellスクリプト: 処理系のインストールが不要 / 書くのが辛い
- Perl: 処理系のインストールが不要 / わからない
- Node.js: dotfiles内でインストールしている /
npm i -g
したくない - Python: dotfiles内でインストールしている /
pip install
したくない, 書きたくない - C++ (+ Luaなどの組み込みスクリプト): シングルバイナリにコンパイルできる / 実装コストが重い
- Rust: シングルバイナリにコンパイルできる / テンプレートエンジンとして標準的なものが存在しない
一方、テンプレート内で任意のRubyスクリプトを書けるERBとは違い、 Goのtext/template
の表現力は極めて限定的です。
例えば、テンプレート内で使用できる関数は (素朴な実装に於いては) コンパイル時に登録されたものに限られます。
そこで、Shell
関数を通じて任意のシェルスクリプトを実行できるようにすることで表現力の低さを補うことにしました。
テストを書きやすいようにパッケージの分割を工夫したりはしたものの、この手のCLIツールはテストを書くのがすごく面倒です。 あとドキュメントを書くのも面倒。
それからGoの time.Time.Format()
の仕様を考えた人は早く謝罪の言葉を考えたほうがいい。
fzf+pmyでzshの補完をカスタマイズする
最近 relastle/pmy というツールにハマっています。
— キラキラの虚無感 (@Ryooooooga) 2020年12月25日
(邪悪な正規表現を代償に)いろいろできる pic.twitter.com/Q0GMzD76zT
— キラキラの虚無感 (@Ryooooooga) 2020年12月25日
Zshの補完機能の欠点
zshの補完機能は強力ですが、カスタマイズ性や拡張の容易さに難があります。
また、やろうとおもえば補完関数内でfzfなどのFuzzy Finderを用いてインタラクティブな補完を実現できますが、fzfの--multi
オプションなどを用いた複数結果の展開などはできません。
デフォルトの補完機能 (ZLEのexpand-or-complete
)を上書きするような形でそれらも実現はできますが、やはり記述の複雑さや保守性の面からつらいものがあります。
そこで、pmyというCLIツールとfzfを組み合わせてインタラクティブな補完を実現しました。 利用時の様子は上のツイートのとおりです。
fzf
fzfはGo製のFuzzy Finderです。
日本では同じくGo製のFuzzy Finderであるpecoの方が知名度が高い印象がありますが、preview windowはpecoにはない魅力です。
preview windowは、任意のコマンドなどを用いて検索候補のプレビューを表示できる機能です。
上の図ではbatを用いてシンタックスハイライト付きでファイルの中身を表示しています。他にもls
やtree
のようなコマンドを使ってディレクトリの構造を表示する、あるいはgit show
でコミットの情報を表示するなど、用途の幅が広い便利な機能です。
私の場合は以下のような用途にfzfを用いていました。
history
を使ったコマンドヒストリーの検索chpwd_recent_dirs
を使ったディレクトリ移動履歴の検索ghq
を使ったリポジトリ間の移動- テキストエディタを開く際のファイルの選択
docker rm
やdocker rmi
の際に削除するイメージの選択
1 ~ 3 はFuzzy Finderの利用例で頻繁に挙げられる用途なので特筆すべきことはありません。
4 に関しては以下のような$EDITOR
(私の場合はnvim) のラッパー関数 e
を用意していました。
e() { if [ $# -eq 0 ]; then local selected="$(fd --hidden --color=always --exclude='.git' --type=f | fzf --exit-0 --multi --preview="fzf-preview-file '{}'" --preview-window="right:60%")" [ -n "$selected" ] && "$EDITOR" -- ${(f)selected} else command "$EDITOR" "$@" fi }
これは、e
に引数が指定されていた場合は通常通りに $EDITOR
を起動し、引数なしで呼び出された場合にはカレントディレクトリ以下のファイルから fzf で選択したものを $EDITOR
の引数に与えるものです (かなり便利)。
これまでの不満点
上のような fzf の活用は、実際に便利で重宝していたのですが、若干の物足りなさを感じていました。 具体的には以下のような点です。
e
で選択したファイルがコマンドヒストリーに記録されない上の関数ではどのようなファイルを選択してもコマンドヒストリーに
e
しか残りません。 そのため、以前に開いたファイルを再び開こうと思った場合にも再度検索を行わなければならず、煩わしさを感じていました。git switch
などの既存のコマンドの補完にfzfを用いたいGitなどの既存のコマンドやそのサブコマンドの補完にfzfを用いたい場合、逐一ラッパースクリプトを用意しなければならず面倒でした。
余談
fzfには補完用のスクリプトが用意されており、これを使用することでfzfを使った補完が可能になっていました。
参考: https://github.com/junegunn/fzf#fuzzy-completion-for-bash-and-zsh
また、この機構を利用してGitなどの補完を実現したプロジェクトに chitoku-k/fzf-zsh-completions があります。
今回、これらのような既存のものを利用しなかったのは以下の理由のためです。
- 自分用の環境に合わせてカスタマイズするのが面倒
- 補完のトリガーである
**<TAB>
というシーケンスに慣れない
pmy
というわけで本題です。
pmyはFuzzy Finderを用いたzshの補完を実現するためのCLIツールです。
YAML (もしくはJSON) で設定ファイルを書くことにより、冒頭で示したようなfuzzyな検索と補完が可能になります。
インストール方法
go get
を用いたビルド方法
$ go get -u github.com/relastle/pmy $ eval "$(pmy init)"
- zinit を用いたプリビルドバイナリのダウンロード
zinit ice lucid wait"0" as"program" from"gh-r" \ pick"pmy*/pmy" \ atload'eval "$(pmy init)"' zinit light 'relastle/pmy'
設定ファイルの編集
pmyは、デフォルトでは ~/.pmy/rules
以下に配置された設定ファイルを参照します。
例えば、冒頭のツイートのような補完は下の設定で再現できます。
# ~/.pmy/rules/_.yaml - description: editor regexp-left: ^\s*(?P<cmd>(nvim|vim|vi))\s+(?P<args>\S+\s+)*)(?P<query>\S*)$ cmd-groups: - stmt: fd --color=always --hidden --type=f after: paste -s -d ' ' - fuzzy-finder-cmd: fzf --multi --preview="bat {}" --query=<query> buffer-left: '<cmd> <args>' buffer-right: '[]'
上の場合、ファイルパスやその一部を雑に入力しただけで候補に出現するため、通常のzsh補完よりも効率的に目的のファイルにたどり着けます。
他にも以下のような設定でpmyを運用しています。
https://github.com/Ryooooooga/dotfiles/tree/main/config/pmy/rules
設定ファイルの参照ディレクトリなどの変更
pmy はデフォルトでは ~/.pmy
以下を作業ディレクトリとして用います。
しかし、私はホームディレクトリが雑多なdotfilesで汚れることをあまり好ましく思っていません。
幸いにも、pmyは環境変数によって作業ディレクトリを変えられるため、XDG風のファイルの配置に変更しました。 また、補完のトリガーになるキー(デフォルトではCtrl+Space)も変えられます。
# 補完のトリガーを変更 (Ctrl+P) export PMY_TRIGGER_KEY="^P" # ルールファイルの参照先を変更 export PMY_RULE_PATH="$XDG_CONFIG_HOME/pmy/rules" export PMY_SNIPPET_PATH="$XDG_CONFIG_HOME/pmy/snippets" # ログファイルの出力先を変更 export PMY_LOG_PATH="$XDG_CACHE_HOME/pmy/log.txt" eval "$(pmy init)"
pmyの利点
pmyを利用することには以下のような利点があります。
pmyの欠点
一方で、pmyもまた万能ではありません。 しばらく利用してみて感じた欠点を以下に挙げます。
pmyはかなり独特でおもしろいツールなので、fzfがすきなzshユーザの方は一回試してみるといいんじゃないでしょうか。