基于node实现一个简单的脚手架工具node控制台交互项目

  • 实现控制台输入输出
  • 实现文件读写操作
  • 全原生实现一个简单的脚手架工具
  • 实现vue-cli2源码

 一、实现控制台输入输出

关于控制台的输入输出依然是基于node进程管理对象process,在process上有三个基于流的对象分别是:标准输入流(stdin)、标准输出流(stdout)、标准错误(stderr)。

关于流这里不做太多解析,后面会专门针对node核心模块做详细的分析,这里简单的说说它们的应用。输入流可以理解为将控制台的输入内容读到一个内存中;输出流就是可以理解为将内存中的内容输出;错误流可以理解为特殊的输出流,负责将内存中描述的错误信息按照特殊标准的输出流输出。

比如拿输出流来说他就是将内存中的数据输出,我们经常使用的console.log就是基于这个输出流,它实现将要输出的数据缓存到一个内存中,然后再按照输出设置的字符编码方式将内存中的数据输出,如果不是设置编码方式就会按照内存中的<Buffer>原始结构输出数据。

同样输入流就是输出流的一个反向操作,他只负责将数据按照指定的进制<Buffer>输入到一个内存中。

上面大概的介绍了以下流的一些概念,接下来看看一个最简单的控制台输入输出示例然后对API做一些解析:

1 #!/usr/bin/env node
2 
3 process.stdout.write('\033[33m输入:\033[39');
4 process.stdin.resume(); //等待输入
5 process.stdin.setEncoding('utf-8');
6 process.stdin.on('data',function(data){
7     process.stdin.pause();
8     console.log('\033[90m输入的内容是:' + data + '\033[39m');
9 })

