关键词: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开始命名,效果如下:

使用nodejs进行了简单的文件分卷工具的更多相关文章

  1. java简单的文件读写工具类

    import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedRead ...

  2. 用nodejs搭建一个简单的服务器

    使用nodejs搭建一个简单的服务器 nodejs优点:性能高(读写文件) 数据操作能力强 官网:www.nodejs.org 验证是否安装成功:cmd命令行中输入node -v 如果显示版本号表示安 ...

  3. 拿nodejs快速搭建简单Oauth认证和restful API server攻略

    拿nodejs快速搭建简单Oauth认证和restful API server攻略:http://blog.csdn.net/zhaoweitco/article/details/21708955 最 ...

  4. 用nodejs搭建一个简单的服务监听程序

    作为一个从业三年左右的,并且从事过半年左右PHP开发工作的前端,对于后台,尤其是对以js语言进行开发的nodejs,那是比较有兴趣的,虽然本身并没有接触过相关的工作,只是自己私下做的一下小实验,但是还 ...

  5. 利用 nodeJS 搭建一个简单的Web服务器(转)

    下面的代码演示如何利用 nodeJS 搭建一个简单的Web服务器: 1. 文件 WebServer.js: //-------------------------------------------- ...

  6. nodejs创建一个简单的web服务

    这是一个突如其来的想法,毕竟做web服务的框架那么多,为什么要选择nodejs,因为玩前端时,偶尔想调用接口获取数据,而不想关注业务逻辑,只是想获取数据,使用java或者.net每次修改更新后还要打包 ...

  7. 使用jsp/servlet简单实现文件上传与下载

    使用JSP/Servlet简单实现文件上传与下载    通过学习黑马jsp教学视频,我学会了使用jsp与servlet简单地实现web的文件的上传与下载,首先感谢黑马.好了,下面来简单了解如何通过使用 ...

  8. Mac OS环境下媒体文件分割工具mediafilesegmenter的简单使用(生成M3U8 TS文件)

    mediafilesegmenter是苹果开发的一款用于分割媒体文件的工具,其功能与mediastreamsegmenter相似,但操作更简单. * 具体可以对比博客中的另一篇简介<Mac OS ...

  9. Spring简单的文件配置

    Spring简单的文件配置 “计应134(实验班) 凌豪” 一.Spring文件配置 spring至关重要的一环就是装配,即配置文件的编写,接下来我按刚才实际过程中一步步简单讲解. 首先,要在web. ...

随机推荐

  1. LINUX - 最简单的CS通信实例

    服务端[编译:gcc server.c -o server] #include <stdio.h> #include <sys/socket.h> #include <s ...

  2. 实现基于股票收盘价的时间序列的统计(用Python实现)

    时间序列是按时间顺序的一组真实的数字,比如股票的交易数据.通过分析时间序列,能挖掘出这组序列背后包含的规律,从而有效地预测未来的数据.在这部分里,将讲述基于时间序列的常用统计方法. 1 用rollin ...

  3. universities

  4. C++ part8

    1.volatile关键字 在C++中,对volatile修饰的对象的访问,有编译器优化上的副作用: 不允许被编译器优化,提供特殊地址的稳定访问(只从内存中读取). 有序性,编译器进行优化时,不能把对 ...

  5. HDU 6704 K-th occurrence(主席树 + RMQ + 后缀数组)题解

    题意: 给一个串\(S\),\(length\leq 1e5\),\(Q\leq1e5\)个询问,每次询问输出和\(S_lS_{l+1}\dots S_r\)长得一模一样的第\(k\)个子串的开头位置 ...

  6. HDU 6155 Subsequence Count(矩阵 + DP + 线段树)题解

    题意:01串,操作1:把l r区间的0变1,1变0:操作2:求出l r区间的子序列种数 思路:设DP[i][j]为到i为止以j结尾的种数,假设j为0,那么dp[i][0] = dp[i - 1][1] ...

  7. Java中的Lambda匿名函数后续

    函数式编程(函数式接口):一个接口只包含一个方法实现 public interface Lambda{ void method(); } // 调用 Lambda lambda = new Lambd ...

  8. Emacs和Vim:神的编辑器和编辑器之神, 到底哪个更好?

    Emacs和Vim:神的编辑器和编辑器之神, 到底哪个更好? 在这个蔚蓝色的星球上,流传着两大神器的传说:据说Emacs是神的编辑器,而Vim是编辑器之神. 一些人勇敢地拾起了Vim或Emacs,却发 ...

  9. React Hooks vs React Class vs React Function All In One

    React Hooks vs React Class vs React Function All In One React Component Types React Hooks Component ...

  10. DMCA Takedown Policy

    DMCA Takedown Policy https://github.com/xgqfrms/xgqfrms/issues/46 https://help.github.com/en/github/ ...