乐趣区

关于命令行:inquirer命令行交互原理一readline的实现方法和原理

背景

目前在开发 ohh-cli 脚手架,当中有用到了 inquirer 来实现命令行的交互;在好奇心的驱动下,想去钻研下它的执行和原理。

知识点
readline/events/stream/ansi-escapes/rxjs
指标

把握命令行交互的实现,并实现一个可交互的列表

一、readline 的实现办法和原理

1. readlie 介绍

node 的内置库,次要是治理输出流;监听键盘上所有按钮的操作;

node 官网的形容:https://nodejs.org/dist/lates…

The node:readline module provides an interface for reading data from a Readable stream (such as process.stdin) one line at a time.
翻译:readline 模块提供了一个接口,用于每次一行从可读流 (如 process.stdin) 读取数据。

2.readlie 的应用

readlie.js

const readline = require ('readline');

const rl = readline.createInterface({
  input: process.stdin,  //  ( process.stdin:零碎输出流);
  output: process.stdout  // (process.stdout: 零碎输入流);})
// 交互命令
rl.question('your name:', answer => {console.log(answer);

  rl.close(); // readline 不会主动敞开,须要调用命令敞开})

node 执行 js

your name:ohh
ohh

readline 是依据传入的输出流信息,逐行读取,按回车后,认为输出信息曾经完结;再将输出流的信息传入到输入流 output 中,进行展现;

3.readline 源码重点浏览

3.1 readlie 的整个过程的筹备工作

(1) 强制将函数转化为构造函数

functuon createInterface(input, output, completer, terminal) {return Interface(input, output, completer, terminal);
}

function Interface(input, output, completer, terminal) {
    // Instanceof 判断一个对象的正确类型,外部机制是通过原型链来判断的,测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。if(!(this instanceof Interface)) { // false 就强制转化
        return new Interface(input, output, completer, terminal)
    }
}

(2)readlie 如何去做事件监听的

利用 Generator

emitKeypressEvents(input, this);// 'input' uaually refers to stdin
input.on('keypress', onkeypress);
input.on('end', ontermend);

// Current line 
this.line = '';

this._setRawMode(true);
this.terminal = true;

// Cursor position on the lie.
this.cursor = 0;

this.history = [];
this.histryIndex = -1;
.....

input.resume();

调用 emitKeypressEvents(input, this);

function emitKeypressEvents(stream, iface) {if(stream[KEYPRESS_DECVODER])return;
    if(StringDecoder === undefined)
    StringDecoder = require('string_decoder).StrinngDecoder;
    stream[KEYPRESS_DECVODER] = new StringDecoder('utf8');
    
>1  stream[ESCAPE_DECODER] = emitKeys(stream);
    stream[ESCAPE_DECODER].next();
    const escapeCodeTimeout = () => stream[ESCAPE_DECODER].next('');
    let timeoutId;
    
    function onData(b) {....};
    
    function onNewListener(event) {if (event === 'keypress') {stream.on('data', onData);
            stream.removeListener('newListener', onNewListener);
        }
    }
    
    if (stream.listenerCount('keypress') > 0 ) {stream.on('data', onData);
    } else {>2        stream.on('newListener', onNewListener);
    }
}

调用 emitKeys,是一个 Generator 函数,所以返回的 stream[ESCAPE_DECODER] 是一个 Generator 函数;

emitKeys 办法

funnction* emitKyes (stream) {while(true) {
        let ch = yied;
        let s =ch;
        let escaped = false;
        const key = {name: undefiend...};
        if(ch === kEscape) {
            escaped = true;
            s += (ch = yield);
        }
        .......
    }
}

第一步执行
stream[ESCAPE_DECODER].next(); 执行,执行到都一个 yied 以前进行返回

while(true) {let ch = 

第二步执行
stream.on('newListener', onNewListener);
stream 是 input stream(输出流);减少一个 newListener 事件,就会跳出emitKeypressEvents 这个办法;

第三步执行
去执行 input.on('keypress', onkeypress);
event 在执行.on 办法的时候,会判断以后监听的这个对象,这个 input 是否存在 newListener 这个事件,如果存在这个事件,就会在 on 这个过程中,emit newListener 这个事件;所以会间接监听都这个办法 onNewListene;

onkeypress 办法

// 重写 on 办法
Readable.prototype.on = function(en, fn) {const res = Stream.prototype.on.call(this, ev, fn);
    const state = this._readableState;
    .....
}

const res = Stream.prototype.on.call(this, ev, fn);
进入 Stream.prototype

EventEmitter.prototype.addListener = function addListener(type, listener) {return _addListener(this, type, listener,false);
}

进入 _addListener 办法

checkListent(listener);
  
events = target._events;
....
// events 指是输出流
if(events.newListener === undefined) {
  //emit  newListener 事件
    target.emit('newListener', type, listener.listener ? listener.listener: listener);
}

newListener 事件 捕捉就就间接执行

emitKeypressEvents 中的 onNewListener 办法

    function onNewListener(event) {if (event === 'keypress') {stream.on('data', onData);
             stener', onNewListener);
        }
    }

第四步执行
stream.on(‘data’, onData);
输出流监听它的输出,这个办法会让以后的过程处于 hold on 的状态;期待用户去输出信息。

第五步执行
stream.removeListener(‘newListener’, onNewListener);
监听的办法给移除了。

第六步执行
this._setRawMode(true);

iput.setRawMode(true) //true 逐字监听; false 是逐行监听;
Inerface.prototype._setRawMode = functiion(mode) {

    const wasInRawMode = this.input.isRaw;
    if (typeof this.input.setRawMode === 'function') {this.input.setRawMode(mode);
    }
    return wasInRawMode;

}

第七步执行
input.resume
把输出流从终止状态复原到监听状态

整个过程的筹备工作就实现了,期待用户输出

3.1 readlie 的 用户输出信息后,回调流程

接管到用户输出的信息就会 emit 一个 data 事件;

function onData(b) {
     ....
    stream[ESCAPE_DECODER].next(r[i]); // r[i] 就是输出的“哦 "
}

emitKeys 办法

funnction* emitKyes (stream) {while(true) {
        let ch = yied;
        let s = ch; // s = 哦
        let escaped = false;
        const key = {
            squence: null,
            name: undefined,
            ctrl: false,
            meta: false,
            shift: false
        };
        if(ch === kEscape) {
            escaped = true;
            s += (ch = yield);
        }else if(判断是否是“\n”、"\t"、空格 等等) {.......} else if (/^[0-9A-Za-z]$/.test(ch)) {key.name = ch.toLowerCase();
            key.shift = /^[A-Z]$/.test(ch);
            key.meta = escaped;
        }
        ....... 
        
        stream.emit('keypress', escaped? undefined: s, key);
        
    }
}

输出显示进去调用的办法:
this.output.write(stringToWrite);
回显出输出的内容后,回进入 Generator 函数中再次被 yied 进来;
再次输出信息,还是会一样的进入循环;

如果输出回车:
再次进入 onData 办法中,

function onData(b) {
     ....
    stream[ESCAPE_DECODER].next(r[i]); // r[i] 就是输出的 "\r" 换行符
}

emitKeys 办法

funnction* emitKyes (stream) {while(true) {
        let ch = yied;
        let s = ch; // s = 哦
        let escaped = false;
        const key = {
            squence: null,
            name: undefined,
            ctrl: false,
            meta: false,
            shift: false
        };
        if(ch === kEscape) {
            escaped = true;
            s += (ch = yield);
        }else if(ch === '\r'){key.name = 'return';} else if (大小写数字和字母的判断) {key.name = ch.toLowerCase();
            key.shift = /^[A-Z]$/.test(ch);
            key.meta = escaped;
        }
        ....... 
        
        stream.emit('keypress', escaped? undefined: s, key);
        
    }
}

进入到 clearLine 中的,执行 this._wirteToOutput('\r\n')让光标去执行换行,新的 this.line 置空;

rl.close();
close 办法调用了 this.pause(); 这个办法执行,this.input,pause(); 将输出流敞开;

Interface.prototype.pause = function () {if(this.paused) return;
    this.input.pause();
    this.paused = true;
    this.emit('pause');
    return this;
};

this._setRawMode(false); 设置为 false;

程序就完结了。

退出移动版