Node.jsで生徒を自動でクラス分けするツールを作り続けているんだ

この記事は、Node.js Advent Calendar 2020の15日です。

たまたま生徒クラス分けみたいなことをしていて毎回入力するの面倒くさいなって思って作ったCLIの話です。 あれ?これNode.jsでただ作っただけじゃ...

作るにあったってパッケージの選定理由やハマりそうなことを書きました。

作ったもの

Sorting-Hatと言う、JSONYamlファイルで定義した人達(生徒)を定義したクラスに分けるCLIを作りました。 正直言うと未完成です!

使い方は、Readmeに書いてあります。 (もし使う方がいらっしゃいましたらフィードバックいただけると幸いです)

未実装の機能

当初の作ろうとしていた機能のうち下記2点の機能がまだ実装されていません。

  • 出力結果の人物毎にランダムに色をつける
    • ランダムで色をつける機能
  • ファイルではなく対話式シェルでの入力の追加
  • ファイル以外で入力するのが面倒な気がしたので未実装

Read Mapに今後実装予定の内容がざっくり書いてあります。

開発環境

今回のツールを開発するために使用した開発環境です。

名前 Version
OS macOS Big Sur
Node.js v14.8.0
yarn 1.22.4

パッケージついて

ここでは、Sorting-Hatを作るにあたり、使用したパッケージや類似のパッケージと比較して選定した理由を書いています。

使用したパッケージ

CLIツールを開発するために次のパッケージを使用しました。

名前 Version 概要 採用理由
TypeScript ^3.9.7 型定義ができてJavaScriptの機能を拡張したAltJS 型を使い明示的に実装をしたかったので採用
meow ^7.0.1 Optionの定義とhelpの設定、versionを表示できるパッケージ シンプルなところ
もっと言うとhelpの定義とhelpのテンプレートリテラルによる記述が可能な点、Optionの定義がJSON形式で可読性が高い点
figlet ^1.5.0 文字をアスキーアートで表示してくれるパッケージ AAで表示もできたら面白いかなと思ったので採用
js-yaml ^3.14.0 yamlファイルを読み書きするためのパッケージ jsonファイル以外にもyamlファイルもサポートしたかったので採用
ESLint ^7.7.0 JavaScriptとTypeScriptの静的検証ツール 細かいコーディングルールを気にしながら書くのが面倒なので採用
Prettier ^2.0.5 .tsや.js、.jsonなどのファイルを整形してくれるコードフォーマッター 主にコードの整形目的で採用

今後使う予定のパッケージ

未実装の機能を実装する時の使用と考えていますが、まだ実装していないため使用予定としているパッケージです。

名前 概要 採用理由
chalk Node.jsが出力した文字に装飾をつけてくれるパッケージ 主に生徒のクラス分け後にランダム色をつけたり、大事なところを太字にしたいと思ったため採用
開発中にランダムで出した色の視認性について考慮が必要だったので一旦未使用
inqurier 対話型インタフェースのパッケージ 対話型インタフェースで、ファイルで入力していた内容を直接入力させるために使用を検討していため採用
そもそも対話式で入力するのが面倒な問題があったので一旦未使用

meowを使った理由

類似パッケージの何点か検討していましたが、下記の3つ理由から最終的にmeowに決めました。

  • Optionの設定やシンプルにまとめて記述できる
    • 他のパッケージは、Optionやコマンドの定義やhelpをfunction chainで定義していたが、今回はOptionの定義と実装は分離したかった。
  • Helpテキストがテンプレートリテラルで自由に記述できる
  • Optionの記述がJSON形式で、入力データの型を記述できる
  • あと猫が可愛い

OptionやHelpテキストシンプルにまとめて記述できる 個人的にはこの理由が一番強く、Optionや一つの場所にまとめて記述したかったので採用しました。

例)

const cli = meow(`
    Usage
      $ sorting-hat

    Options
      --file, -f   target json file.
      --AA , -A    Output as ASCII art.

    Examples
      $ sorting-hat
      $ sorting-hat -f class.json
      $ sorting-hat -f -A class.yaml
`,
    {
        flags: {
            file: {
                type: "string",
                alias: "f",
            },
            AA: {
                type: "boolean",
                alias: "A",
            },
        },
    }
)

候補に上がったパッケージ

候補に上がったパッケージは下記です。

