当前位置:网站首页>基于inquirer封装一个控制台文件选择器
基于inquirer封装一个控制台文件选择器
2022-07-30 18:24:00 【JYeontu】
说在前面
我们在用脚手架初始化项目的时候,往往会进行一些命令交互,用过vue或者react的用脚手架新建项目的应该都进行过命令交互,vue创建的时候会让你选择vue2还是vue3,也有多选要什么配置,也有输入y或者n选择是否用history路由等,这些简单的交互其实用inquire这个包都能实现,但是最近自己在做一个小工具的时候,想要进行文件和文件夹的选择,这时我发现inquire里并没有这个交互功能,所以便自己尝试去在inquire这个库的基础上实现文件选择和文件夹选择这两种类型的交互。
插件效果
通过该插件,我们可以在控制台通过方向键来选择文件和文件夹,具体效果如下:
插件实现
Inquirer.js
Inquirer.js试图为NodeJs做一个可嵌入式的美观的命令行界面。如下图:
inquirer原有参数
- type
表示提问的类型,包括:input、confirm、 list、rawlist、expand、checkbox、password、editor。
- name
存储当前输入的值。
- message
问题的描述。
- default
默认值。
- choices
列表选项,在某些type下可用,并且包含一个分隔符(separator);
- validate
对用户的回答进行校验。
- filter
对用户的回答进行过滤处理,返回处理后的值。
- when
根据前面问题的回答,判断当前问题是否需要被回答。
- pageSize
修改某些type类型下的渲染行数。
- prefix
修改message默认前缀。
- suffix
修改message默认后缀。
二次封装
基于inquirer原有功能及参数,增加一些扩展功能及参数
新增参数
- notNull
是否不能为空,默认为false,设置为true后该参数不能输入空,并且会有不能为空的提示,必须输入字符后才可以回车确认并进行下一步,如下图:
{
type:"input",
message:"请输入你的姓名:",
name:"name",
notNull:true
}

- type
在原有类型中新增两种类型:file、folder,分别为文件选择器和目录选择器,效果如下图:
{
type:"file",
message:"请选择文件:",
name:"fileName",
default:"",
},
{
type:"folder",
message:"请选择文件夹:",
name:"folderName",
default:"",
pathType:'absolute'
},

- pathType
此项为新增配置,设置目录和文件选择器选中路径输出的格式,默认为相对路径,可以设置为absolute,此时会输出绝对路径,效果如下图:
{
type:"file",
message:"请选择文件:",
name:"fileName",
default:"",
},
{
type:"folder",
message:"请选择文件夹:",
name:"folderName",
default:"",
pathType:'absolute'
},

代码实现
获取指定路径下的文件列表
使用fs中的readdirSync方法可以获取指定目录下的文件列表,具体代码如下:
getFileList = (dirPath)=>{
const list = fs.readdirSync(dirPath);
return ['../(返回上一级)',...list];
}
获取指定路径下的目录列表
使用fs中的readdirSync方法可以获取指定目录下的文件列表,通过isDirectory方法可以判断文件是否为目录文件,具体代码如下:
getFolderList = (dirPath)=>{
const list = fs.readdirSync(dirPath);
let resList = [];
list.map(item=>{
const fullPath = path.join(dirPath,item);
if(fs.statSync(fullPath).isDirectory()){
resList.push(item + '(进入文件夹)');
resList.push(item + '(选择文件夹)');
}
});
return ['../(返回上一级)',...resList];
}
交互类型响应控制
新增的file和folder类型使用自己重新封装的方法,其他依旧使用Inquirer中的响应方法,具体代码如下:
run(option){
if(option.type === 'file'){
return this.chooseFile(option);
}else if(option.type === 'folder'){
return this.chooseFolder(option);
}else{
if(option.notNull){
const flag = option.message.slice(-1);
if([":",":"].includes(flag)){
option.message = option.message.slice(0,-1) + '(不能为空)' + flag;
}
}
return this.defaultType(option);
}
}
选择文件
- 选择的为
返回上一级,则将当前目录回退一级:
this.clear(2);
return this.chooseFile(option,path.join(dirPath,'/../'));
- 选择的是目录则进入选择的目录:
return path.join(dirPath, answer[option.name]);
- 选择的是文件则返回选择的文件路径并结束操作:
this.clear(2);
return this.chooseFile(option,fullPath);
- 完整代码如下
chooseFile(option,dirPath = './'){
option.type = 'list';
option.suffix = "(当前浏览目录:" + path.join(__dirname,dirPath) + ')';
option.pageSize = fs.readdirSync('./').length + 1;
option.choices = [...this.getFileList(dirPath)];
const answer = await inquirer.prompt([
option
]);
if(answer[option.name] == '../(返回上一级)'){
this.clear(2);
return this.chooseFile(option,path.join(dirPath,'/../'));
}else{
const fullPath = path.join(dirPath, answer[option.name]);
if(!fs.statSync(fullPath).isFile()){
this.clear(2);
return this.chooseFile(option,fullPath);
}else{
return path.join(dirPath, answer[option.name]);
}
}
}
选择目录