(如果不清楚如何在控制台启动node.exe执行这段代码可以参考这篇博客: 手动封装一个node命令集工具

接着在控制台测试这段代码的效果是这样:

 

首先stdout.write你可以认为他就是一个console.log,虽然这并不准确,但在这篇博客的内容中可以这么理解。可能你会关注到'\033[33m **** \033[39',它是用来设置输出的颜色,这不需要过多关注,如果有兴趣可以自行查找资料了解。

然后就是输入流stdin,这里使用了四个API,其作用分别是:resume负责等待输入;setEncoding负责设置输入数据的编码;on负责注册一个‘data’事件,回调函数传入内存中缓存的数据;pause负责停止等待输入。

这部分内存看示应用上好像非常简单,但实际上并非代码所表现出来的这样简单,关于流是node非常重要的IO内容,也是比较复杂的一部分内容,如果想彻底弄明白流可以多差一些资料和教程,我后期也会围绕这个重要的内容单独写一篇博客。

 二、实现文件的读写操作

关于文件读写操作也同样是一个复杂的内容,它的底层同样是基于流,而且还是node中唯一的一个同时提供同步和异步的模块,这篇博客也只是围绕实现一个简单的手脚架简单的介绍一些应用。

2.1.基于控制台输入信息指定打印文件内容,这里先将上一节内容与文件读取操作结合起来,了解文件操作的基本逻辑:

 1 #!/usr/bin/env node
 2 
 3 const fs = require('fs');   //导入fs模块
 4 const argv = process.argv;  //获取命令行参数
 5 const cwd = process.cwd();  //获取当前执行路径
 6 const dirname = __dirname;  //获取当前程序的全局绝对路径
 7 const templetPath = dirname + '\\templet';  //这里需要注意linux与windows的路径差异
 8 
 9 const stdin = process.stdin;  //获取当前进程上的输入流
10 const stdout = process.stdout;    //获取当前进程上的输出流
11 
12 //读取文件
13 fs.readdir(templetPath, function(err, files){
14     //读取模板文件目录
15     function file(i){
16         let filename = files[i];
17         fs.stat(templetPath + '\\' + filename, function(err, stat){
18             if(stat.isDirectory()){ //如果stat是目录
19                 console.log('   ' + i + '   \033[36m' + filename + '\\\033[39m');
20             }else{
21                 //这里将读取到文件标记上编号打印到控制台界面,为选择文件提供参考信息
22                 console.log('   ' + i + '   \033[90m' + filename + '\033[39m');
23             }
24             i++;
25             if(i === files.length){
26                read();//读取文件
27             }else{
28                 file(i);
29             }
30         });
31     }
32     //读取文件(写入编号读取文件)
33     function read(){
34         console.log(' ');
35         stdout.write('  \033[33m输入文件编号:\033[39m');
36         stdin.resume(); //等待输入
37         stdin.setEncoding('utf8');
38         stdin.on('data', write);    //基于输入流上的data事件调用文件读取方法
39     }
40     //基于控制台写入的文件编号,找到要操作的文件(这里先使用fs.readFile方法读一下文件)
41     function write(data){
42         //data是控制台输入的内容,这里即文件索引
43         let filename = files[Number(data)];
44         let filepath = templetPath + '\\' + filename;
45         if(!filename){
46             stdout.write('  \033[31m输入的编号'+ data.trim() +'不存在,请重新输入:\033[39m');
47         }else{
48             stdin.pause();
49             //打印拷贝的文件内容
50             fs.readFile(filepath, 'utf8', function(err, data){
51                 console.log('\033[90m' + data.replace(/(.*)/g, '    $1') + '\033[39m');//给文件添加缩进并打印
52             });
53         }
54     }
55     file(0);//读取文件目录————根据编号选择操作的文件————将选择的文件写入当前执行路径的page文件夹中
56 });

如果你需要测试这部分代码,别忘记在项目的根目录下添加一个templet文件,并在里面放几个有内容的文件。

解析一下在示例代码中实现文件读取相关的一些属性、方法、模块:

fs模块:nodejs内部提供的操作文件的基础模块;

process.argv:启动执行项目的命令参数,测试代码中的功能实现暂时用不到,可以尝试打印一下看看,如果你需要实现一个完整的CLI工具肯定有用;

process.cwd:当前执行的路径(工作目录),测试代码中暂时用不到,但后面的示例代码用得到;

__dirname:当前程序的全局绝对路径,这是nodejs入门第一节课必将的内容,也非常简单,如果有疑问可以查看官方文档了解;

fs.readdir:读取文件目录;

fs.stat:基于路径读取文件描述属性,这里用来判断读取的文件路径是文件夹还是文件;

fs.readFile:基于文件路径读取文件内容。

上面的代码展示了文件操作的基本过程和逻辑,但还有一些需要注意的是文件操作内部是非常复杂的,涉及的内容包含流、buffer、异步同步等问题,比如上面的示例代码就是基于异步实现的,这是因为nodejs的单线程异步特性才能发挥它的程序性能,所以不建议在非必要的情况下使用同步的方式操作文件。

2.2实现文件拷贝复制(write改一些代码,并增加在一个writeFile方法):

 1 //写入文件的方法
 2     function writeFile(path,data,fun){
 3         fs.writeFile(path, data, function(err){
 4              if(err){
 5                 console.log('   /033[90m' + err.toString() + '\033[39m');
 6              }
 7              fun();
 8         });
 9     }
10     //基于控制台写入的文件编号,找到要操作的文件(这里先使用fs.readFile方法读一下文件)
11     function write(data){
12         //data是控制台输入的内容,这里即文件索引
13         let filename = files[Number(data)];
14         let filepath = templetPath + '\\' + filename;
15         let filePagePath = pagePath + "\\" + filename;  //最终将文件内容写入到的目标路径
16 
17         if(!filename){
18             stdout.write('  \033[31m输入的编号'+ data.trim() +'不存在,请重新输入:\033[39m');
19         }else{
20             stdin.pause();
21             //打印拷贝的文件内容
22             fs.readFile(filepath, 'utf8', function(err, data){
23                 console.log('\033[90m' + data.replace(/(.*)/g, '    $1') + '\033[39m');//给文件添加缩进并打印
24                 fs.readdir(cwd, function(err, files){
25                     //判断工作区间是否存在安装文件的page文件夹
26                     if(!files.length || files.indexOf('page') === -1){
27                         //如果没有page文件夹,在工作区间创建一个page文件夹
28                         fs.mkdir(pagePath,(err)=>{
29                             if(err){
30                                 console.log('   /033[90m' + err.toString() + '\033[39m');
31                                 process.exit(1);    //退出当前进程
32                             }
33                             writeFile(filePagePath,data,writeFileFun);  //向工作区间安装文件
34                         });
35                     }else{
36                         writeFile(filePagePath,data,writeFileFun);      //向工作区间安装文件
37                     }
38                 });
39             });
40         }
41         //写入操作的回调函数
42         function writeFileFun(){
43             console.log('   \033[33m' + filename + '写入成功。\033[39m');
44         }
45     }
46     file(0);//读取文件目录————根据编号选择操作的文件————将选择的文件写入当前执行路径的page文件夹中
47 });

