Yamato DaiwaE(CMA)S(cript) extensions

ConsoleCommandsParser

コンソールコマンドの引数ベクトルの処理、バリデーションそしてオブジェクト系の特定の TypeScript型への変換のに使われているクラスである。 尚、全コマンドに関する参考本文(ヘルプ)生成機能もある。

デモ

下記の例の詳しい解説が論述される。

最低限の理論

コンソールコマンドに関する正式的な用語や此れの解剖学に対する規格は無く、 大体慣用のコンベンションしか存在してない。 事実上、Node.jsのprocess.argv(「引数ベクトル」、C++言語から継承された概念)は文字列の配列に過ぎない。 此れをどうすれば良いか、開発者か其の依頼側か(其の両方か)が決めている。

実は、コンソールコマンドの解析は任意外部構成化データの解析の問題の一種類で、 此処で此の外部のデータは文字列の指数配列、従って要素の 順番を考慮する必要が有る。 標準のNode.js機能の中に、此の様な配列の解析・妥当性確認専用の機能が 無く、TypeScriptの型付け上可能な限り安全に解決出来る 第三者のライブラリでも、存在しているとは限らない。

条約的な用語

全体的に、コンソールコマンドは空白に別けられている文字列の値の順番に成っています。

例えばWebpackの場合、コマンドは下記の様に成る事がある。

コマンド
事実上アプリケーションの名前に成っている(完全名か、省略名)。 例えば、Angularフレームワークの場合、コマンドはngと言う省略である。 然し普通はアプリケーションの名前と一致しているか、此れに近い(例:webpack、 gulp、lerna)。
オプション(キー)
2重のnダッシュから始まる。 上記の例に於いては--mode。
引数
オプションの値である。 上記の例なら、developmentは--mode オプションの引数に成っている。
  • オプションは引数が無い場合、真の値の ブーリアンオプションとして見做される。  此の様に、当オプションが指定されていない場合、偽の値も同然。
  • オプションがハイフンと一文字から成り立っている省略を持っている事が有る(例えば、 「-d」)。 面白い事だが、上記の例に出たWebpackには、-mと言う省略が存在してい はいるが、`--mode`の省略には成っていない。 御覧の様に、省略を入力する事が早い引き換え、暗記するには日常利用が必要。
  • アーギュメントは空白を含める事は出来るが、此の場合括弧に包む事が必要に成る。

上記の例に於いてはbuildとは何だろう? 良い質問で、専用の節に相応しい。

「コマンドフレーズ」用語

コマンドフレーズ(「command phrase」)はコンソールアプリケーションの具体的な機能に該当 している非オプションであるコンソールコマンドの1アーギュメント目である。 例えば、「yda build --mode DEVELOPMENT」には「build」は コマンドフレーズではあるが、一単語から成り立っているとは限らないので、「フレーズ」と呼ぶ。 複数の単語から成り立っている時、此れを括弧に包むと他に、キャメルケースの様に無空白の書き方が活用出来る。

コマンドフレーズは明示的に成っている他に暗黙的に成っている事も有る(規定のコマンドフレーズ)。 例えば、webpack build --mode developmentではbuildは 規定のコマンドフレーズなので、webpack --mode development 飛ばしても良いの様に飛ばしても良い。

TypeScriptの差別化組合せ

ConsoleCommandsParserを用いるにはTypeScript言語の 判別可能なユニオン型の概念を理解する事が必要。 一文で説明していると、判別可能なユニオン型のものは、或るオブジェクト型は特定の プロパティの揃いから成り立っている複数のオブジェクト型の中から何方かに 成っているものであり、但し一つのプロパティは具体的なオブジェクト型 を識別する。 此の様に、判別可能なユニオン型は複数の特定の オブジェクト型の総括。 同じように、「普通車」は「セダン」、「ハッチバック」、「ワゴン車」等の総括で、但し種類の数が有限で、車の種類が正しく判別するには車の資料に は「種類」と言う欄がある前提。

