目录
加餐:命令行交互原理
学习路径
readline 源码分析
如何开发命令行交互列表
实现原理
架构图
本章学习路径和学习目标
readline 的使用方法和实现原理
高能:深入讲解 readline 键盘输入监听实现原理
秀操作:手写 readline 核心实现
命令行样式修改的核心原理:ansi 转移序列讲解
响应式库 rxjs 快速入门
放大招:手写命令行交互式列表组件(上)(下)
inquirer 源码执行流程分析
加餐:命令行交互原理
学习路径
·掌握:readline / events / stream / ansi-escapes(实现命令行中特殊显示) / rxjs(响应式模型库)
·掌握命令行交互的实现原理,并实现一个可交互的列表
·分析 inquirer 源码掌握其中的关键实现
ANSI-escape-code 查阅文档:https://handwiki.org/wiki/ANSI_escape_code
readline 源码分析
·强制将函数转换为构建函数
if (!(this instance Interface)) {
    return new Interface(input, output, completer, terminal)
}·继承 EventEmitter
EventEmitter.call(this)·监听键盘事件
emitKeypressEvents(input, this)
// `input` usually refers to stdin
input.on('keypress', onkeypress)
input.on('end', ontermend)readline 核心实现原理:
 
 
注:readline 利用了 Generator 函数的特性,还不熟悉 Generator 函数的同学可以查看 https://es6.ruanyifeng.com/#docs/generator
如何开发命令行交互列表
实现原理

架构图

 
 
 
 
 
  
本章学习路径和学习目标
......
readline 的使用方法和实现原理
nodejs 内置库,主要帮助我们管理输入流的。命令当中要交互的方式一定是需要用户提供一些输入的。readline 就可以很好的帮我们去一次一次地读取输入流。这里要注意的是,这个输入不只是我们输入一些字符,还包括输入我们键盘上的内容,如 上下左右 esc 回车 空格,这些都在输入流的监听范围内。
// Usage:
const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});
rl.question('your name: ', (answer => {
  console.log(answer);
  rl.close();  // 需要手动关闭
}));
高能:深入讲解 readline 键盘输入监听实现原理
// generator 基础知识
function* g() {
  console.log('read');
  let ch = yield;
  console.log(ch);
  let s = yield;
  console.log(s);
}
const f = g();
f.next();
f.next('a');
f.next('b');
// 输出 read a b秀操作:手写 readline 核心实现
function stepRead(callback) {
  // readline 源码为什么那么长,在处理各种用户输入的场景,如 回车 ctrl+C。场景是特别复杂的。
  // 每输入一次就会监听。所有模式全部由开发者控制。原生模式,所有内容都不会帮你实现了。
  function onkeypress(s) {
    output.write(s);  // 命令行打印。
    line += s;
    switch (s) {
      case '\r':
        input.pause();
        callback(line);
        break;
    }
  }
  const input = process.stdin;
  const output = process.stdout;
  let line = '';
  emitKeypressEvents(input);
  input.on('keypress', onkeypress);
  input.setRawMode(true);
  input.resume();
}
function emitKeypressEvents(stream) {
  function onData(chunk) {
    g.next(chunk.toString());  // toString 否则是个buffer。
  }
  const g = emitKeys(stream);
  g.next();
  stream.on('data', onData);
}
function* emitKeys(stream) {
  while (true) {
    let ch = yield;
    stream.emit('keypress', ch);
  }
}
stepRead(function(s) {
  console.log('answer:' + s);
});
命令行样式修改的核心原理:ansi 转移序列讲解
简单来说,定义一个规范,这个规范可以让我们在终端当中通过转义字符实现一些特殊操作。如:将光标上移或者下移 换行等,输入信息擦除,字体加粗倾斜变色等。
console.log('\x1B[41m\x1B[4m%s\x1B[0m', 'your name:');
// \x1B[4m 文字加下划线
// \x 十六进制,1B 是固定的,%s 占位符。
// 41m 红色,0m 无色。
// m 表示渲染的参数
// B 表示光标下移
// G 表示水平移动
console.log('\x1B[2B%s', 'your name2:')
console.log('\x1B[2G%s', 'your name3:')概念知与不知道,有天壤之别。
响应式库 rxjs 快速入门
异步库,和 promise 相似。inquirer 源码当中,大量使用 rxjs 去做回调、事件绑定监听。
const { range } = require('rxjs');
const { map, filter } = require('rxjs/operators');
const pipe = range(1, 200).pipe(
  filter(x => x % 2 === 1),
  map(x => x + x),
  filter(x => x % 3 === 0),
  filter(x => x % 6 === 0),
  filter(x => x % 9 === 0),
);
pipe.subscribe(x => console.log(x));
// 会打印符合条件的数字
放大招:手写命令行交互式列表组件(上)(下)
npm install -S mute-stream
npm install -S rxjs
npm install -S ansi-escapes  # 清屏const EventEmitter = require('events');
const readline = require('readline');
const MuteStream = require('mute-stream');
const { fromEvent } = require('rxjs');
const ansiEscapes = require('ansi-escapes');
const option = {
  type: 'list',
  name: 'name',
  message: 'select your name:',
  choices: [{
    name: 'sam', value: 'sam',
  }, {
    name: 'shuangyue', value: 'sy',
  }, {
    name: 'zhangxuan', value: 'zx',
  }],
};
function Prompt(option) {
  return new Promise((resolve, reject) => {
    try {
      const list = new List(option);
      list.render();
      list.on('exit', function(answers) {
        resolve(answers);
      })
    } catch (e) {
      reject(e);
    }
  });
}
class List extends EventEmitter {  // 完成事件的监听
  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);  // 对 output 封装 
    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 {
          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 emptyLines = ansiEscapes.eraseLines(this.height);
    this.output.write(emptyLines);
  }
  close() {
    this.output.unmute();
    this.rl.output.end();
    this.rl.pause();
    this.rl.close();
  }
}
Prompt(option).then(answers => {
  console.log('answers:', answers);
});
inquirer 源码执行流程分析
......














![[图神经网络]ViG(Vision GNN)网络代码实现](https://img-blog.csdnimg.cn/1ae45fb6da464bbe87769b5597ca1d75.png)




