mise run 〇〇(タスクランナー)をfzfで実行しやすくする
miseのタスクランナーはmise run 〇〇
で実行できます。これをfzfと連携してさくっと実行するzshのスクリプトを紹介します。
2024年12月12日追記!
miseのtomlファイルが複数ある場合やタスクのrun
が配列の場合にも対応しました。
動作の例
筆者はmise run 何だったっけな~
とタスク名を忘れて、その度にtomlファイルを確認していました。
これをfzfを使って解決します。
実際に使っている例を見てみましょう。
次のようなファイルを対象に実行しました。
[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
は実験的機能であるため、コマンドでその設定をオンにしておきましょう。
mise settings set experimental true
依存
yqとzshを使っていますが、どちらも別のものに置き換え可能です。
yqはtmolファイルからタスクを抽出するために使っています。
zshはキーバインドの割り当てと履歴を残すためにZLEを使っています。
実装の紹介
先にスクリプトの全体を紹介します。
function fzf_mise_tasks() { # mise settings set experimental true # 検索対象のファイルリスト local files=( ".mise.local.toml" "mise.local.toml" ".mise.toml" "mise.toml" "mise/config.toml" ) local found_files=() for file in "${files[@]}"; do if [[ -e $file ]]; then found_files+=("$file") fi done
if [[ ${#found_files[@]} -eq 0 ]]; then echo 'fzf_mise_tasks' echo 'No target mise file found' 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_list=() for file in "${found_files[@]}"; do local file_tasks=$(yq -oj '.tasks | to_entries | .[] | .key + " = " + ( select(.value.run| type == "!!str") .value.run, select(.value.run| type == "!!seq") .value.run | join(" && "))' "$file" 2>/dev/null || echo '') if [[ ${file_tasks:-null} != null ]]; then tasks_list+=("$file_tasks") fi done local tasks=$(printf "%s\n" "${tasks_list[@]}")
if [[ -z $tasks ]]; then echo 'fzf_mise_tasks' echo 'There is no tasks in .mise.toml' zle send-break return 1 fi local selected=`echo "$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_tasksbindkey "^Xm" fzf_mise_tasks
次のセクションから小分けにして順に解説します。
tomlファイルがない場合にエラー
miseで扱うtomlファイルが無い場合はエラーにします。
local files=( ".mise.local.toml" "mise.local.toml" ".mise.toml" "mise.toml" "mise/config.toml" )local found_files=()for file in "${files[@]}"; do if [[ -e $file ]]; then found_files+=("$file") fidone
if [[ ${#found_files[@]} -eq 0 ]]; then echo 'fzf_mise_tasks' echo 'No target mise file found' zle send-break return 1fi
send-break
を使わないと、エラーになったときに次のプロンプトが表示されません。

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

yqがなかったらエラー
上記と同様にyq
がなかったらエラーにしています。
if ! type yq > /dev/null; then echo 'fzf_mise_tasks' echo 'yq command is required' zle send-break return 1fi
タスクを抽出
yqを使ってtomlファイルからタスクを抜き出しています。
local tasks_list=()for file in "${found_files[@]}"; do local file_tasks=$(yq -oj '.tasks | to_entries | .[] | .key + " = " + ( select(.value.run| type == "!!str") .value.run, select(.value.run| type == "!!seq") .value.run | join(" && ") )' "$file" 2>/dev/null || echo '') if [[ ${file_tasks:-null} != null ]]; then tasks_list+=("$file_tasks") fidonelocal tasks=$(printf "%s\n" "${tasks_list[@]}")
この段階ではまだ実行していないため、コマンド部分を改行して掲載します。
yq -oj '.tasks | to_entries | .[] | .key + " = " + ( select(.value.run| type == "!!str") .value.run, select(.value.run| type == "!!seq") .value.run | join(" && ") )' "$file" 2>/dev/null|| echo ''
ざっくりとこんなステップです。
- yqを実行してtomlファイルからtaskを抽出する
run
が文字列ならそのまま出力run
が配列なら&&
で文字列として結合して出力
- yqでエラーがあったら捨てる
- エラーがあったら空文字にする
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 1fi
文字列を整形とfzfの実行
local selected=`echo "$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-promptif [[ -z $selected ]]; then return 0fi
# 再実行したくなるときのために履歴に残して実行BUFFER="mise run $selected"zle accept-line
プロンプトをリセットして、未選択だったらそのまま終了します。
最後に、履歴にmise run 〇〇
という実行記録を残すためにプロンプトを書き換えてから実行します。
キーバインドのセット
キーバインドの設定も忘れずに。
zle -N fzf_mise_tasksbindkey "^Xm" fzf_mise_tasks
以上、miseのタスクランナーとfzfの連携でした。
npm run 〇〇
(pnpm
にも対応)についても書いてます。Node.jsを使う人にはおすすめです。
fzfでpackage.jsonのscriptsを選んで実行 | eiji.page