- 选择的为
返回上一级,则将当前目录回退一级:
this.clear(2);
return this.chooseFile(option,path.join(dirPath,'/../'));
- 选择的是进入文件夹,则进入该目录,这里需要将加入用于区分的后缀去掉再返回:
return path.join(dirPath, answer[option.name].slice(0,-7));
- 选择的是选择文件夹则返回选择的文件夹路径并结束操作:
this.clear(2);
return this.chooseFile(option,fullPath);
- 完整代码如下
chooseFile(option,dirPath = './'){
option.type = 'list';
option.suffix = "(当前浏览目录:" + path.join(__dirname,dirPath) + ')';
option.pageSize = fs.readdirSync('./').length + 1;
option.choices = [...this.getFileList(dirPath)];
const answer = await inquirer.prompt([
option
]);
if(answer[option.name] == '../(返回上一级)'){
this.clear(2);
return this.chooseFile(option,path.join(dirPath,'/../'));
}else{
const fullPath = path.join(dirPath, answer[option.name]);
if(!fs.statSync(fullPath).isFile()){
this.clear(2);
return this.chooseFile(option,fullPath);
}else{
return path.join(dirPath, answer[option.name]);
}
}
}
基本类型调用Inquirer处理
这里增加了notNull(是否不能为空)的参数,代码如下:
defaultType(option){
const answer = await inquirer.prompt([
option
]);
if(option.notNull && answer[option.name] === ''){
this.clear(2);
return this.defaultType(option);
}
return answer[option.name];
}
插件使用
1、安装依赖
npm install @jyeontu/j-inquirer
2、在代码中引用
const JInquirer = require('@jyeontu/j-inquirer');
3、示例代码
const JInquirer = require('@jyeontu/j-inquirer');
let options = [
{
type:"input",
message:"请输入你的姓名:",
name:"name",
notNull:true
},{
type:"input",
message:"请输入你的年龄:",
name:"age",
default:18,
validate:(val)=>{
if(val < 0 || val > 150){
return "请输入0~150之间的数字";
}
return true;
}
},{
type:"file",
message:"请选择文件:",
name:"fileName",
default:"",
},{
type:"folder",
message:"请选择文件夹:",
name:"folderName",
default:"",
pathType:'absolute'
},{
type:"list",
message:"请选择你喜欢的水果:",
name:"fruit",
default:"Apple",
choices:[
"Apple",
"pear",
"Banana"
],
},{
type:"expand",
message:"请选择一个颜色:",
name:"color",
default:"red",
choices:[
{
key : 'R',
value : "red"
},
{
key : 'B',
value : "blue"
},
{
key : 'G',
value : "green"
}
]
},{
type:"checkbox",
message:"选择一至多种颜色:",
name:"color2",
choices:[
"red",
"blue",
"green",
"pink",
"orange"
]
},{
type:"password",
message:"请输入你的密码:",
name:"pwd"
},{
type:"editor",
message:"写下你想写的东西:",
name:"editor"
}
];
let j = new JInquirer(options);
let res = j.prompt().then(res=>{
console.log(res);
});
源码地址
https://gitee.com/zheng_yongtao/node-scripting-tool/tree/master/src/JInquirer
觉得有帮助的同学可以帮忙给我点个star,感激不尽~~~
有什么想法或者改良可以给我提个pr,十分欢迎~~~
有什么问题都可以在评论告诉我~~~
往期精彩
说在后面
这里是JYeontu,喜欢算法,GDCPC打过卡;热爱羽毛球,大运会打过酱油。毕业一年,两年前端开发经验,目前担任H5前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球 ,也喜欢写些东西,既为自己记录,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解,写错的地方望指出,定会认真改进,在此谢谢大家的支持,我们下文再见。
边栏推荐
- LayaBox---TypeScript---变量声明
- 时序数据库在船舶风险管理领域的应用
- mysql的多实例
- LayaBox---TypeScript---泛型
- ROS 环境使用第三方动态链接库(.so)文件
- 【总结】1396- 60+个 VSCode 插件,打造好用的编辑器
- 沉浸式体验科大讯飞2022消博会“官方指定产品”
- 中集世联达飞瞳全球工业人工智能AI领军者,全球顶尖AI核心技术高泛化性高鲁棒性稀疏样本持续学习,工业级高性能成熟AI产品规模应用
- Mysql execution principle analysis
- Immersive experience iFLYTEK 2022 Consumer Expo "Official Designated Product"
猜你喜欢

Linux-安装MySQL(详细教程)

【Swords Offer】Swords Offer 17. Print n digits from 1 to the largest

Pytorch基础--tensorboard使用(一)

Meta元宇宙部门第二季度亏损28亿!仍要继续押注?元宇宙发展尚未看到出路!

ESP8266-Arduino编程实例-HC-SR04超声波传感器驱动

「Redis应用与深度实践笔记」,深得行业人的心,这还不来看看?

core sound driver详解

NC | 西湖大学陶亮组-TMPRSS2“助攻”病毒感染并介导索氏梭菌出血毒素的宿主入侵...

载誉而归,重磅发布!润和软件亮相2022开放原子全球开源峰会

(2022杭电多校四)1001-Link with Bracket Sequence II(区间动态规划)
随机推荐
EMC VPLEX VS2 SPS电池更换详细探讨
第十六期八股文巴拉巴拉说(MQ篇)
运营 23 年,昔日“国内第一大电商网站”黄了...
CCNA-子网划分(VLSM)
【HMS core】【FAQ】Account Kit、MDM能力、push Kit典型问题合集6
【剑指 Offe】剑指 Offer 17. 打印从1到最大的n位数
固定资产可视化智能管理系统
LayaBox---TypeScript---基础数据类型
【开发者必看】【push kit】推送服务典型问题合集3
LeetCode 练习——关于查找数组元素之和的两道题
【HarmonyOS】【FAQ】鸿蒙问题合集4
Meta元宇宙部门第二季度亏损28亿!仍要继续押注?元宇宙发展尚未看到出路!
cocos creater 热更重启导致崩溃
微博广告分布式配置中心的构建与实践(有彩蛋)
BI报表与数据开发
[OC study notes] attribute keyword
Recommendation | People who are kind to you, don't repay them by inviting them to eat
AI Basics: Graphical Transformer
载誉而归,重磅发布!润和软件亮相2022开放原子全球开源峰会
Deepen school-enterprise cooperation and build an "overpass" for the growth of technical and skilled talents