30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Deno で CLI ツールを作る

Deno.argsDeno.exitなどの機能を使い、Deno で CLI ツールを作っていく。

Deno のバージョンは1.3.1で動作確認している。

コマンドライン引数の受け取り方

渡されたコマンドライン引数は、Deno.argsで取得することができる。

例えば$ deno run cli.ts a b --foo 1 -b 2を実行すると、以下の結果になる。

// cli.ts
console.log(Deno.args); // [ "a", "b", "--foo", "1", "-b", "2" ]

このままでも使えないことはないが、標準モジュールが提供しているparseメソッドを使うと、より使いやすくなる。

import { parse } from "https://deno.land/std@0.66.0/flags/mod.ts";

console.log(Deno.args); // [ "a", "b", "--foo", "1", "-b", "2" ]

const parsedArgs = parse(Deno.args);
console.log(parsedArgs); // { _: [ "a", "b" ], foo: 1, b: 2 }
console.log(parsedArgs.foo); // 1
console.log(parsedArgs.b); // 2

parse(Deno.args)はオブジェクトを返し、_プロパティには、フラグと関連付けられなかった全てのコマンドライン引数が入る。今回の例だとabが該当する。

終了コード

Deno.exitメソッドで、プロセスを終了させることができる。このメソッドに渡した引数が、終了コードになる。

以下のコードは、Deno.exit(3);の行でプロセスが終了してしまうので、bは表示されない。

console.log('a');
Deno.exit(3);
console.log('b');

直前に実行したコマンドの終了コードは$?に入っているので、確認してみる。

$ deno run cli.ts
a
$ echo $?
3

Deno.exitに引数を渡さなかったり、Deno.exitを実行することなくプロセスが終了したりした場合は、0が終了コードになる。

テキストファイルを読み込む CLI ツール

ちょっとしたサンプルとして、指定された名前のファイルがカレントディレクトリにあった場合、それをテキストファイルとして読み込んで表示するプログラムを書いた。

const fileName = Deno.args[0];
const filePath = `${Deno.cwd()}/${fileName}`; // Deno.cwd() で、カレントディレクトリのパスを取得できる

const fileData = await Deno.readFile(filePath);
const decoder = new TextDecoder("utf-8");
const fileText = decoder.decode(fileData);

console.log(fileText);

Hello!と書かれたhello.txtを用意した上で、実行してみる。

$ deno run cli.ts hello.txt
error: Uncaught PermissionDenied: read access to <CWD>, run again with the --allow-read flag

権限がないため、エラーになった。--allow-readをつければ、実行できる。

$ deno run --allow-read cli.ts hello.txt
Hello!

CLI ツールの配布方法

Deno には、deno installという、CLI ツールの配布やインストールを簡単に行えるサブコマンドが用意されている。

例えば、console.log("Hi!");とだけ書かれたsay_hi.tsという名前のファイルを、Gist に置く。

そしてそれをインストールする。

$ deno install https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
Download https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
Check https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
✅ Successfully installed say_hi

そうすると、say_hiコマンドが使えるようになっている。

$ say_hi
Hi!

このように、非常に簡単にインストールできる。
配布する側も、ただウェブ上にファイルを置くだけでよい。

インストールされる場所

デフォルトだと、$HOME/.denoというディレクトリに、bin/インストールしたファイル名(拡張子は省略)という形でインストールされる。

対象のディレクトリは--rootで指定することも可能。

$ deno install --root ./ https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
$ ls ./bin/
say_hi

./を指定したので、./bin/say_hiとしてインストールされた。

インストールされるファイルの名前

インストールしようとした場所に既に同名のファイルがある場合は、エラーになる。

$ deno install https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
Download https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
Check https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
error: Existing installation found. Aborting (Use -f to overwrite).

エラーメッセージにあるように-fオプションをつけることで、上書きできる。

また、-nオプションを使うと、インストールされる名前を指定することができる。

$ deno install -n greet https://gist.githubusercontent.com/numb86/0f3352f26cadbead72c98557ba4dae03/raw/76ba53c6e7c3864f30f9f543d051a1e3d7f4dea9/say_hi.ts
$ greet
Hi!

指定しなかった場合は拡張子を省略したファイル名になるのだが、ファイル名がmod.tscli.tsの場合は、親のディレクトリ名になる。

例えば、以下の内容のcli.tsというファイルを Gist に置き、それをインストールする。

console.log("Hello!");

そうすると、cli.tsの親ディレクトリであるaf5628023388ce6e280fbcb328e1081fa1cb4179という名前でインストールされる。

$ deno install https://gist.githubusercontent.com/numb86/26045a06e6d9ff5ca294d7b291120c95/raw/af5628023388ce6e280fbcb328e1081fa1cb4179/cli.ts
Download https://gist.githubusercontent.com/numb86/26045a06e6d9ff5ca294d7b291120c95/raw/af5628023388ce6e280fbcb328e1081fa1cb4179/cli.ts
Check https://gist.githubusercontent.com/numb86/26045a06e6d9ff5ca294d7b291120c95/raw/af5628023388ce6e280fbcb328e1081fa1cb4179/cli.ts
✅ Successfully installed af5628023388ce6e280fbcb328e1081fa1cb4179
$ af5628023388ce6e280fbcb328e1081fa1cb4179
Hello!

権限の指定

--allow-readのような権限の指定は、インストール時に行う。

例として、先程作ったテキストファイルを読み込むプログラムを、read_file.tsとしてGist に置く。

何の権限も指定せずにこれをインストールしてみると、成功する。

$ deno install https://gist.githubusercontent.com/numb86/1bd65079ceafc1c23fce14915af37178/raw/e9873beb7d9d76913192affc43674b1edef85bbb/read_file.ts
Download https://gist.githubusercontent.com/numb86/1bd65079ceafc1c23fce14915af37178/raw/e9873beb7d9d76913192affc43674b1edef85bbb/read_file.ts
Check https://gist.githubusercontent.com/numb86/1bd65079ceafc1c23fce14915af37178/raw/e9873beb7d9d76913192affc43674b1edef85bbb/read_file.ts
✅ Successfully installed read_file

だがread_fileを実行すると、失敗する。実行時に--allow-readを付けることもできない。

$ read_file hello.txt
error: Uncaught PermissionDenied: read access to <CWD>, run again with the --allow-read flag
$ read_file --allow-read hello.txt
error: Uncaught PermissionDenied: read access to <CWD>, run again with the --allow-read flag

--allow-readをつけて、インストールし直す。

$ deno install -f --allow-read https://gist.githubusercontent.com/numb86/1bd65079ceafc1c23fce14915af37178/raw/e9873beb7d9d76913192affc43674b1edef85bbb/read_file.ts
Download https://gist.githubusercontent.com/numb86/1bd65079ceafc1c23fce14915af37178/raw/e9873beb7d9d76913192affc43674b1edef85bbb/read_file.ts
Check https://gist.githubusercontent.com/numb86/1bd65079ceafc1c23fce14915af37178/raw/e9873beb7d9d76913192affc43674b1edef85bbb/read_file.ts
✅ Successfully installed read_file

これで、実行できるようになった。

$ read_file hello.txt
Hello!

ローカルファイルもインストール可能

ウェブ上のあるファイルだけでなく、ローカルにあるファイルもインストールできる。

// say_bye.ts
console.log("Bye");
$ deno install say_bye.ts
Check file:///Users/numb/hello-deno/say_bye.ts
✅ Successfully installed say_bye
$ say_bye
Bye

参考資料