UNPKG

@irim/bin-tool

Version:

node bin tools

176 lines (175 loc) 6.8 kB
// 文件处理相关的方法 // @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; }