ConsoleCommandsParser
クラス
コンソールコマンドの引数ベクトルの処理、バリデーションそしてオブジェクト系の特定の TypeScript型への変換のに使われているクラスである。 尚、全コマンドに関する参考本文(ヘルプ)生成機能もある。
デモ
下記の例の詳しい解説が論述される。
import { RawObjectDataProcessor } from "@yamato-daiwa/es-extensions";
import { ConsoleCommandsParser } from "@yamato-daiwa/es-extensions-nodejs";
namespace ApplicationConsoleLineInterface {
export enum CommandPhrases {
projectBuilding = "build",
packingOfBuild = "pack",
projectDeploying = "deploy",
referenceGenerating = "help"
}
export type SupportedCommandsAndParametersCombinations =
ProjectBuildingConsoleCommand |
PackingOfBuildConsoleCommand |
ProjectDeployingConsoleCommand |
ReferenceGeneratingConsoleCommand;
export type ProjectBuildingConsoleCommand = Readonly<{
phrase: CommandPhrases.projectBuilding;
requiredStringOption: string;
optionalStringOption?: string;
}>;
export type PackingOfBuildConsoleCommand = Readonly<{
phrase: CommandPhrases.packingOfBuild;
enumerationLikeStringOption: "FOO" | "BAR" | "BAZ";
numericOption?: number;
limitedNumericOption?: number;
}>;
export type ProjectDeployingConsoleCommand = Readonly<{
phrase: CommandPhrases.projectDeploying;
booleanOption: boolean;
JSON5_Option?: Readonly<{ foo: string; bar?: number; }>;
}>;
export type ReferenceGeneratingConsoleCommand = Readonly<{
phrase: CommandPhrases.referenceGenerating;
}>;
export const specification: ConsoleCommandsParser.CommandLineInterfaceSpecification = {
applicationName: "Example task manager",
applicationDescription: "Executes various tasks.",
commandPhrases: {
[CommandPhrases.projectBuilding]: {
isDefault: true,
description: "Builds the project for specified mode.",
options: {
requiredStringOption: {
description: "Example required string option",
type: ConsoleCommandsParser.ParametersTypes.string,
required: true,
shortcut: "a"
},
optionalStringOption: {
description: "Example optional string option",
type: ConsoleCommandsParser.ParametersTypes.string,
required: false
}
}
},
[CommandPhrases.packingOfBuild]: {
description: "Create the deployable pack of the project",
options: {
enumerationLikeStringOption: {
description: "Example enumeration like string option",
type: ConsoleCommandsParser.ParametersTypes.string,
defaultValue: "FOO",
allowedAlternatives: [
"FOO",
"BAR",
"BAZ"
]
},
numericOption: {
description: "Example numeric option",
type: ConsoleCommandsParser.ParametersTypes.number,
numbersSet: RawObjectDataProcessor.NumbersSets.naturalNumber,
required: false
},
limitedNumericOption: {
description: "Example numeric option with fixed minimal and maximal value",
type: ConsoleCommandsParser.ParametersTypes.number,
numbersSet: RawObjectDataProcessor.NumbersSets.anyInteger,
required: false,
minimalValue: -10,
maximalValue: 10
}
}
},
[CommandPhrases.projectDeploying]: {
description: "Deploys the project.",
options: {
booleanOption: {
description: "Example boolean option",
type: ConsoleCommandsParser.ParametersTypes.boolean,
shortcut: "b"
},
JSON5_Option: {
description: "Example JSON5 option",
type: ConsoleCommandsParser.ParametersTypes.JSON5,
shortcut: "j",
required: false,
validValueSpecification: {
foo: {
type: RawObjectDataProcessor.ValuesTypesIDs.string,
required: true,
minimalCharactersCount: 1
},
bar: {
type: RawObjectDataProcessor.ValuesTypesIDs.number,
numbersSet: RawObjectDataProcessor.NumbersSets.anyInteger,
required: false,
minimalValue: 1
}
}
}
}
},
[CommandPhrases.referenceGenerating]: {}
}
};
}
最低限の理論
コンソールコマンドに関する正式的な用語や此れの解剖学に対する規格は無く、
大体慣用のコンベンションしか存在してない。
事実上、Node.jsのprocess.argv
(「引数ベクトル」、C++言語から継承された概念)は文字列の配列に過ぎない。
此れをどうすれば良いか、開発者か其の依頼側か(其の両方か)が決めている。
実は、コンソールコマンドの解析は任意外部構成化データの解析の問題の一種類で、 此処で此の外部のデータは文字列の指数配列、従って要素の 順番を考慮する必要が有る。 標準のNode.js機能の中に、此の様な配列の解析・妥当性確認専用の機能が 無く、TypeScriptの型付け上可能な限り安全に解決出来る 第三者のライブラリでも、存在しているとは限らない。
条約的な用語
全体的に、コンソールコマンドは空白に別けられている文字列の値の順番に成っています。
コマンド アーギュメント1 アーギュメント2 … アーギュメントN
例えばWebpackの場合、コマンドは下記の様に成る事がある。
webpack build --mode development
- コマンド
- 事実上アプリケーションの名前に成っている(完全名か、省略名)。
例えば、Angularフレームワークの場合、コマンドは
ng
と言う省略である。 然し普通はアプリケーションの名前と一致しているか、此れに近い(例:webpack
、gulp
、lerna
)。 - オプション(キー)
- 2重のnダッシュから始まる。
上記の例に於いては
--mode
。 - 引数
- オプションの値である。
上記の例なら、
development
は--mode
オプションの引数に成っている。
- オプションは引数が無い場合、真の値の ブーリアンオプションとして見做される。 此の様に、当オプションが指定されていない場合、偽の値も同然。
- オプションがハイフンと一文字から成り立っている省略を持っている事が有る(例えば、
「
-d
」)。 面白い事だが、上記の例に出たWebpackには、-m
と言う省略が存在してい はいるが、`--mode`の省略には成っていない。 御覧の様に、省略を入力する事が早い引き換え、暗記するには日常利用が必要。 - アーギュメントは空白を含める事は出来るが、此の場合括弧に包む事が必要に成る。
--オプション1=引数1 --オプション2=引数2 ... --オプションN=引数N
と言った構文も存在している。
現在ConsoleCommandsParserに対応されていないが、需要が高いと言うフィードバックが届いたら、
将来のバージョンに対応される可能性が有る。
上記の例に於いては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
と言うコマンドフレーズが有るとする。
当のコマンドフレーズはコンソールアプリケーションのどいった機能に該当しているか、後で考えれば良いが、ユーザー達にとって
短いコマンドフレーズの方が良い事に対して開発者達にとって一義的なコマンドフレーズの方が良い時、短い奴を
列挙の値の中に保持すれば良い。
enum CommandPhrases {
projectBuilding = "build",
packingOfBuild = "pack",
projectDeploying = "deploy",
referenceGenerating = "referenceGenerating"
}
各コマンドフレーズにとって以前定義された列挙の一
要素を含むphrase
プロパティと、該当している
コマンドフレーズの全オプションから成り立っているオブジェクトを定義しておこう。
最後の奴以外、全コマンドフレーズはオプションを持っているとする。
type ProjectBuildingConsoleCommand = {
phrase: CommandPhrases.projectBuilding;
requiredStringOption: string;
optionalStringOption?: string;
};
type PackingOfBuildConsoleCommand = {
phrase: CommandPhrases.packingOfBuild;
enumerationLikeStringOption: "FOO" | "BAR" | "BAZ";
numericOption?: number;
limitedNumericOption?: number;
};
type ProjectDeployingConsoleCommand = {
phrase: CommandPhrases.projectDeploying;
booleanOption: boolean;
JSON5_Option?: Readonly<{ foo: string; bar?: number; }>;
};
type ReferenceGeneratingConsoleCommand = {
phrase: CommandPhrases.referenceGenerating;
};
利用者は何方のコマンドフレーズを入力するか、我らは事前に知れないが、 バリデーション規則を正しく定義すれば(定後方法を後ほど説明する)以前定義されたコマンドフレーズの中一つ に成り、オプションも此のコマンドフレーズに該当していると保証。
type SupportedCommandsAndParametersCombinations =
ProjectBuildingConsoleCommand |
PackingOfBuildConsoleCommand |
ProjectDeployingConsoleCommand |
ReferenceGeneratingConsoleCommand;
ConsoleCommandsParser
クラスのparse
メソッドはSupportedCommandsAndParametersCombinations
と
NodeJS_InterpreterAbsolutePath
と言うプロパティ
(process.argv
配列の最初の
2要素に該当している)付きの
SupportedCommandsAndParametersCombinations
と言う以前定義された
型のオブジェクトを返す。
事実上、ParsedCommand<SupportedCommandsAndParametersCombinations>
に於いてParsedCommand
TypeScriptジェネリックは、
SupportedCommandsAndParametersCombinations
ユニオン
に此の2個のプロパティが追加されるとTypeScript
に伝える。
const parsedConsoleCommand: ConsoleCommandsParser.
ParsedCommand<SupportedCommandsAndParametersCombinations> =
ConsoleCommandsParser.parse(ApplicationConsoleLineInterface.specification);
でも、利用者が具体的に何方のコマンドフレーズを入力したか、どうやって判断すれば良いだろう。
判別可能なユニオンの全サブタイプはphrase
と
言った当ユニオンの中に唯一な値の識別子役割の
プロパティを持っているので、条件構成(此の場合だと、switch/case
が
丁度良い)を持ちって具体的なサブタイプが判断出来る。
switch (parsedConsoleCommand.phrase) {
case ApplicationConsoleLineInterface.CommandPhrases.projectBuilding: {
console.log("Build project");
console.log(parsedConsoleCommand.requiredStringOption);
console.log(parsedConsoleCommand.optionalStringOption);
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.packingOfBuild: {
console.log("Pack build");
console.log(parsedConsoleCommand.enumerationLikeStringOption);
console.log(parsedConsoleCommand.numericOption);
console.log(parsedConsoleCommand.limitedNumericOption);
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.projectDeploying: {
console.log("Deploy project")
console.log(parsedConsoleCommand.phrase);
console.log(parsedConsoleCommand.booleanOption);
console.log(parsedConsoleCommand.JSON5_Option);
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.referenceGenerating: {
console.log(ConsoleCommandsParser.generateFullHelpReference(ApplicationConsoleLineInterface.specification));
}
}
結果のして、各caseブロックの中に、現在のコマンドフレーズに該当している
parsedConsoleCommand
のプロパティにアクセスする事が出来る。
もし別のコマンドフレーズに該当しているオプションに参照しているプロパティにアクセスすると、
TypeScriptが気付きエラーを起こす。
但し、上記の例だと、任意なプロパティが多いので、これら使う前に
非undefined確認が必要に成る。
コマンドラインインターフェース(CLI)開発 | 段階的な案内
① コマンドフレーズの初期揃いの決定
貴方のアプリケーションの中に利用可能なコマンドフレーズを決めておこう。
- 単一のコマンドフレーズが希望される場合が在る。 アプリケーションの専門が幅狭ければ、通常の事だと考えられる。
- いつでも新しいコマンドフレーズが追加出来るから、取り敢えず自分にとって明確なコマンドフレーズを決めよう。
全コマンドフレーズを列挙の中に保持しておこう。 列挙のキーはコンソールに実際に入力される文字列と一致させる必要はなく、資料が無くても キーの意味が開発者にとって明かにした方が良い。 多数の開発者と同じ様に、実際に入力される文字列を短くしたいなら、これらを列挙要素の値に保持しておこう。 幸いにTypeScriptなら文字列要素の 列挙が定義可能で、他の言語なら、同じ機能が無いか制限が有る事が多い。
export enum CommandPhrases {
projectBuilding = "build",
packingOfBuild = "pack",
projectDeploying = "deploy",
referenceGenerating = "help"
}
上記通り、列挙のキーとして動詞無しの単語の組み合わせ であり、代わりに動名詞が使われている。 名前は動詞から始まるが、関数・メソッドの名前に成っていないと、混乱が発生する。 然し、スタイルガイド定期な要求と成っているので、反対し自分の規律が導入出来る。
此のコードべ別のファイル(例えば、ApplicationConsoleLineInterface.ts)で書く推薦。
其れに、明確なコンテキストの定義の為当ファイルの中身を名スペース
(例えばApplicationConsoleLineInterface
)に包む事を御勧め。
開発者達の中に(TypeScriptの)名スペースの反対派は居るが、こういった
名スペースの否定的な評判の原因は、コードをモジュールに分割する旧い方法に多く有る。
上記の例の様に、名スペースを型や定数の為のコンテキストコンテナー
として使っても、悪影響が特に無い。
② 各コマンドフレーズのオプションの決定
各コマンドフレーズにとってオプションの揃い(型、 必須性や有れば規定値)を決めておこう。
- コマンドフレーズの場合と同じ様に、より早く自分のコードからフィードバックを取るには初期段階では自分にとって 最も明確で、最低限のオプションにすれば良い。
- 一部分のコマンドフレーズがオプションが無い事が有る。
特に問題無い事であり、将来的にオプションが必要に成ったら、其の時追加されば良い。
現在の例だと、
referenceGenerating
コマンドフレーズは オプションが無い。
今、各コマンドフレーズにとって下記のプロパティを含むオブジェクト型 を定義しておこう。
- phrase
- オブジェクト型自体と同じコマンドフレーズに該当しているコマンドフレーズの
列挙(上記の例に於いては
CommandPhrases
)の 要素でなくてはならない。 当プロパティは識別子の様に成り、利用者は具体的に何方のコマンドフレーズの入力したか、判別するのに使われる。 此れの型は、列挙自体ではなく (上記の例だとCommandPhrases
)、此の列挙の具体的な要素 (例えばCommandPhrases.projectBuilding
)にしてはいけない (TypeScriptの差別化組合せ上は意味の無い事ではない)。 - コマンドのオプション
対象のコマンドフレーズのに該当している全オプション。 キーは利用者によりコンソール入力されるものと必ず一致させなければいけない訳ではなく、 資料が無くても和訳出来るエンジン達にとって意味を明確にすれば良い。 各プロパティの型は下記の利用可能な型の何方か でなくてはならない。
string
number
boolean
- 主要パッケージ(@yamato-daiwa/es-extensions)からのParsedJSON系のオブジェクト
オプションは任意で、但し規定値は予定されていない場合、 コロン(キーと値の型の区切り)の前に 疑問符を入れる必要が有る。
やがて、各コマンドフレーズに該当している以前作ったオブジェクト型にとって
ユニオンを定義しておいてください (下記の例に於いては
SupportedCommandsAndParametersCombinations
)。
import { RawObjectDataProcessor } from "@yamato-daiwa/es-extensions";
import { ConsoleCommandsParser } from "@yamato-daiwa/es-extensions-nodejs";
namespace ApplicationConsoleLineInterface {
export enum CommandPhrases {
projectBuilding = "build",
packingOfBuild = "pack",
projectDeploying = "deploy",
referenceGenerating = "help"
}
export type SupportedCommandsAndParametersCombinations =
ProjectBuildingConsoleCommand |
PackingOfBuildConsoleCommand |
ProjectDeployingConsoleCommand |
ReferenceGeneratingConsoleCommand;
export type ProjectBuildingConsoleCommand = Readonly<{
phrase: CommandPhrases.projectBuilding;
requiredStringOption: string;
optionalStringOption?: string;
}>;
export type PackingOfBuildConsoleCommand = Readonly<{
phrase: CommandPhrases.packingOfBuild;
enumerationLikeStringOption: "FOO" | "BAR" | "BAZ";
numericOption?: number;
limitedNumericOption?: number;
}>;
export type ProjectDeployingConsoleCommand = Readonly<{
phrase: CommandPhrases.projectDeploying;
booleanOption: boolean;
JSON5_Option?: Readonly<{ foo: string; bar?: number; }>;
}>;
export type ReferenceGeneratingConsoleCommand = Readonly<{
phrase: CommandPhrases.referenceGenerating;
}>;
}
- 再び名前の付け方と関連しているスタイルガイドの例に気を付けてもらいたい。 具体的なコマンドフレーズに該当している各型の名前は動詞を 含めていなく、~ConsoleCommand末尾は原型と成っています。
- Readonlyと言うユーティリティ型の利用は必須ではないが、 推薦だ。 此れを使う事に依り、以前宣言されたオブジェクト型のプロパティはプログラミングの実行中変わる事は無い と明示的に指示する。
③ コンソールアプリケーションの仕様を定義
コンソールアプリケーション仕様通りに入力されたコンソールコマンドがバリデーション・処理される事に成り、 必要に応じて当コンソールアプリケーションの使い方に対して案内本文が生成される。
ConsoleCommandsParser.CommandLineInterfaceSpecification
型の
定数を定義しておこう。
多層オブジェクト型と成り、全コマンドフレーズ
(各一個のオプションを含めて)の定義及び案内を生成する為のデータを含まなければいけません。
ApplicationConsoleLineInterface
の様な名スペースにコードを包む推薦通り
してくれた場合、consoleCommandLineInterfaceSpecification
の様に長い定数名
は不要で、ApplicationConsoleLineInterfaceと言うコンテキスト
が有るから、単にspecification
だけで良い。
import { RawObjectDataProcessor } from "@yamato-daiwa/es-extensions";
import { ConsoleCommandsParser } from "@yamato-daiwa/es-extensions-nodejs";
namespace ApplicationConsoleLineInterface {
export enum CommandPhrases {
projectBuilding = "build",
packingOfBuild = "pack",
projectDeploying = "deploy",
referenceGenerating = "help"
}
export type SupportedCommandsAndParametersCombinations =
ProjectBuildingConsoleCommand |
PackingOfBuildConsoleCommand |
ProjectDeployingConsoleCommand |
ReferenceGeneratingConsoleCommand;
export type ProjectBuildingConsoleCommand = Readonly<{
phrase: CommandPhrases.projectBuilding;
requiredStringOption: string;
optionalStringOption?: string;
}>;
export type PackingOfBuildConsoleCommand = Readonly<{
phrase: CommandPhrases.packingOfBuild;
enumerationLikeStringOption: "FOO" | "BAR" | "BAZ";
numericOption?: number;
limitedNumericOption?: number;
}>;
export type ProjectDeployingConsoleCommand = Readonly<{
phrase: CommandPhrases.projectDeploying;
booleanOption: boolean;
JSON5_Option?: Readonly<{ foo: string; bar?: number; }>;
}>;
export type ReferenceGeneratingConsoleCommand = Readonly<{
phrase: CommandPhrases.referenceGenerating;
}>;
export const specification: ConsoleCommandsParser.CommandLineInterfaceSpecification = {
applicationName: "Example task manager",
applicationDescription: "Executes various tasks.",
commandPhrases: {
[CommandPhrases.projectBuilding]: {
isDefault: true,
description: "Builds the project for specified mode.",
options: {
requiredStringOption: {
description: "Example required string option",
type: ConsoleCommandsParser.ParametersTypes.string,
required: true,
shortcut: "a"
},
optionalStringOption: {
description: "Example optional string option",
type: ConsoleCommandsParser.ParametersTypes.string,
required: false
}
}
},
[CommandPhrases.packingOfBuild]: {
description: "Create the deployable pack of the project",
options: {
enumerationLikeStringOption: {
description: "Example enumeration like string option",
type: ConsoleCommandsParser.ParametersTypes.string,
defaultValue: "FOO",
allowedAlternatives: [
"FOO",
"BAR",
"BAZ"
]
},
numericOption: {
description: "Example numeric option",
type: ConsoleCommandsParser.ParametersTypes.number,
numbersSet: RawObjectDataProcessor.NumbersSets.naturalNumber,
required: false
},
limitedNumericOption: {
description: "Example numeric option with fixed minimal and maximal value",
type: ConsoleCommandsParser.ParametersTypes.number,
numbersSet: RawObjectDataProcessor.NumbersSets.anyInteger,
required: false,
minimalValue: -10,
maximalValue: 10
}
}
},
[CommandPhrases.projectDeploying]: {
description: "Deploys the project.",
options: {
booleanOption: {
description: "Example boolean option",
type: ConsoleCommandsParser.ParametersTypes.boolean,
shortcut: "b"
},
JSON5_Option: {
description: "Example JSON5 option",
type: ConsoleCommandsParser.ParametersTypes.JSON5,
shortcut: "j",
required: false,
validValueSpecification: {
foo: {
type: RawObjectDataProcessor.ValuesTypesIDs.string,
required: true,
minimalCharactersCount: 1
},
bar: {
type: RawObjectDataProcessor.ValuesTypesIDs.number,
numbersSet: RawObjectDataProcessor.NumbersSets.anyInteger,
required: false,
minimalValue: 1
}
}
}
}
},
[CommandPhrases.referenceGenerating]: {}
}
};
}
applicationName
プロパティに貴方のアプリケーションの名前を指定しておこう。 ターミナルに入力される名前と必ずしも一致しなければいけない訳ではなく、複数の言葉から成り立っている場合空白で訳ても良い。 指定された名前は参考の生成の為に使われる。applicationName
と違って、applicationDescription
(和訳:アプリケーションの記述)プロパティは任意ではありますが、ちゃんとした参考を生成するには 短くても良いからアプリケーションについて一言を指定する推薦。commandPhrases
プロパティは 各コマンドフレーズの仕様を含めなくてはならない。 連想配列系のオブジェクトと成り、キーはコマンドフレーズと一致 しなければいけない。 一番安全なのは、ブラケット表記法を活用し、当オブジェクトのキーをCommandPhrases
列挙の要素に 結び付く事(例えば、[CommandPhrases.packingOfBuild]
)。 連想配列の値なら、- 該当しているコマンドフレーズは規定に成っている場合、
isDefault
と言う 真偽型のプロパティにtrue
をしていする事。 規定のコマンドフレーズは一個しか 有り得ないので、isDefault: true
を指定しても良いのは、最大 一個のコマンドフレーズ、 さもなくばアプリケーションの起動の際例外が投げられる。 - 上述の
applicationDescription
の様に、コマンドフレーズの仕様にはdescription
プロパティ(和訳:コマンドフレーズの記述)が指定 出来る。 任意に成ってはいるが、高品質の参考本文を生成するにはしたいした方が良い。 上記の例だと、help
(CommandPhrases.referenceGenerating
)コマンドだけ記述がついていない。 - コマンドフレーズがオプションがある場合、
options
プロパティ(こいつも連想配列系のオブジェクトである)を埋める事。
- 該当しているコマンドフレーズは規定に成っている場合、
各コマンドフレーズのオプションの定義
コマンドフレーズのオプションが多ければ、コマンドフレーズの オプションの使用定義こそ此の段階の大抵の時間がかかる。 とは言え、難しい事は無くて、コンソールに入力される値と同じ対象コマンドフレーズの 全オプションをオブジェクトのキーで指定し、 オブジェクトの値なら、オブジェクト型と成り、下記通り此れの中に オプションの型、必須・任意等を指定すれば良い。
ConsoleCommandsParser.ParametersTypes
列挙の 要素でオプションの型を指定しておこう。 現在、文字列型、数型、真偽型及びオブジェクト型 (入力の際妥当なJSON5型の文字列でなければけない)。- オプションが規定値が有る場合、
defaultValue
プロパティを 該当している型の値で指定しておこう。 規定値が無い場合、代わりにrequired
と言う真偽型の プロパティを指定する必要がある(無論、必須の場合はtrue
)。 但し、任意の真偽型のオプションの場合、 コンソールに明示的に入力されなかった値は明示的に入力されたfalse
も同然なので、 TypeScriptはdefaultValue
を指定されない。 - 高品質なコードを目指しているが、説明書を読まない意味が分からないオプション名でも、利用者達の入力の便利さの為に短いオプション名にしたい場合、
コードの整備者の為に仕様書を読まなくても和訳すれば意味が分かるオプション名を
newName
プロパティに指定しておこう。 - オプションの為一文字のの省略(フラッグ)を確保したい場合、望ましい省略を
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
を呼び出す事。
import { ConsoleCommandsParser } from "@yamato-daiwa/es-extensions-nodejs";
import ApplicationConsoleLineInterface from "./ConsoleLineInterface";
const parsedConsoleCommand: ConsoleCommandsParser.
ParsedCommand<ApplicationConsoleLineInterface.SupportedCommandsAndParametersCombinations> =
ConsoleCommandsParser.parse(ApplicationConsoleLineInterface.specification);
- 直近の段階で定義されたアプリケーションコマンドの資料は必須の引数である。
- 2引数目を指定しない限り、
process.argv
は引数ベクトル として使われる。 - 2段階目に定義された
SupportedCommandsAndParametersCombinations
と言うTypeScriptユニオンをジェネリック引数として渡す事。
続き、具体的に何方のコマンドフレーズが入力されたか、判断する目的。
尚、安全に(any
型・TypeScriptエラー無し)
該当しているオプションにアクセス出来る様にしなけれないけない(但し、規定値が無い任意な
オプションはどうしても利用の前非undefined確認が必要)。
switch/case
は此の様な場合に取って丁度良い。
switch (parsedConsoleCommand.phrase) {
case ApplicationConsoleLineInterface.CommandPhrases.projectBuilding: {
console.log("Build project", parsedConsoleCommand);
console.log("requiredStringOption", parsedConsoleCommand.requiredStringOption);
console.log("optionalStringOption", parsedConsoleCommand.optionalStringOption);
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.packingOfBuild: {
console.log("Pack project", parsedConsoleCommand);
console.log("enumerationLikeStringOption", parsedConsoleCommand.enumerationLikeStringOption);
console.log("numericOption", parsedConsoleCommand.numericOption);
console.log("limitedNumericOption", parsedConsoleCommand.limitedNumericOption);
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.projectDeploying: {
console.log("Deploy project", parsedConsoleCommand);
console.log("booleanOption", parsedConsoleCommand.booleanOption);
console.log("JSON5_Option", parsedConsoleCommand.JSON5_Option);
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.referenceGenerating: {
console.log(ConsoleCommandsParser.generateFullHelpReference(ApplicationConsoleLineInterface.specification));
}
}
⑤ 名前でアプリケーションを実行
現在、アプリケーションがファイルパスを指定し ts-node で既に実行出来る、例えば
ts-node EntryPoint.test.ts --requiredStringOption test --optionalStringOption sample
過半数の読者達はコマンド(混乱させてはいるが、汎用な用語に依るとアプリケーション名を意味する)でアプリケーションを実行したいだろうから、 上記の実行方法で満足しないはずだが、上記の方法が十分だから以前の手順で終了の場合を手短に考察しておこう。 先ずは、開発対象のコンソールインターフェースは単一のプロジェクトにしか使わない予定で、npmが、 別の方法で公開する予定も場合である。 特に、
- サーバアプリケーションを
- 普通だと、サーバを実行する単一のコマンドフレーズしかなく、オプションでHTTPポート番号や ロギングレーベル等が指定される。
- 自動化専用スクリプト
- 普通はファイル生成・コピー・自動改善の様な課題で、但しGulpの様に一般手段が役に立たない程特別な目的である。
上記は一旦宜しいが、gulpやwebpackの様に、名前で コンソールアプリケーションを実行するにはどうすれば良いだろう。
先ずは、Node.jsランタイムはTypeScriptに対応 されていないので、ソースコードをJavaScriptに トラスパイルしなければいけない。 TypeScriptパッケージのコンソールインターフェースでも実現出来るし、例に出た Webpackでも活用すれば良い(但しTypeScript専用のプラグインが 必要に成る)。
然し、トランスパイリングを実行する前に、エントリーポイントからアプリケーションの論理を実行する関数か
クラスをエクスポートしなければいけない。
「エントリーポイントからエクスポートする」事は、普通だと可笑しいだろう。
理由としては、コンソールアプリケーションの場合、エントリーポイントは直接実行されるのではなく、
実行ファイルを介して実行されるのだ。
下記の例では#executeApplication
関数をエクスポートする
エントリーポイント。
import { ConsoleCommandsParser } from "@yamato-daiwa/es-extensions-nodejs";
import ApplicationConsoleLineInterface from "./ConsoleLineInterface";
export function executeApplication(): void {
const parsedConsoleCommand: ConsoleCommandsParser.
ParsedCommand<ApplicationConsoleLineInterface.SupportedCommandsAndParametersCombinations> =
ConsoleCommandsParser.parse(ApplicationConsoleLineInterface.specification);
switch (parsedConsoleCommand.phrase) {
case ApplicationConsoleLineInterface.CommandPhrases.projectBuilding: {
// ...
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.packingOfBuild: {
// ...
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.projectDeploying: {
// ...
break;
}
case ApplicationConsoleLineInterface.CommandPhrases.referenceGenerating: {
console.log(ConsoleCommandsParser.generateFullHelpReference(ApplicationConsoleLineInterface.specification));
}
}
}
ソースコードのJavaScriptへの変換が終わり次第、手動で下記の様な内容のファイルを作ろう(例えば、 Executable.js)。
#!/usr/bin/env node
require("./EntryPoint").executeApplication();
此処で一行目はシバンを含み、此のファイルをNode.jsで実行しなければいけない指定の役割。
次は、アプリケーションを実行する関数(上記の例だとexecuteApplication
)をインポートし呼び出す。
このファイルをエントリーポイントにすれば良いじゃない?シバンを除き、一般JavaScriptファイルはずだ。
続き、上記の実行ファイルに貴方プロジェクトのpackage.json
から参照
しなければいけない。
コンソールアプリケーションを名前で呼び出せる様に成るには、bin
フィルドを
連想配列系ののオブジェクトで埋めなければいけない、但しキーは
コマンドであり、値は該当している実行ファイルへの相対
パス(ファイル名拡張無しでも可能)。
{
// ...
"bin": {
"my_app": "Executable"
},
// ...
}
bin
フィルドに就いてもっと詳しい情報を
npmのオフィシャル説明サイトで確認可能。
貴方のユーティリティを他のプロジェクトに導入するには、name
や
version
の様に、追加でpackage.jsonのフィルド
を記入しなけければいけない。
尚、npmでユーティリティを公開したい場合、更に多くのフィルドを記入するべき。
npmのオフィシャルサイトでは、関連している案内が有る。