Menulis CLI di NodeJS


Selamat malam semuanya.


Ada masalah untuk menulis CLI imersif Anda di node.js. Vorpal yang digunakan sebelumnya untuk tujuan ini. Kali ini saya ingin melakukannya tanpa ketergantungan yang tidak perlu, dan, di samping itu, saya mempertimbangkan kemungkinan mengambil argumen perintah secara berbeda.


Dengan vorpal, perintah ditulis sebagai berikut:


setValue -s 1 -v 0 

Setuju, menulis -s setiap kali sangat tidak nyaman.


Pada akhirnya, tim berubah menjadi sebagai berikut:


 set 1: 0 

Bagaimana itu bisa diterapkan - di bawah luka


  1. Juga, bonus yang baik adalah transfer beberapa argumen dalam bentuk daftar nilai, dipisahkan oleh spasi dan dalam bentuk array.

input teks


Saya menggunakan readline untuk memasukkan teks. Dengan cara berikut ini kami membuat antarmuka dengan dukungan untuk pelengkapan otomatis:


  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 berfungsi seperti yang diharapkan, yaitu menampilkan teks pada baris saat ini dan menutupnya, dan jika dipicu oleh peristiwa eksternal yang tidak tergantung pada input teks, data akan ditampilkan pada baris input. Oleh karena itu, kami menggunakan fungsi console_out, yang, setelah output ke konsol, memanggil jalur input readline.


pengurai


Tampaknya Anda dapat membagi string ke dalam ruang, memisahkan bagian-bagian individual dan memprosesnya. Tetapi kemudian tidak mungkin untuk melewatkan parameter string yang berisi spasi; dan dalam hal apa pun, akan perlu untuk menghapus spasi dan tab tambahan.


Awalnya, ia berencana untuk mengimplementasikan parser sendiri, menulis ulang parser rekursif turun dari buku Herbert Schildt pada bahasa C. ke dalam JS. Selama eksekusi, diputuskan untuk menyederhanakan parser, tetapi pada akhirnya tidak mungkin untuk mengimplementasikannya, karena dalam proses penulisan saya menemukan paket ebnf , dan, setelah menjadi tertarik dan terbiasa dengan sistem definisi sintaksis BNF / EBNF, saya memutuskan untuk menggunakannya dalam aplikasi saya.


tata bahasa


Kami menjelaskan perintah dan argumen dalam file tata bahasa.
Untuk memulai, tentukan yang berikut ini:


  1. Ekspresi terdiri dari satu baris. Kita tidak perlu memproses lebih dari dua baris.
  2. Di awal ekspresi adalah pengidentifikasi perintah. Argumen lebih lanjut.
  3. Ada sejumlah perintah, sehingga masing-masing ditulis dalam file tata bahasa.

Titik masuknya adalah sebagai berikut:


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

WS * berarti spasi - karakter spasi atau tab. Dijelaskan sebagai berikut:


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

Yang berarti ruang karakter, tab atau garis istirahat, terjadi sekali dan lebih banyak.


Mari kita beralih ke tim.
Yang paling sederhana, tanpa argumen:


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

Selanjutnya, perintah yang mengambil daftar bilangan asli dipisahkan oleh spasi, atau array.


 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 ) 

Jadi, contoh-contoh berikut ini benar untuk perintah get:


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

Selanjutnya, perintah yang ditetapkan, yang mengambil id pasangan input: nilai, atau array nilai.


 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 ) 

Dengan demikian, untuk perintah yang ditetapkan, formulir notasi berikut ini benar:


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

proses dalam js


Kami membaca file, membuat objek parser.


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

Lebih lanjut, ketika memasukkan data, sebuah instance dari objek readline memberi sinyal event garis, yang diproses oleh fungsi berikut:


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

Jika perintah itu ditulis dengan benar, parser mengembalikan pohon, di mana setiap elemen memiliki tipe, bidang anak-anak dan bidang teks. Bidang tipe mengambil nilai tipe elemen saat ini. Yaitu jika kita meneruskan perintah ping ke parser, pohon akan terlihat seperti jejak. cara:


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

Kami menulis dalam bentuk:


 command ping Text = "ping" 

Untuk perintah "dapatkan 1 2 3",


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

Selanjutnya, kami memproses setiap perintah, melakukan tindakan yang diperlukan dan menampilkan hasilnya di konsol.


Hasilnya adalah antarmuka yang sangat nyaman yang mempercepat pekerjaan dengan ketergantungan minimum. Saya akan menjelaskan:


di antarmuka grafis (ETS) untuk membaca alamat grup (misalnya), Anda harus memasukkan satu alamat grup di kolom input, lalu gunakan tombol mouse (atau beberapa TAB) untuk mengirim permintaan.


Dalam antarmuka yang diimplementasikan melalui vorpal, perintahnya adalah sebagai berikut:


 readValue -s 1 

Atau:


 readValues -s "1, 3" 

Menggunakan parser, Anda dapat menghindari elemen "-s" yang tidak perlu dan tanda kutip.


 read 1 3 

tautan


  1. https://github.com/bobaoskit/bobaos.tool - repositori proyek. Anda bisa melihat kodenya.
  2. http://menduz.com/ebnf-highlighter/ - Anda dapat mengedit dan memeriksa tata bahasa dengan cepat.

Source: https://habr.com/ru/post/id426615/


All Articles