读<了不起的Node.js>-06.命令行工具(CLI)以及FS API首个Node应用

简介

  • nodejs中重要的API:处理进程(stdio)的stdin以及stdout相关的API还有文件系统FS的相关api
  • 之前介绍过 node通过使用回调和事件机制来实现并发,现在这些api 会首次接触到基于非阻塞事件的io编程中的流控制
  • 搭建一个简单的命令行文件浏览器,功能是允许用户读取和创建文件

需求

-定义需求

  • 程序需要在命令行运行,意味着程序得通过node命令执行,或者直接执行,然后通过终端提供交互给用户进行输入和输出

  • 启动后显示当前目录下列表

  • 选择文件时,显示文件的内容

  • 选择目录时,显示目录下信息

  • 运行结束后退出

  • 根据需求我们需要做到以下几点

    • 创建模块
    • 决定采用同步的fs 还是异步的fs
    • 理解什么是流 stream
    • 实现输入输出
    • 重构
    • 使用fs进行文件交互
    • 完成

编写首个 node程序

创建模块

  • 先创建文件夹 file-xeplorer
  • 在内部创建 package.json

/这里版本号遵循semver 的版本控制标准/

{
  "name": "file-explorer",
  
  "version": "0.0.1",
  "description": "一个命令行文件资源管理器",
  "dependencies": {}
}
  • 通过命令行输入 npm install 来验证package.json是否有效
  • 接着创建index.js

同步还是异步

  • 我们从生命依赖关系开始,由于stdio Api 是一个全局的process对象的一部分,所以我们这里为一个依赖就是fs
/*
* 模块依赖
* */
const fs = require('fs');
//同步版本
 console.log(fs.readdirSync('.'));

[图片上传失败...(image-3124cc-1533372113516)]

  • index.js
/*
* 模块依赖
* */
const fs = require('fs');

fs.readdir(__dirname, (err, files) =>{
    console.log(files);
});

什么是流 stream

console.log('hello world');
console.log('-----------------');//换行
process.stdout.write('hello world');
process.stdout.write('------------');//不换行
  • process全局对象包含了三个流对象

    • stdin : 标准输入
    • stdout :标准输出
    • stderr : 标准错误
      [图片上传失败...(image-dca6c7-1533372113516)]
  • 第一个stdin 是一个可读流,而stdout和stderr都是可写流

  • stdin流默认的状态是暂停的,通常,执行一个程序,程序会做一些处理,然后退出, 在这里程序需要纸质处在运行状态来接收用户输入的数据

  • 当恢复那个流的时候,node会观察对应的文件描述(unix状态下为0),随后宝石时间循环的运行,同时保持程序不退出,等待事件的触发,除非有io等待,否则nodejs总是会自动退出

输入和输出

  • 这里我们写出第一部分,列出当前目标路下的问文件 然后等待用户输入
/*
* 模块依赖
* */
const fs = require('fs');

