UNPKG

majo

Version:

A minimal module to manipulate files.

213 lines (206 loc) 6.59 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var path = _interopDefault(require('path')); var fs = _interopDefault(require('fs')); var util = require('util'); var glob = _interopDefault(require('fast-glob')); var rimraf = _interopDefault(require('rimraf')); var ensureDir = _interopDefault(require('mkdirp')); class Wares { constructor() { this.middlewares = []; } use(middleware) { this.middlewares = this.middlewares.concat(middleware); return this; } run(context) { return this.middlewares.reduce((current, next) => { return current.then(() => Promise.resolve(next(context))); }, Promise.resolve()); } } const readFile = util.promisify(fs.readFile); const writeFile = util.promisify(fs.writeFile); const remove = util.promisify(rimraf); class Majo { constructor() { this.middlewares = []; this.meta = {}; this.files = {}; } /** * Find files from specific directory * @param source Glob patterns * @param opts * @param opts.baseDir The base directory to find files * @param opts.dotFiles Including dot files */ source(patterns, options = {}) { const { baseDir = '.', dotFiles = true, onWrite } = options; this.baseDir = path.resolve(baseDir); this.sourcePatterns = Array.isArray(patterns) ? patterns : [patterns]; this.dotFiles = dotFiles; this.onWrite = onWrite; return this; } /** * Use a middleware */ use(middleware) { this.middlewares.push(middleware); return this; } /** * Process middlewares against files */ async process() { if (!this.sourcePatterns || !this.baseDir) { throw new Error(`[majo] You need to call .source first`); } const allEntries = await glob(this.sourcePatterns, { cwd: this.baseDir, dot: this.dotFiles, stats: true }); await Promise.all(allEntries.map(entry => { const absolutePath = path.resolve(this.baseDir, entry.path); return readFile(absolutePath).then(contents => { const file = { contents, stats: entry.stats, path: absolutePath }; // Use relative path as key this.files[entry.path] = file; }); })); await new Wares().use(this.middlewares).run(this); return this; } /** * Filter files * @param fn Filter handler */ filter(fn) { return this.use(context => { for (const relativePath in context.files) { if (!fn(relativePath, context.files[relativePath])) { delete context.files[relativePath]; } } }); } /** * Transform file at given path * @param relativePath Relative path * @param fn Transform handler */ async transform(relativePath, fn) { const contents = this.files[relativePath].contents.toString(); const newContents = await fn(contents); this.files[relativePath].contents = Buffer.from(newContents); } /** * Run middlewares and write processed files to disk * @param dest Target directory * @param opts * @param opts.baseDir Base directory to resolve target directory * @param opts.clean Clean directory before writing */ async dest(dest, options = {}) { const { baseDir = '.', clean = false } = options; const destPath = path.resolve(baseDir, dest); await this.process(); if (clean) { await remove(destPath); } await Promise.all(Object.keys(this.files).map(filename => { const { contents } = this.files[filename]; const target = path.join(destPath, filename); if (this.onWrite) { this.onWrite(filename, target); } return ensureDir(path.dirname(target)).then(() => writeFile(target, contents)); })); return this; } /** * Get file contents as a UTF-8 string * @param relativePath Relative path */ fileContents(relativePath) { return this.file(relativePath).contents.toString(); } /** * Write contents to specific file * @param relativePath Relative path * @param string File content as a UTF-8 string */ writeContents(relativePath, contents) { this.files[relativePath].contents = Buffer.from(contents); return this; } /** * Get the fs.Stats object of specified file * @para relativePath Relative path */ fileStats(relativePath) { return this.file(relativePath).stats; } /** * Get a file by relativePath path * @param relativePath Relative path */ file(relativePath) { return this.files[relativePath]; } /** * Delete a file * @param relativePath Relative path */ deleteFile(relativePath) { delete this.files[relativePath]; return this; } /** * Create a new file * @param relativePath Relative path * @param file */ createFile(relativePath, file) { this.files[relativePath] = file; return this; } /** * Get an array of sorted file paths */ get fileList() { return Object.keys(this.files).sort(); } rename(fromPath, toPath) { if (!this.baseDir) { return this; } const file = this.files[fromPath]; this.createFile(toPath, { path: path.resolve(this.baseDir, toPath), stats: file.stats, contents: file.contents }); this.deleteFile(fromPath); return this; } } const majo = () => new Majo(); /** * Ensure directory exists before writing file */ const outputFile = (filepath, data, options) => ensureDir(path.dirname(filepath)).then(() => writeFile(filepath, data, options)); exports.glob = glob; exports.ensureDir = ensureDir; exports.Majo = Majo; exports.majo = majo; exports.outputFile = outputFile; exports.remove = remove;