[[📒Articles]] > [[📒2021 Articles]]
![[2021-08-29.jpg|cover-picture]]
先日紹介した[[Task]]を使って、ターミナル上ならどこにいても実行できるグローバルコマンド、`owl`を作ってみました。
## はじめに
パソコンを使っていると1日に1回行うか行わないかくらいの、少しだけ手間な作業が発生します。この作業を**タスク**と呼びましょう。私の場合、以下のようなタスクがあります。
- ブログに挿入する画像を1280pxにリサイズし、品質を最適化する
- [[MP4]]で作成した動画を[[GIF]]に変換する
- 定期バックアップ
- 特定言語のsandbox環境構築
これらのタスクをすべて手動でやっているわけではありません。ターミナルでコマンドをいくつか実行したりスクリプトを実行すれば終わるようにはしています。どのタスクも30秒とかからないでしょう。
しかし、30秒とかからないそれらのタスクをするとき、なんとも言えない窮屈な気持ちを覚えます。『思った瞬間に実行してくれればいいのに..』とまでは言いませんが、せめて==今の状態を気にせず==**コマンド一発で終わらせたい**なと。
### スクリプトを量産する方法
まず思いつくのはスクリプトの量産です。私の場合は[[Windows]]なので片っ端から[[PowerShell]]のスクリプトを作成し、それらにPATHを通すのです。すると、それらをコマンド一発で実行できます。
しかし、この方法は全ての課題を解決しません。==どういうときにどのコマンドを使うか==**を把握しておく必要**があります。
『それくらい覚えればいいじゃないか..』と思うかもしれません。しかし、結局は不安になってREADMEやメモを見たり、コマンドヘルプを表示するのがオチです。
### 顧客が欲しかったもの
私が欲しいのは以下の要件を満たすソリューションです。
- タスクはできるだけ **いつでも/どこでも** 実行できる
- タスクはできるだけ **状態に依存せずに** 実行できる
- タスクの定義を **簡単に** 確認できる
- タスクの定義は **気軽に** 追加/変更できる
極力、余計なことを考えなくてもいいようなソリューションが欲しいということですね。
## [[Task]]を使ったグローバルタスク管理
実現方法を考えていたとき、[[📘Windowsにも優しいタスクランナーTaskを試してみた|先日に書いたTaskの記事]]を思い出しました。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/favicon-64.png" />
<span class="link-card-v2-site-name">Minerva</span>
</div>
<div class="link-card-v2-title">
📘Windowsにも優しいタスクランナーTaskを試してみた
</div>
<div class="link-card-v2-content">Windows環境でのタスク自動化やクロスプラットフォーム対応に悩む方へ。TaskはYAMLで簡単にタスク定義でき、MakeやPowerShell、cmd、UTF-8日本語対応も強みです。導入方法や使い方を詳しく解説していますので、ぜひご覧ください。</div>
<img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/%F0%9F%93%98Articles/attachments/2021-08-08.jpg" />
<a data-href="📘Windowsにも優しいタスクランナーTaskを試してみた" class="internal-link"></a>
</div>
%%[[📘Windowsにも優しいタスクランナーTaskを試してみた]]%%
[[Task]]は非常に優れたツールで気に入っています。この記事を書いてから、私が開発しているリポジトリの半分弱は[[Make]]から[[Task]]に移行しました。残りも時間の問題です。
今回話題にあがっているのもまさに**タスク**..、まさに[[Task]]を使うべきシーンだと思いました。
### 完成イメージ
まずは完成イメージをコマンドにしてみました。コマンド名は私のトレードマークでもある`owl`にしました。
```console
# タスク一覧の表示
$ owl
$ owl -h
# プライベートのバックアップ
$ owl backup:private
# 直近に作成したmp4をgifにコンバート
$ owl gif
# TypeScriptのNode.jsプロジェクトを作成
$ owl typescript:node DST=ts-sample
```
**いつでも、どこでも、状態に依存せず、簡単に、気軽に.. **
カレントディレクトリを意識せず、全てワンコマンドで、最低限の引数で済むようインターフェースは工夫しました。
## [[Task]]の定義
まずは`Taskfile.yml`を書いてみました。
```yaml:Taskfile.yml
# Required powershell
version: "3"
tasks:
default:
- task: help
help:
silent: true
cmds:
- task -l -t Taskfile_tmp.yml
gif:
desc: 直近に作成したmp4をgifにコンバートする
cmds:
- ffmpeg -y -i "{{.INPUT}}" -filter_complex "[0:v] split [a][b];[a] palettegen [p];[b][p] paletteuse" ~/tmp/{{now | date "2006-01-02"}}.gif
- cmd: cd ~/tmp && explorer .
ignore_error: true # なぜか必ずエラーになる..
vars:
INPUT:
sh: ls -t ~/Pictures/Screenpresso/*.mp4 | head -1
backup:private:
desc: プライベートのバックアップ
cmds:
- cmd: |
cd ~/work &&
7z a minerva.7z minerva &&
explorer .
ignore_error: true # なぜか必ずエラーになる..
typescript:node:
desc: |
Node.js用のTypeScriptプロジェクトを作成する
∟ DST: プロジェクトを作成するパス (ex: DST=sample)
cmds:
- npx degit tadashi-aikawa/typescript-base ~/tmp/{{.DST}}
- cd ~/tmp/{{.DST}} && pwsh -File init.ps1 && code .
preconditions:
- sh: "[ {{.DST}} != '' ]"
msg: "DST is required."
```
どのタスクも2つ程度のコマンドで構成されており、コアな処理は別のものに委譲しています。それでも[[Task]]でこれらを制御する価値は以下3点に尽きるでしょう。
- カレントディレクトリを気にせず実行できる (`cd`コマンドで制御)
- 引数のバリデーションチェックができる
- 実行後のアクションを見据えて必要なツールを開くところまでやる
## owlコマンド
『`Taskfile.yml`があれば[[Task]]を実行するだけでいい.. なぜ`owl`コマンドが必要なのか?』と思うかもしれません。ただ、今のままでは==どこからでも==**実行できるわけではない**のです。[[Task]]は`Taskfile.yml`があるディレクトリで実行するか、`-t`オプションでTaskfileを実行しなければいけません。
どこからでも実行できる`owl`コマンドを[[PowerShell]]のプロファイルファイルに関数として定義します。
```powershell:%USERPROFILE%/Documents/PowerShell/Microsoft.PowerShell_profile.ps1
# owl
function owl() {
cp $env:USERPROFILE/git/github.com/tadashi-aikawa/owl-playbook/task/Taskfile_windows.yml Taskfile_tmp.yml
task -t Taskfile_tmp.yml $args
rm Taskfile_tmp.yml
}
```
これでターミナルから[[PowerShell]]を起動すると、どこでも`owl`コマンドが使えるようになります。
### Taskfileとカレントディレクトリ
`owl()`関数ではリポジトリの`Taskfile_windows.yml`を手元にコピーしてから`task`コマンドを実行しています。[[Taskfileに任意のファイルを指定]]することができるのにも関わらず、そうしないのは**カレントディレクトリの取得が面倒そう**だからです。
[[Make]]や他の[[タスクランナー]]を使う時も同じですが、[[タスクランナー]]は定義ファイルがあるディレクトリで実行するのがベストだと思います。変な不具合を踏む確率も下がるでしょう。
それならば、**こちらから`Taskfile`を取りに行って実行すればいい**のです。タスクが成功しても、失敗しても、最終的にカレントディレクトリの`Taskfile`は削除されます。コピーであるためバージョン管理された`Taskfile`には一切影響ありません。
なお、カレントディレクトリに`Taskfile.yml`があると上書き/削除されてしまうため、コピー後のファイル名は`Taskfile_tmp.yml`にしています。絶対に同じ名前を避けたい場合はちゃんとした一時ファイル作成コマンドを使った方がいいです..が、そこまでする必要はなかったので妥協しました。
### 実行結果
最後に実行結果をお見せします。まずはタスク一覧の表示から。
```console:powershell
$ owl
task: Available tasks for this project:
* backup:private: プライベートのバックアップ
* gif: 直近に作成したmp4をgifにコンバートする
* typescript:node: Node.js用のTypeScriptプロジェクトを作成する
∟ DST: プロジェクトを作成するパス (ex: DST=sample)
```
ターミナルさえ開いていれば、どのような状況でも`owl`で一覧が表示されます。
コマンドは引数なしの`owl gif`を実行した動画です。[[Screenpresso]]を使って直前に記録した[[MP4]]ファイルがある前提での動作になります。
![[2021-08-29.mp4]]
実行後に作成されたファイルの場所が開くので、コピペやドラッグ&ドロップのようなアクションに素早く繋げられます。[[GIF]]ファイル作成[^1]のハードルがかなり下がりました。
[^1]: [[Screenpresso]]でも[[ffmpeg]]を使って保存形式を[[GIF]]にするオプションがあります。ただ、サイズが大きすぎると[[GIF]]ではなく[[MP4]]で共有したいケースもあるため、一旦[[MP4]]で保存してから[[GIF]]にも変換できるほうが便利ですね。
## まとめ
[[Task]]を使って、ターミナル上ならどこにいても実行できる魔法のグローバルコマンド、`owl`を作ってみました。
ここで紹介した以外にも様々なタスクを定義しています。興味がありましたら[[🦉owl-playbook]]のリポジトリの`Taskfile_windows.yml`覗いてみてください。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
owl-playbook/task/Taskfile_windows.yml at master · tadashi-aikawa/owl-playbook
</div>
<div class="link-card-v2-content">
Playbook both Linux and Windows for me. Contribute to tadashi-aikawa/owl-playbook development by creating an acc ...
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/ae210c5941dde033d4def911650286292898f9d1d90f3c274acd90d44396ebfd/tadashi-aikawa/owl-playbook" />
<a href="https://github.com/tadashi-aikawa/owl-playbook/blob/master/task/Taskfile_windows.yml"></a>
</div>
どのようなコマンドインターフェースが直感的かは人によって変わります。だからこそ、自分だけのグローバルコマンドを作成してみる価値はあると思います。
シェルスクリプトを駆使したり、[[Go]]のような言語で実装してもいいかもしれません。ただ、[[Task]]という優れた[[タスクランナー]]を使ったGenericな実装も、時にはエレガントと言えるのではないでしょうか。