@irim/bin-tool
Version:
node bin tools
176 lines (175 loc) • 6.8 kB
JavaScript
// 文件处理相关的方法
// @author Pluto <huarse@gmail.com>
// @create 2020/01/09 20:16
import path from 'path';
import fs from 'fs-extra';
import chalk from 'chalk';
import ora from 'ora';
import { print, getProgressStr, templateRender } from './utils';
import { confirm } from './inquirer';
/**
* 遍历文件目录,执行 callback
* @param src 目录路径
* @param fileCallback 执行到文件时的回调,如果返回 false 或 async false,则遍历中止
* @param dirCallback 执行到目录时的回调
* @param exclude 忽略的文件
*/
export async function fileIterator(src, fileCallback, dirCallback, exclude) {
return await (async function _recur(currSrc) {
const isDirectory = fs.statSync(currSrc).isDirectory();
const pathAddon = isDirectory ? '/' : '';
if (exclude && exclude.test(`${currSrc}${pathAddon}`))
return;
if (!isDirectory) {
return await fileCallback(currSrc, path.relative(src, currSrc));
}
if (typeof dirCallback === 'function') {
await dirCallback(path.relative(src, currSrc));
}
const dirFiles = await fs.readdir(currSrc);
for (let i = 0, j = dirFiles.length; i < j; i++) {
const file = dirFiles[i];
const recurResult = await _recur(path.resolve(currSrc, file));
if (recurResult === false)
return false;
}
})(src);
}
/**
* 遍历文件目录,同步到目标目录,并对每一个文件执行 callback
* @param source 源文件目录
* @param target 目标目录
* @param callback
* @param exclude 忽略的文件规则
*/
export async function dirSyncIterator(source, target, callback, exclude) {
return fileIterator(source, async (filePath, fileRelativePath) => {
await callback(filePath, path.resolve(target, fileRelativePath));
}, async (dirRelativePath) => {
await fs.ensureDir(path.resolve(target, dirRelativePath));
}, exclude);
}
/**
* 统计目录中的文件数量
* @param src 目录路径
* @param exclude 忽略的文件
*/
export async function getFileCount(src, exclude) {
let count = 0;
await fileIterator(src, () => count++, null, exclude);
return count;
}
const defaultOptions = {
src: null,
dist: null,
exclude: /node_modules\/|build\/|\.DS_Store\/|\.idea\/|\.paiconfig|\.git\/|\.bak/,
readonlyFile: /\.(png|jpe?g|gif|svg|obj|mtl|geojson|gltf|mp4|min\.js|min\.css)$/,
fileNameTransfer: (name) => name.replace(/^__/, '.').replace(/^_/, ''),
};
/**
* 复制文件目录
*/
export async function copyDir(options) {
options = {
...defaultOptions, ...options,
};
if (!options.src || !options.dist) {
throw new Error('src or dist is empty!');
}
if (!fs.existsSync(options.src)) {
throw new Error(`${options.src} does not exist!`);
}
const totalCount = await getFileCount(options.src, options.exclude);
let tickCount = 0;
print('debug', `copy directory: ${options.src} ➡︎ ${options.dist}`);
const spinner = ora(getProgressStr(tickCount, totalCount, '文件复制中...')).start();
await dirSyncIterator(options.src, options.dist, async (sourceFile, targetFile) => {
const curDistPathList = targetFile.split(path.sep);
const newFilename = options.fileNameTransfer(curDistPathList.pop());
if (!newFilename) {
throw new Error(`${curDistPathList.join(path.sep)} File name conversion failed and returned empty`);
}
targetFile = path.resolve(curDistPathList.join(path.sep), newFilename);
if (options.readonlyFile.test(sourceFile)) {
await fs.copyFile(sourceFile, targetFile);
}
else if (options.replacer) {
let fileContent = await fs.readFile(sourceFile, 'utf8');
options.replacer.forEach(x => {
fileContent = fileContent.replace(x.holder, x.value);
});
await fs.writeFile(targetFile, fileContent);
}
else if (options.contentFormatter) {
let fileContent = await fs.readFile(sourceFile, 'utf8');
fileContent = await options.contentFormatter(fileContent, sourceFile);
await fs.writeFile(targetFile, fileContent);
}
else {
await fs.copyFile(sourceFile, targetFile);
}
spinner.text = getProgressStr(++tickCount, totalCount, '文件复制中...');
}, options.exclude);
spinner.succeed(chalk.green('文件复制完成!'));
}
const defaultClearOptions = {
src: null,
confirm: true,
confirmText: '请确认要清空的目录: {{src}}',
};
export async function clearDir(options) {
options = {
...defaultClearOptions,
...options,
};
if (!options.src)
return false;
if (options.confirm !== false) {
const confirmResult = await confirm(templateRender(options.confirmText, options), false);
if (!confirmResult)
return false;
}
const originFileList = await fs.readdir(options.src);
const fileList = originFileList.filter(name => {
return options.exclude ? !options.exclude.test(name) : true;
});
const totalCount = fileList.length;
print('debug', `clear directory: ${options.src}`);
const spinner = ora(getProgressStr(0, totalCount, '文件删除中...')).start();
for (let i = 0, j = fileList.length; i < j; i++) {
const filename = fileList[i];
const filepath = path.resolve(options.src, filename);
await fs.remove(filepath);
spinner.text = getProgressStr(i + 1, totalCount, '文件删除中...');
}
spinner.succeed(chalk.green('文件删除完成!'));
return true;
}
/**
* 从目录中查找内容符合条件的文件
* @param src 文件目录
* @param callback 文件校验规则,传入参数是文件的内容
* @param options
* @return
*/
export async function findInFolder(src, callback, options = {}) {
const result = [];
await fileIterator(src, (filePath, fileRelativePath) => {
if (options.include && !options.include.test(fileRelativePath))
return;
const fileStats = fs.statSync(filePath);
if (options.limit > 0) {
if (fileStats.size > options.limit)
return;
}
const fileContent = fs.readFileSync(filePath, options.encoding || 'utf-8');
const flag = callback(filePath, fileContent);
if (flag === true) {
result.push({ filePath, fileRelativePath, fileSize: fileStats.size });
// 如果不是找所有文件,则找到一个后立即返回结果
if (!options.all)
return false;
}
}, null, options.exclude);
return result;
}