readline
强制将函数转成构造函数
if (!(this instanceof Interface)) {
return new Interface(input, output, completer, terminal);
}
让当前函数/类具备事件驱动的能力 可以执行on 和emit方法
EventEmitter.call(this);
generate函数
执行generate函数 并不会执行函数体 而是返回一个函数 这个函数可以调用next方法向下执行到yield位置,每调用一次next向下走一个yield,
function* g() {
const a = yield;
log(a);
const b = yield;
log(b);
}
const function1 = g();
function1.next();
function1.next(2); //a=2
return;
readline监听事件原理
红色为用户输入的时候走的流程
//手写readline核心实现
function stepRead(cb) {
const input = process.stdin;
const output = process.stdout;
let line = "";
function onkeypress(s) {
output.write(s);
line += s;
switch (s) {
case "\r":
input.pause();
input.setRawMode(false);
cb(line);
break;
}
}
eventKeypressEvents(input);
input.on("keypress", onkeypress);
input.setRawMode(true); //原生模式 所有字符的监控由我们自己控制 终端不进行控制
input.resume();
}
stepRead((s) => {
console.log("answer is ", s);
});
function eventKeypressEvents(stream) {
function onData(chunk) {
g.next(chunk.toString());
}
const g = emitKeys(stream);
g.next();
stream.on("data", onData);
}
function* emitKeys(stream) {
while (true) {
// 执行mac-mw-cli-test2 会卡在这个yield方法不动 直到输入字符 被ondata监听到走next 走到/r退出input
let ch = yield;
stream.emit("keypress", ch);
}
}
ansi
console.log("\x1B[41m\x1B[4m%s", "your name");
console.log("\x1B[2B%s", "your name2");
手写命令行交互
const eventEmiter = require("events");
const MuteStream = require("mute-stream");
const { title } = require("process");
const readline = require("readline");
const { fromEvent } = require("rxjs");
const ansiEscapes = require("ansi-escapes");
const option = {
type: "list",
name: "name",
message: "select your name : ",
choices: [
{
name: "mengwan",
value: "mengwan",
},
{
name: "wuyacheng",
value: "wuyacheng",
},
{
name: "liaoxiyue",
value: "liaoxiyue",
},
],
};
class List extends eventEmiter {
constructor(option) {
super();
this.name = option.name;
this.message = option.message;
this.choices = option.choices;
this.input = process.stdin;
const ms = new MuteStream();
ms.pipe(process.stdout);
this.output = ms;
this.rl = readline.createInterface({
input: this.input,
output: this.output,
});
this.selected = 0;
this.height = 0;
this.keypress = fromEvent(this.rl.input, "keypress").forEach(
this.onkeypress
);
this.haveSelected = false;
}
/** 处理键盘按下 上下 的事件 */
onkeypress = (keymap) => {
const key = keymap[1];
if (key.name === "down") {
this.selected++;
if (this.selected > this.choices.length - 1) {
this.selected = 0;
}
this.render();
} else if (key.name === "up") {
this.selected--;
if (this.selected < 0) {
this.selected = this.choices.length - 1;
}
this.render();
} else if (key.name === "return") {
this.haveSelected = true;
this.render();
this.close();
this.emit("exit", this.choices[this.selected]);
}
};
render() {
this.output.unmute();
this.clean();
this.output.write(this.getContent());
this.output.mute();
}
getContent = () => {
if (!this.haveSelected) {
let title =
"\x1B[32m?\x1B[39m \x1B[1m" +
this.message +
"\x1B[22m\x1B[0m\x1B[0m\x1B[2m(Use arrow keys)\x1B[22m\n";
this.choices.forEach((choice, index) => {
if (index === this.selected) {
/** 是否为最后一个元素 如果是则不加\n */
if (index === this.choices.length - 1) {
title += "\x1B[36m> " + choice.name + "\x1B[39m ";
} else {
title += "\x1B[36m> " + choice.name + "\x1B[39m \n";
}
} else {
/** 是否为最后一个元素 如果是则不加\n */
if (index === this.choices.length - 1) {
title += " " + choice.name;
} else {
title += " " + choice.name + "\n";
}
}
});
this.height = this.choices.length + 1;
return title;
} else {
const name = this.choices[this.selected].name;
let title =
"\x1B[32m\x1B[39m \x1B[1m" +
this.message +
"\x1B[22m\x1B[0m \x1B[36m" +
name +
"\x1B[39m\x1B[0m \n";
return title;
}
};
clean() {
const empty = ansiEscapes.eraseLines(this.height);
this.output.write(empty);
}
close() {
this.output.unmute();
this.rl.output.end();
this.rl.pause();
this.rl.close();
}
}
function prompt(options) {
return new Promise((resolve, reject) => {
try {
const list = new List(options);
list.render();
list.on("exit", (answers) => {
resolve(answers);
});
} catch (e) {
reject(e);
}
});
}
prompt(option).then((answers) => {
console.log("answers", answers);
});