• 基于inquirer封装一个控制台文件选择器


    说在前面

    我们在用脚手架初始化项目的时候,往往会进行一些命令交互,用过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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    • type

    在原有类型中新增两种类型:file、folder,分别为文件选择器和目录选择器,效果如下图:

    {
        type:"file",
        message:"请选择文件:",
        name:"fileName",
        default:"",
    },
    {
        type:"folder",
        message:"请选择文件夹:",
        name:"folderName",
        default:"",
        pathType:'absolute'
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    • pathType

    此项为新增配置,设置目录和文件选择器选中路径输出的格式,默认为相对路径,可以设置为absolute,此时会输出绝对路径,效果如下图:

    {
        type:"file",
        message:"请选择文件:",
        name:"fileName",
        default:"",
    },
    {
        type:"folder",
        message:"请选择文件夹:",
        name:"folderName",
        default:"",
        pathType:'absolute'
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    代码实现

    获取指定路径下的文件列表

    使用fs中的readdirSync方法可以获取指定目录下的文件列表,具体代码如下:

    getFileList = (dirPath)=>{
        const list = fs.readdirSync(dirPath);
        return ['../(返回上一级)',...list];
    }
    
    • 1
    • 2
    • 3
    • 4
    获取指定路径下的目录列表

    使用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];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    交互类型响应控制

    新增的filefolder类型使用自己重新封装的方法,其他依旧使用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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    选择文件
    • 选择的为返回上一级,则将当前目录回退一级:
    this.clear(2);
    return this.chooseFile(option,path.join(dirPath,'/../'));
    
    • 1
    • 2
    • 选择的是目录则进入选择的目录:
    return path.join(dirPath, answer[option.name]);
    
    • 1
    • 选择的是文件则返回选择的文件路径并结束操作:
    this.clear(2);
    return this.chooseFile(option,fullPath);
    
    • 1
    • 2
    • 完整代码如下
    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]);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    选择目录

    在这里插入图片描述

    • 选择的为返回上一级,则将当前目录回退一级:
    this.clear(2);
    return this.chooseFile(option,path.join(dirPath,'/../'));
    
    • 1
    • 2
    • 选择的是进入文件夹,则进入该目录,这里需要将加入用于区分的后缀去掉再返回:
    return path.join(dirPath, answer[option.name].slice(0,-7));
    
    • 1
    • 选择的是选择文件夹则返回选择的文件夹路径并结束操作:
    this.clear(2);
    return this.chooseFile(option,fullPath);
    
    • 1
    • 2
    • 完整代码如下
    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]);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    基本类型调用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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    插件使用

    1、安装依赖

    npm install @jyeontu/j-inquirer
    
    • 1

    2、在代码中引用

    const JInquirer = require('@jyeontu/j-inquirer');
    
    • 1

    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);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    源码地址

    https://gitee.com/zheng_yongtao/node-scripting-tool/tree/master/src/JInquirer

    觉得有帮助的同学可以帮忙给我点个star,感激不尽~~~
    有什么想法或者改良可以给我提个pr,十分欢迎~~~
    有什么问题都可以在评论告诉我~~~

    往期精彩

    node封装一个控制台进度条插件

    vue实现一个鼠标滑动预览视频封面组件

    密码太多不知道怎么记录?不如自己写个密码箱小程序

    微信小程序实现一个手势图案锁组件

    vue封装一个图案手势锁组件

    vue封装一个弹幕组件

    为了学(mo)习(yu),我竟开发了这样一个插件

    程序员的浪漫之——情侣日常小程序

    vue简单实现词云图组件

    vue + echarts实现中国地图省份下钻联动

    使用学过的算法做个游戏很酷的好吗

    说在后面

    🎉这里是JYeontu,喜欢算法,GDCPC打过卡;热爱羽毛球,大运会打过酱油。毕业一年,两年前端开发经验,目前担任H5前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球🏸 ,也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。

  • 相关阅读:
    如何克服答辩恐惧:从心理准备到实际应对
    大数据毕业设计选题推荐-市天气预警实时监控平台-Hadoop-Spark-Hive
    某大学作业
    Vue.js+SpringBoot开发厦门旅游电子商务预订系统
    日语基础复习 Day 13
    leetcode 647. 回文子串、516. 最长回文子序列
    MyBatis-Puls一篇就够
    【SpringMVC】面向全球的用户,我们该怎么办
    Linux系统设置防火墙
    配置OSPFv3引入外部路由及路由过滤 华为实验
  • 原文地址:https://blog.csdn.net/Twinkle_sone/article/details/126064845