at backyard

Color my life with the chaos of trouble.

Terminal上で動作するspinnerライブラリ、go-spinをDenoに移植した

Terminal上で動作するGo製のspinnerライブラリであるgo-spinを、Denoに移植したdeno_spinというものを公開しました。

https://deno.land/x/deno_spin

目次

ターミナルで動作するDenoのためspinnerライブラリ、deno_spin

Go言語で書かれたターミナル上で動作するスピナーライブラリがあります。

名前はgo-spin。

github.com

こちらのパッケージをDenoに移植しました。名前は deno_spin です。

github.com

下記のように動作します。

https://user-images.githubusercontent.com/8216064/144514238-cac872db-f876-46d8-a889-b7a0a09f7ff8.gif

コードサンプルは下記のとおりです。

import Spinner from "https://deno.land/x/deno_spin@v0.0.3/mod.ts";

console.log("box1: ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏");
const box1 = new Spinner(); // Detault spinner type is box1
for (let i = 0; i < 30; i++) {
  await box1.next();
}

console.log("box2: ⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓");
console.log("and drawInterval: 200msec");
const box2 = new Spinner({ type: "box2", drawInterval: 200 });
for (let i = 0; i < 30; i++) {
  await box2.next();
}

console.log("box3: ⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆");
const box3 = new Spinner({ type: "box3" });
for (let i = 0; i < 30; i++) {
  await box3.next();
}

console.log("box4: ⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋");
const box4 = new Spinner({ type: "box4" });
for (let i = 0; i < 30; i++) {
  await box4.next();
}

console.log("box5: ⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁");
const box5 = new Spinner({ type: "box5" });
for (let i = 0; i < 30; i++) {
  await box5.next();
}

console.log("box6: ⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈");
const box6 = new Spinner({ type: "box6" });
for (let i = 0; i < 30; i++) {
  await box6.next();
}

console.log("box7: ⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈");
const box7 = new Spinner({ type: "box7" });
for (let i = 0; i < 30; i++) {
  await box7.next();
}

console.log("spin1: |/-\\");
const spin1 = new Spinner({ type: "spin1" });
for (let i = 0; i < 30; i++) {
  await spin1.next();
}

console.log("spin2: ◴◷◶◵");
const spin2 = new Spinner({ type: "spin2" });
for (let i = 0; i < 30; i++) {
  await spin2.next();
}

console.log("spin3: ◰◳◲◱");
const spin3 = new Spinner({ type: "spin3" });
for (let i = 0; i < 30; i++) {
  await spin3.next();
}

console.log("spin4: ◐◓◑◒");
const spin4 = new Spinner({ type: "spin4" });
for (let i = 0; i < 30; i++) {
  await spin4.next();
}

console.log("spin5: ▉▊▋▌▍▎▏▎▍▌▋▊▉");
const spin5 = new Spinner({ type: "spin5" });
for (let i = 0; i < 30; i++) {
  await spin5.next();
}

console.log("spin6: ▌▄▐▀");
const spin6 = new Spinner({ type: "spin6" });
for (let i = 0; i < 30; i++) {
  await spin6.next();
}

console.log("spin7: ╫╪");
const spin7 = new Spinner({ type: "spin7" });
for (let i = 0; i < 30; i++) {
  await spin7.next();
}

console.log("spin8: ■□▪▫");
const spin8 = new Spinner({ type: "spin8" });
for (let i = 0; i < 30; i++) {
  await spin8.next();
}

console.log("spin9: ←↑→↓");
const spin9 = new Spinner({ type: "spin9" });
for (let i = 0; i < 30; i++) {
  await spin9.next();
}

new Spinner()インスタンスを生成する際にSpinnerのタイプと描画間隔を選択することで好みのスピナーを利用することが可能です。

例えば、描画間隔を100msecにしてbox2 (⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓)のタイプのspinnerを利用したい場合は、下記のように設定することで利用が可能です。
(なお、何も指定しない場合はスピナーのタイプは box1(⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) となり、描画間隔は50msecで設定されます)

const box2 = new Spinner({ type: "box2", drawInterval: 100 });
// 30回spinnerを描画する
for (let i = 0; i < 30; i++) {
  await box2.next();
}

TypeScript備忘録 - contructorにオブジェクトを渡したい

今回インスタンス作成時にオブジェクト型でパラメータを付与したいと考えました。
(最初は new Spinner('box1', 100) というように2つの引数で初期化するような設計でしたが、その場合描画間隔だけを指定したいケースでもスピナーのタイプを指定せざるおえず、ナンセンスだと思ったためこのような設計に変えました。ちなみにこのインスタンス生成に関する仕様は移植元には存在しておらず、deno_spin オリジナルの仕様となっています。)

constructorにオブジェクトを渡したいとき、TypeScriptではどのように書けばよいかと考えたところ、今回のようなケースではPartialが使えそうだと考えました。

Partialは例えば Partial<T> があったとして、この T の全てのプロパティをOptional(任意)のプロパティにしてくれるものです。

実際に deno_spin のコードをサンプルとして下に載せます。

ここでは SpinnerTypedrawInterval 以外はハードプライベートとして指定しています。
あまり深くは調べていませんが、どうやらハードプライベートで指定した変数についてはPartialで参照できないようなので、今回のようにコンストラクタで受け取るパラメータについてはハードプライベートを指定しないようにしています。

コンストラクタのパラメータは完全に任意としているので、パラメータが指定されている場合のみ、値をセットするようにしています。

export default class Spinner {
  type: SpinnerType = "box1";
  drawInterval: number = 50;
  #frames: Array<string> = spinnerType[this.type].split("");
  #length: number = this.#frames.length;
  #pos: number = 0;

  constructor(props?: Partial<Spinner>) {
    if (props?.type) {
      this.type = props.type;
      this.#frames = spinnerType[props.type].split("");
    }

    if (props?.drawInterval) {
      this.drawInterval = props.drawInterval;
    }
  }

これでオプショナルなパラメータをobjectとしてconstructorに設定することができました。

まだまだTypeScriptについて理解できていない部分も多いので、より良いやり方などあればコメントいただけると幸いです。