NodeJSでのCLIの作成







みなさんこんばんは。







node.jsで没入型CLIを作成する際に問題が発生しました。 この目的で以前に使用されたvorpal 。 今回は、不必要な依存関係なしでやりたいと思っていました。さらに、コマンド引数を別の方法で取る可能性を考えました。







vorpalでは、コマンドは次のように記述されました。







setValue -s 1 -v 0
      
      





同意する-毎回書くことはあまり便利ではありません。







最終的に、チームは次のように変わりました。







 set 1: 0
      
      





実装方法-カットの下で







  1. また、良いボーナスは、スペースで区切られた値のリストの形式で、配列の形式でいくつかの引数を転送することです。


テキスト入力



readline



を使用してテキストを入力します。 次の方法で、自動補完をサポートするインターフェイスを作成します。







  let commandlist = []; commandlist.push("set", "get", "stored", "read", "description"); commandlist.push("watch", "unwatch"); commandlist.push("getbyte", "getitem", "progmode"); commandlist.push("ping", "state", "reset", "help"); function completer(line) { const hits = commandlist.filter(c => c.startsWith(line)); // show all completions if none found return [hits.length ? hits : commandlist, line]; } /// init repl const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "bobaos> ", completer: completer }); const console_out = msg => { process.stdout.clearLine(); process.stdout.cursorTo(0); console.log(msg); rl.prompt(true); };
      
      





console.log



は期待どおりに機能しconsole.log



つまり、 現在の行にテキストを表示し、行を折り返します。テキスト入力に依存しない外部イベントによってトリガーされた場合、データは入力行に表示されます。 したがって、console_out関数を使用します。この関数は、コンソールへの出力後、readline入力行を呼び出します。







パーサー



文字列をスペースに分割し、個々の部分を分離して処理できるように思われます。 ただし、スペースを含む文字列パラメーターを渡すことはできません。 とにかく、余分なスペースとタブを削除する必要があります。







当初、彼はパーサーを自分で実装することを計画し、C言語に関するHerbert Schildtの本から降順の再帰的パーサーをJSに書き換えました。実行中に、パーサーを単純化することが決定されました 執筆の過程でebnfパッケージを見つけ、BNF / EBNF構文定義システムに興味を持ち慣れてきたので、アプリケーションで使用することにしました。







文法



文法ファイルでコマンドと引数について説明します。

開始するには、次を定義します。







  1. 式は1行で構成されます。 3行以上を処理する必要はありません。
  2. 式の先頭はコマンド識別子です。 さらなる議論。
  3. コマンドの数は限られているため、それぞれが文法ファイルに書き込まれます。


エントリポイントは次のとおりです。







 command ::= (set|get|stored|read|description|getbyte|watch|unwatch|ping|state|reset|getitem|progmode|help) WS*
      
      





WS *は空白-スペースまたはタブ文字を意味します。 次のように説明されています。







 WS ::= [#x20#x09#x0A#x0D]+
      
      





これは、文字スペース、タブ、または改行が何度も発生することを意味します。







チームに移りましょう。

引数なしの最も単純な:







 ping ::= "ping" WS* state ::= "state" WS* reset ::= "reset" WS* help ::= "help" WS*
      
      





さらに、スペースまたは配列で区切られた自然数のリストを取得するコマンド。







 BEGIN_ARRAY ::= WS* #x5B WS* /* [ left square bracket */ END_ARRAY ::= WS* #x5D WS* /* ] right square bracket */ COMMA ::= WS* #x2C WS* /* , comma */ uint ::= [0-9]* UIntArray ::= BEGIN_ARRAY (uint WS* (COMMA uint)*) END_ARRAY UIntList ::= (uint WS*)* get ::= "get" WS* ( UIntList | UIntArray )
      
      





したがって、次の例はgetコマンドに適しています。







 get 1 get 1 2 3 5 get [1, 2, 3, 5, 10]
      
      





次に、setコマンド。入力ペアid:valueまたは値の配列を受け取ります。







 COLON ::= WS* ":" WS* Number ::= "-"? ("0" | [1-9] [0-9]*) ("." [0-9]+)? (("e" | "E") ( "-" | "+" )? ("0" | [1-9] [0-9]*))? String ::= '"' [^"]* '"' | "'" [^']* "'" Null ::= "null" Bool ::= "true" | "false" Value ::= Number | String | Null | Bool DatapointValue ::= uint COLON Value DatapointValueArray ::= BEGIN_ARRAY (DatapointValue WS* (COMMA DatapointValue)*)? END_ARRAY set ::= "set" WS* ( DatapointValue | DatapointValueArray )
      
      





したがって、setコマンドの場合、次の表記形式が正しいです。







 set 1: true set 2: 255 set 3: 21.42 set [1: false, 999: "hello, friend"]
      
      





jsでの処理



ファイルを読み取り、パーサーオブジェクトを作成します。







 const grammar = fs.readFileSync(`${__dirname}/grammar`, "utf8"); const parser = new Grammars.W3C.Parser(grammar);
      
      





さらに、データを入力すると、readlineオブジェクトのインスタンスがlineイベントで通知し、次の関数によって処理されます。







 let parseCmd = line => { let res = parser.getAST(line.trim()); if (res.type === "command") { let cmdObject = res.children[0]; return processCmd(cmdObject); } };
      
      





コマンドが正しく記述されていれば、パーサーはツリーを返します。各要素には、タイプ、子フィールド、およびテキストフィールドがあります。 タイプフィールドは、現在の要素のタイプ値を受け入れます。 つまり パーサーにpingコマンドを渡すと、ツリーはトレースのようになります。 方法:







 { "type": "command", "text": "ping", "children": [{ "type": "ping", "text": "ping", "children": [] }] }
      
      





次の形式で書き込みます。







 command ping Text = "ping"
      
      





コマンド「get 1 2 3」の場合、







 command get UIntList uint Text = "1" uint Text = "2" uint Text = "3"
      
      





次に、各コマンドを処理し、必要なアクションを実行して、結果をコンソールに出力します。







その結果、最小限の依存関係で作業を高速化する非常に便利なインターフェイスが実現します。 私が説明します:







(たとえば)グループアドレスを読み取るためのグラフィカルインターフェイス(ETS)では、入力フィールドに1つのグループアドレスを入力し、(または複数のTAB)をクリックして要求を送信する必要があります。







vorpalを介して実装されたインターフェイスでは、コマンドは次のとおりです。







 readValue -s 1
      
      





または:







 readValues -s "1, 3"
      
      





パーサーを使用すると、不要な「-s」要素と引用符を回避できます。







 read 1 3
      
      





リンク



  1. https://github.com/bobaoskit/bobaos.tool-プロジェクトリポジトリ。 コードを見ることができます。
  2. http://menduz.com/ebnf-highlighter/-文法をその場で編集および確認できます。



All Articles