const fs = require("fs"); const path = require("path"); class CntEvent { constructor() { this.cnt = 0; this.emptyEvent = []; this.encount = this.encount.bind(this); this.decount = this.decount.bind(this); this.add = this.add.bind(this); } encount(delta = 1) { this.cnt += delta; } decount() { if (this.cnt > 0) --this.cnt; if (this.cnt == 0) { for (let info of this.emptyEvent) info[0](...info[1]); this.emptyEvent = []; } } add(cb, ...attach) { this.emptyEvent.push([cb, attach]); } check(cb, ...attach) { if (this.cnt == 0) cb(...attach); else this.add(cb, ...attach); } } class LimitedRunner { constructor(limit) { this.limit = limit; this.cnt = 0; this.funcs = []; } run(func) { if (this.cnt < this.limit) { this.cnt++; setTimeout(func, 0); } else { this.funcs.push(func); } } done() { if (this.cnt > 0) this.cnt--; if (this.funcs.length > 0) { this.cnt++; setTimeout(this.funcs.shift(), 0); } } runWithCb(func, ...args) { let cb = args.pop(), self = this; function agent(...args) { self.done(); return cb.apply(this, args); } args.push(agent); this.run(() => func(...args)); } } let ioEvent = new CntEvent; let ioLimit = new LimitedRunner(4096); function mkdirs(dir, cb) { ioLimit.runWithCb(fs.stat.bind(fs), dir, (err, stats) => { if (err) mkdirs(path.dirname(dir), () => fs.mkdir(dir, cb)); else if (stats.isFile()) throw Error(dir + " was created as a file, so we cannot put file into it."); else cb(); }); } function save(name, content) { ioEvent.encount(); mkdirs(path.dirname(name), () => ioLimit.runWithCb(fs.writeFile.bind(fs), name, content, err => { if (err) throw Error("Save file error: " + err); ioEvent.decount(); })); } function get(name, cb, opt = {encoding: 'utf8'}) { ioEvent.encount(); ioLimit.runWithCb(fs.readFile.bind(fs), name, opt, (err, data) => { if (err) throw Error("Read file error: " + err); else cb(data); ioEvent.decount(); }); } function del(name) { ioEvent.encount(); ioLimit.runWithCb(fs.unlink.bind(fs), name, ioEvent.decount); } function changeExt(name, ext = "") { return name.slice(0, name.lastIndexOf(".")) + ext; } function scanDirByExt(dir, ext, cb) { let result = [], scanEvent = new CntEvent; function helper(dir) { scanEvent.encount(); ioLimit.runWithCb(fs.readdir.bind(fs), dir, (err, files) => { if (err) throw Error("Scan dir error: " + err); for (let file of files) { scanEvent.encount(); let name = path.resolve(dir, file); fs.stat(name, (err, stats) => { if (err) throw Error("Scan dir error: " + err); if (stats.isDirectory()) helper(name); else if (stats.isFile() && name.endsWith(ext)) result.push(name); scanEvent.decount(); }); } scanEvent.decount(); }); } scanEvent.add(cb, result); helper(dir, ext, scanEvent); } function toDir(to, from) {//get relative path without posix/win32 problem if (from[0] == ".") from = from.slice(1); if (to[0] == ".") to = to.slice(1); from = from.replace(/\\/g, '/'); to = to.replace(/\\/g, '/'); let a = Math.min(to.length, from.length); for (let i = 1, m = Math.min(to.length, from.length); i <= m; i++) if (!to.startsWith(from.slice(0, i))) { a = i - 1; break; } let pub = from.slice(0, a); let len = pub.lastIndexOf("/") + 1; let k = from.slice(len); let ret = ""; for (let i = 0; i < k.length; i++) if (k[i] == '/') ret += '../'; return ret + to.slice(len); } function commonDir(pathA, pathB) { if (pathA[0] == ".") pathA = pathA.slice(1); if (pathB[0] == ".") pathB = pathB.slice(1); pathA = pathA.replace(/\\/g, '/'); pathB = pathB.replace(/\\/g, '/'); let a = Math.min(pathA.length, pathB.length); for (let i = 1, m = Math.min(pathA.length, pathB.length); i <= m; i++) if (!pathA.startsWith(pathB.slice(0, i))) { a = i - 1; break; } let pub = pathB.slice(0, a); let len = pub.lastIndexOf("/") + 1; return pathA.slice(0, len); } function commandExecute(cb, helper) { console.time("Total use"); function endTime() { ioEvent.check(() => console.timeEnd("Total use")); } let orders = []; for (let order of process.argv) if (order.startsWith("-")) orders.push(order.slice(1)); let iter = process.argv[Symbol.iterator](), nxt = iter.next(), called = false, faster = orders.includes("f"), fastCnt; if (faster) { fastCnt = new CntEvent; fastCnt.add(endTime); } function doNext() { let nxt = iter.next(); while (!nxt.done && nxt.value.startsWith("-")) nxt = iter.next(); if (nxt.done) { if (!called) console.log("Command Line Helper:\n\n" + helper); else if (!faster) endTime(); } else { called = true; if (faster) fastCnt.encount(), cb(nxt.value, fastCnt.decount, orders), doNext(); else cb(nxt.value, doNext, orders); } } while (!nxt.done && !nxt.value.endsWith(".js")) nxt = iter.next(); doNext(); } module.exports = { mkdirs: mkdirs, get: get, save: save, toDir: toDir, del: del, addIO: ioEvent.add, changeExt: changeExt, CntEvent: CntEvent, scanDirByExt: scanDirByExt, commonDir: commonDir, commandExecute: commandExecute };