軽い気持ちで、node.jsのreadlineを使ってstdinとstdoutを繋いでコマンドインタプリタを書いてみた。
バラックなソースはそう面倒も無くできた。
// コマンドライン CmdLine.js
module.exports = class CmdLine {
constructor(cmds) {
this.cmds = cmds;
this.reader = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
//コンソールからの1行入力処理
this.reader.on('line',(line)=>{
var cmdLine = line.split(/,|\s/g); // コマンドラインの入力をカンマまたは空白文字を区切り文字として処理
this.cmdAnalize(cmdLine);
this.prompt();
});
// Ctrl+Dの時の処理
process.stdin.on('end',()=>{
//do something
});
//コマンドリスト
this.cmds = this.cmds.concat( [
{ name: "exit", help: "終了", proc: (cmdLine)=>{
this.log("EXIT:");
process.exit(0);
}},
{ name: "help", help: "ヘルプ", proc: (cmdLine)=>{
this.log("HELP:");
var msg = this.cmds.map((e,i,a)=>{ return "" + (i+1) + ":" + e.name + ":" + e.help; }).join('\r\n');
this.log(msg);
}}
]);
//最初のプロンプト表示
this.prompt();
}
//プロンプト※処理が終わってから出力する
prompt(msg) {
if( msg === undefined ) { msg = 'command:(' + this.cmds.map((e)=>{ return e.name; }).join(',') + ')';}
process.nextTick(()=>{ console.log(msg); });
}
//LOG
log(msg) {
console.log(msg);
}
//コマンドラインの最初の単語をキーワードとしてコマンドリストを検索し実行する
cmdAnalize(cmdLine) {
if(cmdLine.length==0 || cmdLine[0] == '') { return; }
var cmd = this.cmds.filter((e)=>{
return e.name.substring(0,cmdLine[0].length) == cmdLine[0].toLowerCase();
});
switch(cmd.length) {
// Not found.
case 0:
this.log('\'' + cmdLine[0] + '\' unknown command.');
break;
case 1:
// Command execute
cmd[0].proc(cmdLine);
break;
default:
// Duplex pattern.
this.log('\'' + cmd.map((e)=>{ return e.name; }).join('\' or \'') + '\' commands. retry.');
break;
}
};
}
// End of CmdLine.js .
テストコードは
// テストコード test.js
const CL = require('./CmdLine.js');
var cmds = [
{ name: "list", help: "リスト", proc: (cmdLine)=>{
cl.log("LIST:");
}},
{ name: "last", help: "ラスト", proc: (cmdLine)=>{
cl.log("LAST:");
}},
];
var cl = new CL(cmds);
// End of CmdLine.js .
ハマったのは、コンストラクタの中のコールバックで{ … } の中で、thisを使おうとしたら、thisがプロセスか何かに割り当てられ xxx not function などとエラってしまった。
{ … } を { … } .bind(this)と書けばいいけど、 { … } の中に { … } があると、.bind(this)が必要になる。
つまり、 { … } の代わりに { … } .bind(this) と書き続けることになるので、サクっと降参して、無名関数は止めて => 形式にして、thisの割り当てを期待するようにした。
勿論、別ファイルのtest.jsでは無効なので、thisの代わりにnewしたcl変数を指定した。
あ、自前のコマンドはnewする時に引数で渡してください。(汗
で、こんなの書いて何が嬉しいのか?と云えば
> node test.js と打てば
mailServer>node test.js
command:(list,last,exit,help)
と出るので、例えば、test.jsでSMTPとかPOP3のサービスを起動させた場合、jsソース中の変数(ファイリングしているメール・ファイルとかログとか)を自前のコマンドから色々と操作できるのです。
そういうのを作ろうとすると普段は別EXEを作ったり結構面倒なのですが、node.jsなら CmdLine.js をコピってきて、対象サービスにtest.jsなコードをくっ付ければ済むので非常にありがたいです。
後は、CmdLine.jsをもっと短く書ければいいんだけどなぁ・・・(遠い目