ConsoleCommandsParserと関連している例で此の概念を考察しよう。 我らに開発されているコンソールアプリケーションはbuild、pack、 deploy、helpと言うコマンドフレーズが有るとする。 当のコマンドフレーズはコンソールアプリケーションのどいった機能に該当しているか、後で考えれば良いが、ユーザー達にとって 短いコマンドフレーズの方が良い事に対して開発者達にとって一義的なコマンドフレーズの方が良い時、短い奴を 列挙の値の中に保持すれば良い。

各コマンドフレーズにとって以前定義された列挙の一 要素を含むphraseプロパティと、該当している コマンドフレーズの全オプションから成り立っているオブジェクトを定義しておこう。 最後の奴以外、全コマンドフレーズはオプションを持っているとする。

利用者は何方のコマンドフレーズを入力するか、我らは事前に知れないが、 バリデーション規則を正しく定義すれば(定後方法を後ほど説明する)以前定義されたコマンドフレーズの中一つ に成り、オプションも此のコマンドフレーズに該当していると保証。

ConsoleCommandsParserクラスのparse メソッドはSupportedCommandsAndParametersCombinationsと NodeJS_InterpreterAbsolutePathと言うプロパティ (process.argv配列の最初の 2要素に該当している)付きの SupportedCommandsAndParametersCombinationsと言う以前定義された 型のオブジェクトを返す。 事実上、ParsedCommand<SupportedCommandsAndParametersCombinations> に於いてParsedCommandTypeScriptジェネリックは、 SupportedCommandsAndParametersCombinationsユニオン に此の2個のプロパティが追加されるとTypeScript に伝える。

でも、利用者が具体的に何方のコマンドフレーズを入力したか、どうやって判断すれば良いだろう。 判別可能なユニオンの全サブタイプはphraseと 言った当ユニオンの中に唯一な値の識別子役割の プロパティを持っているので、条件構成(此の場合だと、switch/caseが 丁度良い)を持ちって具体的なサブタイプが判断出来る。

結果のして、各caseブロックの中に、現在のコマンドフレーズに該当している parsedConsoleCommandのプロパティにアクセスする事が出来る。 もし別のコマンドフレーズに該当しているオプションに参照しているプロパティにアクセスすると、 TypeScriptが気付きエラーを起こす。 但し、上記の例だと、任意なプロパティが多いので、これら使う前に 非undefined確認が必要に成る。

コマンドラインインターフェース(CLI)開発 | 段階的な案内

① コマンドフレーズの初期揃いの決定

貴方のアプリケーションの中に利用可能なコマンドフレーズを決めておこう。

  • 単一のコマンドフレーズが希望される場合が在る。 アプリケーションの専門が幅狭ければ、通常の事だと考えられる。
  • いつでも新しいコマンドフレーズが追加出来るから、取り敢えず自分にとって明確なコマンドフレーズを決めよう。

全コマンドフレーズを列挙の中に保持しておこう。 列挙のキーはコンソールに実際に入力される文字列と一致させる必要はなく、資料が無くても キーの意味が開発者にとって明かにした方が良い。 多数の開発者と同じ様に、実際に入力される文字列を短くしたいなら、これらを列挙要素の値に保持しておこう。 幸いにTypeScriptなら文字列要素の 列挙が定義可能で、他の言語なら、同じ機能が無いか制限が有る事が多い。

上記通り、列挙のキーとして動詞無しの単語の組み合わせ であり、代わりに動名詞が使われている。 名前は動詞から始まるが、関数・メソッドの名前に成っていないと、混乱が発生する。 然し、スタイルガイド定期な要求と成っているので、反対し自分の規律が導入出来る。

此のコードべ別のファイル(例えば、ApplicationConsoleLineInterface.ts)で書く推薦。 其れに、明確なコンテキストの定義の為当ファイルの中身を名スペース (例えばApplicationConsoleLineInterface)に包む事を御勧め。 開発者達の中に(TypeScriptの)名スペースの反対派は居るが、こういった 名スペースの否定的な評判の原因は、コードをモジュールに分割する旧い方法に多く有る。 上記の例の様に、名スペースを型や定数の為のコンテキストコンテナー として使っても、悪影響が特に無い。

② 各コマンドフレーズのオプションの決定

