脚手架难点(4)-命令行交互原理

113 阅读2分钟

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);
});