fs.readdir(process.cwd(), function (err, files) {
    console.log('');

    if (!files.length) {
        return console.log('   \033[31m 没有文件显示 !\033[39m\n');
    }
    
    console.log('    选择您想要查看的文件或目录\n');

    function file(i) {
        let filename = files[i];

        fs.stat(__dirname + '/' + filename, function (err, stat) {
            if (stat.isDirectory()) {
                console.log('    ' + i + ' \033[36m' + filename + '/\033[39m');
            } else {
                console.log('    ' + i + ' \033[36m' + filename + '\033[39m');
            }
            i++;
            if (i == files.length) {
                console.log('');
                process.stdout.write('    \033[33m输入你的选择: \033[39m');
                process.stdin.resume();
                process.stdin.serEncoding('utf8');
            } else {
                file(i)
            }
        });
    }
    file(0);
});

  • 下面我们来分析上面的代码
  • 为了输出更加友好我们首先输出一个空行:console.log('')
  • 如果files数组为空,告知用户当前目录没有文件 文本周围的\033[31m\033[39m为了让文本呈现为红色,例子中得\n也是为了输出友好console.log(' \033[31m 没有文件显示 !\033[39m\n')
  • 在输出查看语句后
  • 定义了一个函数,数组中每个元素都会执行这个额函数,这里也出现贯穿始终的异步流程控制模式,出阿航执行,
  • function file(i){....}
  • 然后,现货区文件名,在查看文件名对应的路径情况,fs.stat会个提出文件或者目录的元数据let filename = files[i];fs.stat(__dirname + '/' + filename, function (err, stat)..
  • 回调函数中,同时还给出了错误对象和一个stat对象,背离中使用到的stat对象上的方法是isDirectory
  • 如果路径所代表的是目录,我们就用有别于文件的颜色表示出来
  • 下面就是流控制中得核心部分了
    -计数器不断递增,同时检查是否还有为处理的文件
  • 如果所有文件都处理完了 此时提示用户进行选择,注意
  • 这里用的是process.stdout.weite而不是cl,这样就无需换行可以直接在提示语后面输入
  • 这里process.stdin.resume()就是等待用户输入
  • 后面是设置流编码为utf8
  • 如果还有未处理的文件则用递归调用函数进行处理
  • 直到列出所有文件,用户输入完毕,紧接着进行下一步串行处理
  • 重要模式: 串行处理

重构

  • 要做重构,我们从创建快捷变量开始
  • [图片上传失败...(image-6e8c61-1533372113516)]
  • 由于我们书写的代码是异步,会有问题,随着韩数量的增长,过多的函数嵌套会让程序的可读性变差
  • 为此我们可以为每一个异步操作预先定一个一个函数
  • 抽取函数
  • [图片上传失败...(image-85c6-1533372113516)]
  • [图片上传失败...(image-582698-1533372113516)]
  • 读取用户输入后,接下来要做的就是根据用户输入做出相应处理,用户需要选择要读取的文件,所以代码层,我们设置了stdin的编码后,开始监听器data时间:
function read() {
        console.log('');
        stdout.write('    \033[33m输入你的选择: \033[39m');
        stdin.resume();
        stdin.setEncoding('utf8');
        stdin.on('data', option);
    }

    function option(data) {
        if (!files[Number(data)]) {
            stdout.write('    \031[33m输入你的选择: \033[39m')
        } else {
            stdin.pause();
        }
    }
  • 这里我们检查用户的输入是否匹配files数组的下表,files数组是fs.readdir回调函数中的一部分,上述代码中,我们将utf-8编码字符串转换为了Number类型来方便检查
  • 现在我们已经能定位文件了那就开始读取他
    function option(data) {
        const filename = files[Number(data)];
        if (!filename) {
            stdout.write('    \033[33m输入你的选择: \033[39m')
        } else {
            stdin.pause();
            fs.readFile(__dirname + '/' + filename, 'utf8', function (err, data) {
                console.log('');
                console.log('\033[90m'+data.replace(/(.*)/g, '    $1')+'\033[39m');
            });
        }
    }

  • 这样我们已经可以读取一个文件了
  • 但是读取文件夹的时候就会出错,在这种情况下,我们就将其目录下的文件列表显示出来,
  • 为了避免再次执行fs.stat 我们在file函数中,将stat对象保存下来

至此我们就完成了首个查看文件和文件夹内容的程序,虽然很简陋

/*
* 模块依赖
* */
const fs = require('fs'), stdin = process.stdin, stdout = process.stdout;


fs.readdir(process.cwd(), function (err, files) {
    console.log('');

    if (!files.length) {
        return console.log('   \033[31m 没有文件显示 !\033[39m\n');
    }

    console.log('    选择您想要查看的文件或目录\n');
    let stats = [];

    function file(i) {
        let filename = files[i];

        fs.stat(__dirname + '/' + filename, function (err, stat) {
            stats[i] = stat;
            if (stat.isDirectory()) {
                console.log('    ' + i + ' \033[36m' + filename + '/\033[39m');
            } else {
                console.log('    ' + i + ' \033[36m' + filename + '\033[39m');
            }

            if (++i === files.length) {
                read();
            } else {
                file(i)
            }
        });
    }

    function read() {
        console.log('');
        stdout.write('    \033[33m输入你的选择: \033[39m');
        stdin.resume();
        // stdin.setEncoding('utf8');
        stdin.on('data', option);
    }

    function option(data) {
        const filename = files[Number(data)];
        if (!filename) {
            stdout.write('    \033[33m输入你的选择: \033[39m')
        } else {
            stdin.pause();
            if (stats[Number(data)].isDirectory()) {
                fs.readdir(__dirname + '/' + filename, function (err, files) {
                    console.log('');
                    console.log('   (' + files.length + '  files)');
                    files.forEach(function (file) {
                        console.log('    -  ' + file);
                    });
                    console.log('');
                });
            } else {
                fs.readFile(__dirname + '/' + filename, 'utf8', function (err, data) {
                    console.log('');
                    console.log('\033[90m' + data.replace(/(.*)/g, '    $1') + '\033[39m');
                });
            }
        }
    }

    file(0);
});


    


    



你可能感兴趣的:(读<了不起的Node.js>-06.命令行工具(CLI)以及FS API首个Node应用)