各コマンドフレーズにとってオプションの揃い(型、 必須性や有れば規定値)を決めておこう。

  • コマンドフレーズの場合と同じ様に、より早く自分のコードからフィードバックを取るには初期段階では自分にとって 最も明確で、最低限のオプションにすれば良い。
  • 一部分のコマンドフレーズがオプションが無い事が有る。 特に問題無い事であり、将来的にオプションが必要に成ったら、其の時追加されば良い。 現在の例だと、referenceGeneratingコマンドフレーズは オプションが無い。

今、各コマンドフレーズにとって下記のプロパティを含むオブジェクト型 を定義しておこう。

phrase
オブジェクト型自体と同じコマンドフレーズに該当しているコマンドフレーズの 列挙(上記の例に於いてはCommandPhrases)の 要素でなくてはならない。 当プロパティは識別子の様に成り、利用者は具体的に何方のコマンドフレーズの入力したか、判別するのに使われる。 此れの型は、列挙自体ではなく (上記の例だとCommandPhrases)、此の列挙の具体的な要素 (例えばCommandPhrases.projectBuilding)にしてはいけない (TypeScriptの差別化組合せ上は意味の無い事ではない)。
コマンドのオプション

対象のコマンドフレーズのに該当している全オプション。 キーは利用者によりコンソール入力されるものと必ず一致させなければいけない訳ではなく、 資料が無くても和訳出来るエンジン達にとって意味を明確にすれば良い。 各プロパティの型は下記の利用可能な型の何方か でなくてはならない。

  • string
  • number
  • boolean
  • 主要パッケージ(@yamato-daiwa/es-extensions)からのParsedJSON系のオブジェクト

オプションは任意で、但し規定値は予定されていない場合、 コロン(キーと値の型の区切り)の前に 疑問符を入れる必要が有る。

やがて、各コマンドフレーズに該当している以前作ったオブジェクト型にとって ユニオンを定義しておいてください (下記の例に於いては SupportedCommandsAndParametersCombinations)。

  • 再び名前の付け方と関連しているスタイルガイドの例に気を付けてもらいたい。 具体的なコマンドフレーズに該当している各型の名前は動詞を 含めていなく、~ConsoleCommand末尾は原型と成っています。
  • Readonlyと言うユーティリティ型の利用は必須ではないが、 推薦だ。 此れを使う事に依り、以前宣言されたオブジェクト型のプロパティはプログラミングの実行中変わる事は無い と明示的に指示する。

③ コンソールアプリケーションの仕様を定義

コンソールアプリケーション仕様通りに入力されたコンソールコマンドがバリデーション・処理される事に成り、 必要に応じて当コンソールアプリケーションの使い方に対して案内本文が生成される。

ConsoleCommandsParser.CommandLineInterfaceSpecification型の 定数を定義しておこう。 多層オブジェクト型と成り、全コマンドフレーズ (各一個のオプションを含めて)の定義及び案内を生成する為のデータを含まなければいけません。 ApplicationConsoleLineInterfaceの様な名スペースにコードを包む推薦通り してくれた場合、consoleCommandLineInterfaceSpecificationの様に長い定数名 は不要で、ApplicationConsoleLineInterfaceと言うコンテキスト が有るから、単にspecificationだけで良い。

  1. applicationNameプロパティに貴方のアプリケーションの名前を指定しておこう。 ターミナルに入力される名前と必ずしも一致しなければいけない訳ではなく、複数の言葉から成り立っている場合空白で訳ても良い。 指定された名前は参考の生成の為に使われる。
  2. applicationNameと違って、applicationDescription (和訳:アプリケーションの記述)プロパティは任意ではありますが、ちゃんとした参考を生成するには 短くても良いからアプリケーションについて一言を指定する推薦。
  3. commandPhrasesプロパティは 各コマンドフレーズの仕様を含めなくてはならない。 連想配列系のオブジェクトと成り、キーはコマンドフレーズと一致 しなければいけない。 一番安全なのは、ブラケット表記法を活用し、当オブジェクトのキーを CommandPhrases列挙の要素に 結び付く事(例えば、[CommandPhrases.packingOfBuild])。 連想配列の値なら、
    • 該当しているコマンドフレーズは規定に成っている場合、isDefaultと言う 真偽型のプロパティにtrueをしていする事。 規定のコマンドフレーズは一個しか 有り得ないので、isDefault: trueを指定しても良いのは、最大 一個のコマンドフレーズ、 さもなくばアプリケーションの起動の際例外が投げられる。
    • 上述のapplicationDescriptionの様に、コマンドフレーズの仕様には descriptionプロパティ(和訳:コマンドフレーズの記述)が指定 出来る。 任意に成ってはいるが、高品質の参考本文を生成するにはしたいした方が良い。 上記の例だと、help (CommandPhrases.referenceGenerating)コマンドだけ記述がついていない。
    • コマンドフレーズがオプションがある場合、options プロパティ(こいつも連想配列系のオブジェクトである)を埋める事。

