使用nodejs进行了简单的文件分卷

关键词:node fs readline generator 

(在这之前需要声明的是这篇博客的应用范围应该算是相当狭隘,写出来主要也就是给自己记录一下临时兴起写的一个小工具,仅从功能需求上来说我相信是不适用于大多数读者的,欢迎有兴趣看的朋友给我做一次review)

  最近沉迷漫画,收集了一堆野生资源,偶尔会遇到一些四格漫画,观看体验不是很好,因为每话篇幅比较短,就独立成了一个目录,譬如这样:

 

  然后我就下意识的想写个脚本把这些目录合并成一个目录,解决这个问题,需要操作本地文件,而这是用很多途径都可以实现的,由于入门编程的时候学的是py,我第一时间是想用py的os模块去实现功能,一想上一次用py是快三年前的事情了,很多东西忘得差不多,包括前段时间换了新电脑,也没装有py环境,所以还是选择了语法上更熟悉也不需要额外配环境的node。   在明确了功能是在不改变源目录中文件顺序的情况下将多个目录按文件名顺序合并到新目录之后,由于涉及到操作本地文件(复制文件、创建目录等),所以确定了核心功能需要通过fs模块来完成;如果是自己用的话,一些比如输入输出目录这种配置参数,大可直接写死在代码里,需要用的时候再改就是了,但为了提高灵活性吧,所以还需要做一个微型的CLI,通过用户在命令行的输入来决定配置,这需要用到readline模块来提示和获取用户在命令行中的输入(在了解readline之后发现这里会比py麻烦不少,py用一个input方法就可以获取用户在命令行里的输入了);为了实现单向的流程,以及在提高代码在注释之外的的易理解和可读性,使用了平时比较少用的generator来实现了一个状态机并串联各个独立操作。   一边看node文档一边写代码,在忙活小半天之后,出来一点成果:

const fs = require('fs');
const readline = require('readline');
/**
 * @description 获取questtion的返回
 * @param {String} question 用户提示
 * @param {Function} handler 验证用户输入
 * @returns {Promise} rl.question方法本身是通过回调来处理用户输入的,所以选择了返回promise来做阻塞,有序地抛出question并接收answer
 */
function getQuestionResult(question,handler){
    return new Promise((res)=>{
        // 创建一个可读流,用来读取在cmd中的输入
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
        });
        /**
         * 考虑到即使用户输入异常,question方法都会在监听到换行之后结束,所以把handleResult的结构设计成一个对象,
         * 由一个状态值success来表示是否通过handler的校验,success为false时应再次执行并且获取用户输入         */
        rl.question(question, async (ans)=>{
            const handleResult = handler(ans)
            if (handleResult.success){
                rl.close()
                res(handleResult.value)
            } else {
                //handleResult.success为false时,handleResult.value是handler中设置的错误提示
                rl.close()
                const rejecthandle = await getQuestionResult(handleResult.value,handler)
                res(rejecthandle)
            }
        })
    })
}
// 这里创建一个generator实例,我觉得generator的yield单向有序的特性很适合我这个需求
function*gen(){
    function getChangeSettingsFlag(ans){
        return ans==="Y"?{success:true,value:true}:{success:true,value:false}
    }
    const getPath = function (ans) {
        /**
* @description 验证路径(是否是目录)
* @param {String} path
*/ const validatePath = function (path) { try { // node10.x及以下版本不支持readdirSync,需要你需要这种写法,在运行之前需要切换node版本 const dir = fs.readdirSync(path)
if (dir) { return {success:true,value:path} } } catch(err){ return {success:false,value:"提供的地址不合理,请重新输入:"} } } return validatePath(ans) } // yield并不会返回值,这里声明的changeSettingFlag的值实际上是接收的next方法的参数 const changeSettingFlag = yield getChangeSettingsFlag const settings = { volumeSize: 10, dirName: "新建分卷" } // 改变预设 if (changeSettingFlag){ const volumeSize = yield function(volumeSize){ return Number(volumeSize)>0&&Number(volumeSize)!==Infinity?{success:true,value:volumeSize}:{success:false,value:"输入的数字不合理,请重新输入:"} } settings.volumeSize = Number(volumeSize)||settings.volumeSize const dirName = yield function(dirName){ return dirName.trim()?{success:true,value:dirName}:{success:false,value:"输入的目录名不合理,请重新输入:"} } settings.dirName = dirName.trim()||settings.dirName } // 接收路径 const pathInfo = {
input: "",
output: ""
} const inputPath = yield getPath pathInfo.input = inputPath; const outputPath = yield getPath pathInfo.output = outputPath; const conf = {
pathInfo,
settings
} console.log(
"conf",conf) yield conf } // run it async function workflow(generator){ const func0 = generator.next().value const changeSettingFlag = await getQuestionResult("当前预设置如下:\n\t输出的分卷名:“新建分卷”;\n\t容量:10话/卷;\n希望调整预设吗?(Y/n) ",func0) let getvolumeSize,getdirName if (changeSettingFlag){ const func1 = generator.next(changeSettingFlag).value getvolumeSize = await getQuestionResult("期望的卷容量(话/卷)是: ",func1) const func2 = generator.next(getvolumeSize).value getdirName = await getQuestionResult("期望的分卷名是:",func2) } const func3 = generator.next(getdirName).value const inputPath = await getQuestionResult("选择的源路径是:",func3) const func4 = generator.next(inputPath).value const outputPath = await getQuestionResult("期望的输出路径是:",func4) const conf = generator.next(outputPath).value // 当前计数,通过在文件名中添加count来保持排序 let currentCount = 0; // 当前分卷 let currentVolume = 0; /** * @param {String} path * @param {String} newFolderName */ async function letsdance(path,newFolderName="") { const childs = fs.opendirSync(path) let chunkNum = 0 let newFolderPath = newFolderName for await (const dirent of childs) { if (dirent.isDirectory()) { // 填充满一个目录之后创建一个新目录 if (currentVolume%conf.settings.volumeSize===0) { chunkNum+=1; newFolderPath = conf.pathInfo.output+"/"+conf.settings.dirName+"_"+chunkNum fs.mkdirSync(newFolderPath) // 如果你不希望命名后缀一直递增,也可以在新建目录之后把currentCount重新置为0 } currentVolume+=1 const nextLevelPath = path+"/"+dirent.name letsdance(nextLevelPath,newFolderPath) } else if (dirent.isFile()){ currentCount += 1 const extName = dirent.name.split(".").reverse()[0] const targetFilePath = path+"/"+dirent.name const newFileName = newFolderPath+"/"+currentCount+"."+extName fs.copyFileSync(targetFilePath,newFileName) } } } letsdance(conf.pathInfo.input) } const g = gen() workflow(g)

  运行这个脚本,如果我有两个目录,需要将他们之中的文件复制到一个新目录来实现合并的效果的话,在讲第一个目录的文件按原顺序命名为1-n之后。第二个目录的文件则会从n+1开始命名,效果如下:

给TA买糖
共{{data.count}}人
人已赞赏
经验教程

设计模式之建造者模式(BuilderPattern)

2021-3-14 16:37:00

经验教程

unittest系列(三)unittest用例如何执行

2021-3-14 16:48:00

⚠️
免责声明:根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。 本站为个人博客非盈利性站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途,网站会员捐赠是您喜欢本站而产生的赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求。本站部份代码及教程来源于互联网,仅供网友学习交流,若您喜欢本文可附上原文链接随意转载。
无意侵害您的权益,请发送邮件至 momeis6@qq.com 或点击右侧 私信:momeis 反馈,我们将尽快处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索