Typescriptで体重維持に必要なカロリーを計算できるコマンドラインツールを実装してみた

## はじめに
はじめまして、 [saba_can](https://twitter.com/saba_can00)と申します。

今回、OSSデビューとして、[oclif](https://oclif.io/)というTypescriptでコマンドラインツールを簡単に作成できるフレームワークを利用して、体重維持に必要なカロリーを計算できる簡易ツール(TDEE calculator)を実装したため、紹介させていただきます。

ソースコードは[こちら](https://github.com/saba-can00/tdee_calculator)
 
体重や食事のコントロールをして健康的な生活を送りたいという欲求があるので、ドメイン(健康 / 体重管理 / 食事)の関連知識と技術的な知識の両面でのアウトプットをしたいという想いで今回のツールを実装しました。

## ツールの紹介
自分の身長、体重、年齢、性別、活動量(日常の運動量)を入力すると、現在の体重維持に必要なカロリーを計算してくれます。


ツール名にも含まれているTDEEとはTotal Daily Energy Expenditureの略で、基礎代謝量に一日の活動カロリーを足したもので、一日の総消費カロリーのことをいいます。
(参考: [TDEEの計算は確実に痩せたいなら絶対にしたほうがいい理由](https://life-lemon.com/tdee/))

このカロリー数よりもたくさん食べれば体重が増加するし、少ない量に抑えれば体重が減少できるという目安にすることができます。

カロリーの計算式としてはこの手のツールで採用実績の多い[ハリス。ベネディクト方程式(改良版)](https://musclewiki.com/Harris%E2%80%93Benedict_equation#cite_note-3)を採用しました。
 
**実行画面**
![tdee_calculator_usage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584011/c289eb15-2b49-ec1e-77ed-f95e86c0dd20.png)

## 技術スタック
下記の技術スタックでツールの作成を行っています。

言語: Typescript
フレームワーク: [oclif (Open CLI Framework)](https://oclif.io/)
ユニットテスト: jest
Linter: eslint

技術スタック選定としては、今キャッチアップを行っているTypescriptを利用してツールを作ってみたいという思いがあったため、フロントエンドエンジニアがよく利用する技術スタックを利用しています。

## oclifについて

oclifはJavascript/ Typescriptでコマンドラインツールを実装できるNode.jsをベースとしたフレームワークです。

ls、cdのような１つのコマンドで完結するコマンドもgit, dockerのようなサブコマンドをもつコマンド(例: git commitやdocker runなど)のどちらにも対応しています。

下記、今回実装したコードをベースにoclifの利用方法を簡単に解説します。

### インストール
oclifはcliを提供しているため、npxでコマンドを実行するだけで開発をスタートできます。

```shell
 npx oclif single mynewcli
```

### コマンドで実行する処理を実装

コマンドラインツール実行時には```run()```メソッドが呼び出されるため、ここにコマンドラインツールで実行する処理を記述します。
本ツールではここでパラメーターの取得と計算処理、結果の表示を行っています。

```typescript:index.ts
async run(): Promise<never> {
    const { flags } = this.parse(TdeeCalculator); //パラメーターの取得
    const param = await this.makeCalculatorParam(flags); 
    // 計算処理　+ 結果の表示
    this.log(
      `The calorie to keep your weight is ${this.calculator.calculateIntakeCalorie(
        param
      )} calories.`
    );
    this.exit(0); // 正常終了なので0でexit
  }
```
#### オプションからパラメーターの取得
oclifが提供するCommand classを継承(cliが自動でやってくれます)しているため、static な flags プロパティにコマンドラインで利用したいオプションを設定することで```tdee_calculator -a 30 ```のようにオプションからパラメーターを取得することができます。

```typescript:index.ts
class TdeeCalculator extends Command {

public static readonly flags = {
    // add --version flag to show CLI version
    version: flags.version({ char: "v" }),
    help: flags.help({ char: "H" }),
    // flag with a value (-n, --name=VALUE)
    age: flags.integer({
      char: "a",
      description: TdeeCalculator.descriptionOfAge,
      required: false,
    }),
  ・・・ 
  }
}
```

#### 対話形式でのパラメーター入力
すべてのパラメーターをオプションで渡すのは使い勝手が悪いと思ったので、今回は[Inquirer.js](https://github.com/SBoudrias/Inquirer.js/)を利用して、対話形式でパラメーターを取得できるようにしました。

[Inquirer.js](https://github.com/SBoudrias/Inquirer.js/)では、```inquirer.prompt```の引数にメッセージや入力する値のタイプを設定することで簡単に対話形式での入力を実装できます。
(参考: [Inquirer.js/Prompt types](https://github.com/SBoudrias/Inquirer.js/#prompt-types))

下記の赤枠部分を[Inquirer.js](https://github.com/SBoudrias/Inquirer.js/)で実現しました。
![tdee_calculator_usage2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584011/c898baa2-e74c-39fe-0296-7e4d16533570.png)

**コード**

```typescript:harris-benedict-calculator.ts
 public async makeNumberParameter(
    param: number | undefined,
    message: string
  ): Promise<number> {
    switch (typeof param) {
      case "number":
        return param;
      default: {
        // ここで対話形式でパラメーターを取得
        const { inputNumber } = await this.inquirer.prompt([
          {
            type: "input",
            name: "inputNumber",
            message: message,
            validate: function (value: string) {
              const isValid = !isNaN(parseFloat(value));
              return isValid || "Please input a number";
            },
          },
        ]);
        return !isNaN(inputNumber) ? (inputNumber as number) : 0;
      }
    }
  }

```

#### メッセージの表示
oclifでは、```this.log```または```console.log```を利用することでメッセージの表示が行えます。
```this.log```と```console.log```はブロッキングするかどうかが異なります。
(参考:[this.log](https://oclif.io/docs/commands#thislogmessage-string))

## まとめ
コマンドラインツールというとBashやGoなどを利用することが多いですが、フロントエンドエンジニアの技術スタックでも思ったより簡単に実装できたので、フロントエンドエンジニアの方がちょっとしたツールを作りたいなという場合に参考になれば幸いです。

それでは、素敵なTypescriptライフを!

## 参考リンク
* [oclif (Open CLI Framework)](https://oclif.io/)
* [TDEEの計算は確実に痩せたいなら絶対にしたほうがいい理由](https://life-lemon.com/tdee/)
* [Harris–Benedict equation](https://musclewiki.com/Harris%E2%80%93Benedict_equation#cite_note-3)
* [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/)