各コマンドフレーズのオプションの定義

コマンドフレーズのオプションが多ければ、コマンドフレーズの オプションの使用定義こそ此の段階の大抵の時間がかかる。 とは言え、難しい事は無くて、コンソールに入力される値と同じ対象コマンドフレーズの 全オプションをオブジェクトのキーで指定し、 オブジェクトの値なら、オブジェクト型と成り、下記通り此れの中に オプションの型、必須・任意等を指定すれば良い。

  1. ConsoleCommandsParser.ParametersTypes列挙の 要素でオプションの型を指定しておこう。 現在、文字列型、数型、真偽型及びオブジェクト型 (入力の際妥当なJSON5型の文字列でなければけない)。
  2. オプションが規定値が有る場合、defaultValueプロパティを 該当している型の値で指定しておこう。 規定値が無い場合、代わりにrequiredと言う真偽型の プロパティを指定する必要がある(無論、必須の場合はtrue)。 但し、任意の真偽型のオプションの場合、 コンソールに明示的に入力されなかった値は明示的に入力されたfalseも同然なので、 TypeScriptはdefaultValueを指定されない。
  3. 高品質なコードを目指しているが、説明書を読まない意味が分からないオプション名でも、利用者達の入力の便利さの為に短いオプション名にしたい場合、 コードの整備者の為に仕様書を読まなくても和訳すれば意味が分かるオプション名をnewName プロパティに指定しておこう。
  4. オプションの為一文字のの省略(フラッグ)を確保したい場合、望ましい省略を shortcutプロパティに指定しておこう。 コンソールへの入力の際、当文字の前にハイフンを指定しなければいけないが、オプションの仕様を定義している今なら、ハイフンが無くても 良いです。

残りのプロパティは皆オプションの 型に依ります。 これらを考察しておこう。

文字列型オプションの特別なプロパティ

現在、こう言ったプロパティが一件しかなく、即ち allowedAlternatives。 文字列型のオプションの値を有限の数の選択肢に制限したい場合、 此の選択肢をallowedAlternativesプロパティに配列 として指定すれば良い。

数型オプションの特別なプロパティ
numbersSet
数の集合、此のオプションの型にとって必須なプロパティである。 値は当ライブラリ主要なパッケージから再利用されたRawObjectDataProcessor.NumbersSets 列挙でなければいけません。
naturalNumber
自然数
nonNegativeInteger
正の整数
negativeInteger
負の整数
negativeIntegerOrZero
負の整数・0
anyInteger
正負不問の整数
positiveDecimalFraction
正の小数
negativeDecimalFraction
負の小数
decimalFractionOfAnySign
正負不問の小数
anyRealNumber
実数
minimalValue
大小限
maximalValue
最大限
オブジェクト型オプションの特別なプロパティ

現在こう言ったプロパティが一つしかなくて、 validValueSpecificationと言い、必須である。 此れでJSON5形式の文字列が変換されるオブジェクトの使用を定義する必要が有る。

当プロパティは RawObjectDataProcessor.PropertiesSpecification型 と成り、主要パッケージから再利用され、コマンドフレーズのオプションの定義に似ているが、 RawObjectDataProcessorクラスのAPI が使われている。

④ コンソールコマンド処理ロジックの作成