これらのパッケージは、基本的にOptionやコマンド毎に定義しfunction chainで処理や説明文の定義する仕様でした。 コマンドとOptionが多く、個別に処理を記述したりしたい場合に良いと思います。 また、meowに共通した仕様もありました。

ただ今回は、Optionやコマンドがたくさんあるものを作る予定はなかったこと、シンプルで使いやすいと感じたので今回は採用しなかった次第です。

ハマったこと

開発していて個人的にハマりそうなポイントを書いておきます。 READMEをしっかり読めば問題ないんじゃ...

meow編

引数やOptionの扱いについて

下記みたいに複数の引数やOptionが並んだ場合どうなるか。

結論から書くとmeowはちゃんと引数とOptionの型・値を分けた上で認識してくれます。

実行するコマンド

$ yarn run sorting-hat -f ./classData.yaml data -A none template 

ソースコード

const cli = meow(
    `
    Usage
      $ sorting-hat

    Options
      --file, -f   target json file.
      --AA , -A    Output as ASCII art.

    Examples
      $ sorting-hat
      $ sorting-hat -f class.json
      $ sorting-hat -f -A class.yaml
`,
    {
        flags: {
            file: {
                type: "string",
                alias: "f",
            },
            AA: {
                type: "boolean",
                alias: "A",
            },
        },
    }
)

console.log('------ Inputs : Start -------')
console.log(cli.input)
console.log('------ Inputs : End -------')
console.log('------ Flags : Start -------')
console.log(cli.flags)
console.log('------ Flags : End -------')

出力結果

------ Inputs : Start -------
[ 'data', 'none', 'template' ]
------ Inputs : End -------
------ Flags : Start -------
{ file: './classData.yaml', aa: true }
------ Flags : End -------

Options(meow的にはflags)は、StringとNumberを指定した場合、-AなどのOptionの後の引数をOptionの値として認識しています。
それ以外のものは、引数に扱われます。

figlet編

TypeScriptを使うなら...

なんかES Modulesでfigletをimportできないなって思ったら、@types/figletを忘れてた。
そりゃそうだ。

$ yarn add -D @types/figlet

表示されないAA

ReadmeのSimple usageの通りに使ったらfiglet()クロージャー内の出力のみ表示されなませんでした。
クロージャーがヒントですが、figlet()クロージャーの実行は別のタイミングで行っていたのが原因です。 さらに言うと、figlet.textでも同じことが発生します。

コード

import figlet from "figlet"

console.log('----------------')

figlet('Hello World!!', (err, data) => {
    if (err) {
        console.dir(err);
        return;
    }
    console.log(data)
});

console.log('----------------')

出力結果

----------------
----------------

解決方法ですが、figlet.textSyncを使いましょう。
READMEにも書かれていますが、このメソッドは、textメソッドの同期版です。

import figlet from "figlet"

console.log('----------------')

console.log(figlet.textSync('Hello World!!',{font: basic});

console.log('----------------')

日本語...君は表示できないんか....?

なんと!見出しの通り日本語は表示できません。
この記事を書いている(2020/12/14 21:41)に気が付きました

console.log(figlet.textSync('Aさん',{font: basic});

色々と試しましたが、figletの仕様みたいなので一旦諦めました。

参考にした偉大なる先駆者の方々の記事

この記事を書くにあたり大変お世話になった記事です。ありがとうございました。

最後に

実はこのツール作ったのは、9月でブログにも記事を書き始めていて準備だけしていたのですが、さまざまな事情によりなかなかアウトプットする機会に恵まれなかったです。 なのでアドベントカレンダーはとても良い機会でした。とても感謝です。
ただ、ネタとして微妙だなと、記事を書いていて思ったので、来年はもっと深い話をしたいと思いました。 去年の冗長に書きすぎた反省は、今回は抑えられたので良しとしました。

それと、私事でさっき気が付いたのですが去年の現職のアドベントカレンダーでの記事を投稿する日も12月15日だったんですね。 実は、現職の最終出社日が今日なので、なんと言いますか運命とか1年でこんなにも変化したんだなという成長を感じました。 来年もいずれかのアドベントカレンダーに出没したいと思います。

(12/21にSvelteのカレンダーに出没しますが...)

ところで npm publish しないの?

需要は....ありますかね....? (あるなら教えて欲しいです。) それにOptionなしを実装していない未完成品なのでまだ公開はしないつもりです。

Copyright (c) NaoNao All Rights Reserved.