添加上面修改过的新代码到前面的代码中就可以实现一个简单的手脚架工具了,完整的代码在第三节。

关于添加的这部分代码做一些简单的分析:

这里重点在于需要判断工作区间是否由安装文件的文件夹“page”,如果没有需要在工作区间添加这个文件夹,实现这个功能的是fs模块上的mkdir方法,需要注意这已然是一个异步方法。然后就是使用了fs模块上的writeFile实现了文件的写入,即安装。还有一个细节就是在添加文件夹的时候如果报错我是用了process.exit(1)退出当前进程,这个逻辑就不需要过的说明了,仅仅说一下个代码的作用,如果还有不理解的话就查一下node官方文档吧。

还有就是关于templetPath以及templet文件夹这其实是一个非常简单的东西,就是在CLI全局下有一个templet用来存放模板文件,在这个文件里你可以存放一些简单的模板代码文件用于测试。

然后介绍了一些可能你会需要的内容:

进程和操作系统是基于信号的方式,如果需要让进程终止可以发送SIGKILL信号;

process.on('SIGKILL',function(){
    //信号以收到
})

ANSI转义:这个内容在前面有提到过,就是用来修改控制台打印内容的颜色的操作,这里拿“\033[90m xxxx \033[39m”来简单的说一下:‘\033’标识转义的开始;‘[’表示开始设置颜色;‘90’表示一种颜色(亮灰色);‘m’表示颜色设置结束;‘39’用来将颜色设置回去。

最后重复提示一下关于文件操作流很重要,在node中有一个Stream模块,虽然我的代码中没有使用fs.createReadStream这样基于流的API,其实fs.readFile底层依然是基于流,只是fs.readFile是将所有内容读出来,而fs.createReadStream可以分段的读取文件,这对于文件操作非常重要,你可以想一下如果操作的是一个非常大的文件会如何。同样写入文件也一样,而且我的示例代码中的fs.writeFile将整个文件写入,如果文件中已经存在同名的文件会直接覆盖,这当然不能覆盖有问题,但对于大文件分段写入显然是不合适的。

然后,谈到了分段写入肯定就要考虑在写入的过程中文件发生了改变了怎么办,这时候就需要对文件进行监视,fs.watchFile就可以实现监视整个目录。

肯定还有很多问题我没有讲到,也不是在一篇博客中能讲完,特别是在FS模块和Stream模块后期肯定会有解析的博客,如果又不理解的地方或问题可以在评论区留言。

 三、全原生实现一个简单的脚手架工具

 1 #!/usr/bin/env node
 2 
 3 const fs = require('fs');   //导入fs模块
 4 const argv = process.argv;  //获取命令行参数
 5 const cwd = process.cwd();  //获取当前执行路径
 6 const dirname = __dirname;  //获取当前程序的全局绝对路径
 7 const templetPath = dirname + '\\templet';  //这里需要注意linux与windows的路径差异
 8 
 9 const stdin = process.stdin;  //获取当前进程上的输入流
10 const stdout = process.stdout;    //获取当前进程上的输出流
11 const pagePath = cwd + "\\page";    //设置文件写入的路径(当前工作区间的静态文件目录下)
12 
13 //读取文件
14 fs.readdir(templetPath, function(err, files){
15     //读取模板文件目录
16     function file(i){
17         let filename = files[i];
18         fs.stat(templetPath + '\\' + filename, function(err, stat){
19             if(stat.isDirectory()){ //如果stat是目录
20                 console.log('   ' + i + '   \033[36m' + filename + '\\\033[39m');
21             }else{
22                 //这里将读取到文件标记上编号打印到控制台界面,为选择文件提供参考信息
23                 console.log('   ' + i + '   \033[90m' + filename + '\033[39m');
24             }
25             i++;
26             if(i === files.length){
27                read();//读取文件
28             }else{
29                 file(i);
30             }
31         });
32     }
33     //读取文件(写入编号读取文件)
34     function read(){
35         console.log(' ');
36         stdout.write('  \033[33m输入文件编号:\033[39m');
37         stdin.resume(); //等待输入
38         stdin.setEncoding('utf8');
39         stdin.on('data', write);    //基于输入流上的data事件调用文件读取方法
40     }
41     //写入文件的方法
42     function writeFile(path,data,fun){
43         fs.writeFile(path, data, function(err){
44              if(err){
45                 console.log('   /033[90m' + err.toString() + '\033[39m');
46              }
47              fun();
48         });
49     }
50     //基于控制台写入的文件编号,找到要操作的文件(这里先使用fs.readFile方法读一下文件)
51     function write(data){
52         //data是控制台输入的内容,这里即文件索引
53         let filename = files[Number(data)];
54         let filepath = templetPath + '\\' + filename;
55         let filePagePath = pagePath + "\\" + filename;  //最终将文件内容写入到的目标路径
56 
57         if(!filename){
58             stdout.write('  \033[31m输入的编号'+ data.trim() +'不存在,请重新输入:\033[39m');
59         }else{
60             stdin.pause();
61             //打印拷贝的文件内容
62             fs.readFile(filepath, 'utf8', function(err, data){
63                 console.log('\033[90m' + data.replace(/(.*)/g, '    $1') + '\033[39m');//给文件添加缩进并打印
64                 fs.readdir(cwd, function(err, files){
65                     //判断工作区间是否存在安装文件的page文件夹
66                     if(!files.length || files.indexOf('page') === -1){
67                         //如果没有page文件夹,在工作区间创建一个page文件夹
68                         fs.mkdir(pagePath,(err)=>{
69                             if(err){
70                                 console.log('   /033[90m' + err.toString() + '\033[39m');
71                                 process.exit(1);    //结束当前进程
72                             }
73                             writeFile(filePagePath,data,writeFileFun);  //向工作区间安装文件
74                         });
75                     }else{
76                         writeFile(filePagePath,data,writeFileFun);      //向工作区间安装文件
77                     }
78                 });
79             });
80         }
81         //写入操作的回调函数
82         function writeFileFun(){
83             console.log('   \033[33m' + filename + '写入成功。\033[39m');
84         }
85     }
86     file(0);//读取文件目录————根据编号选择操作的文件————将选择的文件写入当前执行路径的page文件夹中
87 });

 四、实现vue-cli2源码

这个源码我正在写,过几天写完上传到github上,到时候再来添加项目地址。

原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/15931793.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍“基于nodejs的ssh2怎么实现自动化部署”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“基于nodejs...
本文小编为大家详细介绍“nodejs怎么实现目录不存在自动创建”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs怎么实现目录不存在自动创建”文章能帮助大...
这篇“如何把nodejs数据传到前端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这...
本文小编为大家详细介绍“nodejs如何实现定时删除文件”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs如何实现定时删除文件”文章能帮助大家解决疑惑...
这篇文章主要讲解了“nodejs安装模块卡住不动怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来...
今天小编给大家分享一下如何检测nodejs有没有安装成功的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
本篇内容主要讲解“怎么安装Node.js的旧版本”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎...
这篇“node中的Express框架怎么安装使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要介绍“nodejs如何实现搜索引擎”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nodejs如何实现搜索引擎...
这篇文章主要介绍“nodejs中间层如何设置”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nodejs中间层如何设置”文...
这篇文章主要介绍“nodejs多线程怎么实现”,在日常操作中,相信很多人在nodejs多线程怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
这篇文章主要讲解了“nodejs怎么分布式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs怎么分布式”...
本篇内容介绍了“nodejs字符串怎么转换为数组”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情...
这篇文章主要介绍了nodejs如何运行在php服务器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇nodejs如何运行在php服务器文章都...
本篇内容主要讲解“nodejs单线程如何处理事件”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“nodejs单线程如何...
这篇文章主要介绍“nodejs怎么安装ws模块”,在日常操作中,相信很多人在nodejs怎么安装ws模块问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
本篇内容介绍了“怎么打包nodejs代码”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!
本文小编为大家详细介绍“nodejs接收到的汉字乱码怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs接收到的汉字乱码怎么解决”文章能帮助大家解...
这篇“nodejs怎么同步删除文件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇...
今天小编给大家分享一下nodejs怎么设置淘宝镜像的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希