コンソールアプリケーションの仕様の定義が完了し、此れを入力されたコンソールコマンドの処理のに使われる様に成った。 其の為、ConsoleCommandsParserクラスの静的メソッド parseを呼び出す事。

  • 直近の段階で定義されたアプリケーションコマンドの資料は必須の引数である。
  • 2引数目を指定しない限り、process.argvは引数ベクトル として使われる。
  • 2段階目に定義されたSupportedCommandsAndParametersCombinations と言うTypeScriptユニオンをジェネリック引数として渡す事。

続き、具体的に何方のコマンドフレーズが入力されたか、判断する目的。 尚、安全に(any型・TypeScriptエラー無し) 該当しているオプションにアクセス出来る様にしなけれないけない(但し、規定値が無い任意な オプションはどうしても利用の前非undefined確認が必要)。 switch/caseは此の様な場合に取って丁度良い。

⑤ 名前でアプリケーションを実行

現在、アプリケーションがファイルパスを指定し ts-node で既に実行出来る、例えば

過半数の読者達はコマンド(混乱させてはいるが、汎用な用語に依るとアプリケーション名を意味する)でアプリケーションを実行したいだろうから、 上記の実行方法で満足しないはずだが、上記の方法が十分だから以前の手順で終了の場合を手短に考察しておこう。 先ずは、開発対象のコンソールインターフェースは単一のプロジェクトにしか使わない予定で、npmが、 別の方法で公開する予定も場合である。 特に、

サーバアプリケーションを
普通だと、サーバを実行する単一のコマンドフレーズしかなく、オプションでHTTPポート番号や ロギングレーベル等が指定される。
自動化専用スクリプト
普通はファイル生成・コピー・自動改善の様な課題で、但しGulpの様に一般手段が役に立たない程特別な目的である。

上記は一旦宜しいが、gulpやwebpackの様に、名前で コンソールアプリケーションを実行するにはどうすれば良いだろう。

先ずは、Node.jsランタイムはTypeScriptに対応 されていないので、ソースコードをJavaScriptに トラスパイルしなければいけない。 TypeScriptパッケージのコンソールインターフェースでも実現出来るし、例に出た Webpackでも活用すれば良い(但しTypeScript専用のプラグインが 必要に成る)。

然し、トランスパイリングを実行する前に、エントリーポイントからアプリケーションの論理を実行する関数か クラスをエクスポートしなければいけない。 「エントリーポイントからエクスポートする」事は、普通だと可笑しいだろう。 理由としては、コンソールアプリケーションの場合、エントリーポイントは直接実行されるのではなく、 実行ファイルを介して実行されるのだ。 下記の例では#executeApplication関数をエクスポートする エントリーポイント。

ソースコードのJavaScriptへの変換が終わり次第、手動で下記の様な内容のファイルを作ろう(例えば、 Executable.js)。

此処で一行目はシバンを含み、此のファイルをNode.jsで実行しなければいけない指定の役割。 次は、アプリケーションを実行する関数(上記の例だとexecuteApplication)をインポートし呼び出す。

質問

このファイルをエントリーポイントにすれば良いじゃない?シバンを除き、一般JavaScriptファイルはずだ。

回答
可能ではあり、規約だけの問題。 普通は、行言った実行ファイルには、単一の関数かメソッドの呼び出しより複雑な倫理を保管する事が避けられる。 とは言え、推薦に過ぎないので、守っている開発者もいて(例えば、Gulpの開発者)、守っていない開発者も要る(例えば Webpackの開発者、最終の確認は2024年の春)。

続き、上記の実行ファイルに貴方プロジェクトのpackage.jsonから参照 しなければいけない。 コンソールアプリケーションを名前で呼び出せる様に成るには、binフィルドを 連想配列系ののオブジェクトで埋めなければいけない、但しキーは コマンドであり、値は該当している実行ファイルへの相対 パス(ファイル名拡張無しでも可能)。

binフィルドに就いてもっと詳しい情報を npmのオフィシャル説明サイトで確認可能。

貴方のユーティリティを他のプロジェクトに導入するには、nameや versionの様に、追加でpackage.jsonのフィルド を記入しなけければいけない。 尚、npmでユーティリティを公開したい場合、更に多くのフィルドを記入するべき。 npmのオフィシャルサイトでは、関連している案内が有る。