mise run 〇〇(タスクランナー)をfzfで実行しやすくする

miseのタスクランナーはmise run 〇〇で実行できます。これをfzfと連携してさくっと実行するzshのスクリプトを紹介します。

動作の例

筆者はmise run 何だったっけな~とタスク名を忘れて、その度に.mise.tomlを確認していました。
これをfzfを使って解決します。

実際に使っている例を見てみましょう。

次のようなファイルを対象に実行しました。

.mise.toml
[tasks.hello]
run = "echo 'hello'"
[tasks.echopath]
run = "echo $PATH | tr ':' '\n'"
[tasks.rev]
run = "rev"

mise runが初めての人

まだmise runを使ったこと無い人は、初回で次のようにエラーが出ます(2024年10月現在)。

mise `mise run` is experimental. Enable it with `mise settings set experimental true`
mise Run with --verbose or MISE_VERBOSE=1 for more information

書いてあるとおりmise runは実験的機能であるため、コマンドでその設定をオンにしておきましょう。

Terminal window
mise settings set experimental true

依存

yqとzshを使っていますが、どちらも別のものに置き換え可能です。

yqはtmolファイルからタスクを抽出するために使っています。
zshはキーバインドの割り当てと履歴を残すためにZLEを使っています。

実装の紹介

先にスクリプトの全体を紹介します。

function fzf_mise_tasks() {
if [ ! -e .mise.toml ]; then
echo 'fzf_mise_tasks'
echo 'There is no .mise.toml'
zle send-break
return 1
fi
if ! type yq > /dev/null; then
echo 'fzf_mise_tasks'
echo 'yq command is required'
zle send-break
return 1
fi
local tasks="yq -oj '.tasks | to_entries | .[] | .key + \" = \" + .value.run' .mise.toml 2>/dev/null || echo ''"
if [[ -z $tasks ]]; then
echo 'fzf_mise_tasks'
echo 'There is no tasks in .mise.toml'
zle send-break
return 1
fi
local selected=`eval $tasks | sed 's/^"//;s/"$//' | FZF_DEFAULT_OPTS='' fzf --height=50% --reverse --exit-0 | awk -F ' = ' '{ print $1}'`
zle reset-prompt
if [[ -z $selected ]]; then
return 0
fi
# 再実行したくなるときのために履歴に残して実行
BUFFER="mise run $selected"
zle accept-line
}
zle -N fzf_mise_tasks
bindkey "^Xm" fzf_mise_tasks

次のセクションから小分けにして順に解説します。

tomlファイルがない場合にエラー

.mise.tomlが無い場合はエラーにします。

if [ ! -e .mise.toml ]; then
echo 'fzf_mise_tasks'
echo 'There is no .mise.toml'
zle send-break
return 1
fi

send-breakを使わないと、エラーになったときに次のプロンプトが表示されません。

send-breakを使わなかった場合

send-breakを使えば、今のプロンプトを終了して新しいプロンプトに切り替わります。

send-breakを使った例

公式ドキュメントを読むとtaskだけ他のtomlファイルに切り出したりmiseのtomlファイルのパスもいろいろあるようですが、今回は.mise.tomlのみを対象としました。必要に応じて変更してください。

yqがなかったらエラー

上記と同様にyqがなかったらエラーにしています。

if ! type yq > /dev/null; then
echo 'fzf_mise_tasks'
echo 'yq command is required'
zle send-break
return 1
fi

タスクを抽出

yqを使って.mise.tomlからタスクを抜き出しています。

local tasks="yq -oj '.tasks | to_entries | .[] | .key + \" = \" + .value.run' .mise.toml 2>/dev/null || echo ''"

この段階ではまだ実行していないため、コマンド部分を改行して掲載します。

yq -oj \
'.tasks | to_entries | .[] | .key + " = " + .value.run' \
.mise.toml \
2>/dev/null \
|| echo ''

ざっくりとこんなステップです。

  1. yqを実行して.mise.tomlからtaskを抽出する
  2. yqでエラーがあったら捨てる
  3. エラーがあったら空文字にする

yqの実行後は次のように"タスク名 = コマンドの内容"のような文字列に変換されています。

抽出後
"hello = echo 'hello'"

yqのオプション-ojで出力をJSON形式にしています。こうしないとコマンドに改行を示す\nが入っている場合にうまく処理できないためです。

タスクがなければエラー

タスクがなかったらエラーにします。

if [[ -z $tasks ]]; then
echo 'fzf_mise_tasks'
echo 'There is no tasks in .mise.toml'
zle send-break
return 1
fi

文字列を整形とfzfの実行

local selected=`eval $tasks | sed 's/^"//;s/"$//' | FZF_DEFAULT_OPTS='' fzf --height=50% --reverse --exit-0 | awk -F ' = ' '{ print $1}'`

また長いコマンドが訪れたので改行したものを見てみましょう。

eval $tasks \
| sed 's/^"//;s/"$//' \
| FZF_DEFAULT_OPTS='' fzf \
--height=50% \
--reverse \
--exit-0 \
| awk -F ' = ' '{ print $1}'

yqの結果は"hello = echo 'hello'"のようにダブルクオートが付いているため、sedで葬ります。

fzf実行後、選択した文字列のうちタスク名の部分だけを抜き出します。

プロンプトを弄ってから実行

zle reset-prompt
if [[ -z $selected ]]; then
return 0
fi
# 再実行したくなるときのために履歴に残して実行
BUFFER="mise run $selected"
zle accept-line

プロンプトをリセットして、未選択だったらそのまま終了します。

最後に、履歴にmise run 〇〇という実行記録を残すためにプロンプトを書き換えてから実行します。

キーバインドのセット

キーバインドの設定も忘れずに。

zle -N fzf_mise_tasks
bindkey "^Xm" fzf_mise_tasks

以上、miseのタスクランナーとfzfの連携でした。

npm run 〇〇pnpmにも対応)についても書いてます。Node.jsを使う人にはおすすめです。
fzfでpackage.jsonのscriptsを選んで実行 | えいじのサイバー備忘録