From a63abe7a9cfb8e3afeab309e1869ee93d2c8b80f Mon Sep 17 00:00:00 2001 From: Sergei Chipiga Date: Sat, 24 Aug 2019 08:17:41 +0300 Subject: [PATCH 1/2] Move small functions to separated module --- index.js | 200 -------------------------- lib/small.js | 229 ++++++++++++++++++++++++++++++ package.json | 2 +- tests.js | 285 +------------------------------------ tests/unit/testSmall.js | 302 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 533 insertions(+), 485 deletions(-) diff --git a/index.js b/index.js index d5aeaa7..cd74681 100644 --- a/index.js +++ b/index.js @@ -39,29 +39,6 @@ var clearEmptyFolders = module.exports.clearEmptyFolders = folder => { fs.rmdirSync(folder); } }; -/** - * Makes delay (sleep) during code execution. - * - * @function - * @arg {number} timeout - Time to sleep, ms. - * @arg {boolean} [blocking=false] - Flag whether sleep should be - * block code execution. - * @return {Promise} If sleep isn't blocking. - * @return {undefined} If sleep is blocking. - */ -module.exports.sleep = (timeout, blocking) => { - blocking = !!blocking; - if (blocking) { - (ms => { - ms += new Date().getTime(); - while (new Date() < ms) { /* nothing */ } - })(timeout); - } else { - return new Promise(resolve => { - setTimeout(resolve, timeout); - }); - } -}; /** * Composes file path from segments. If folder of file is absent, it will * be created. @@ -296,141 +273,6 @@ module.exports.help = d => { .help("h") .alias("h", "help"); }; -/** - * Defines whether object is located on screen or no. - * - * @function - * @arg {object} obj - Object which may be on screen. - * @arg {object} screen - Screen object. - * @arg {object} [opts] - Options. - * @arg {boolean} [opts.fully=false] - Flag to check full presence on screen. - * @return {boolean} `true` if it is on screen, `false` otherwise. - */ -module.exports.isInScreen = (obj, screen, opts) => { - opts = I.coalesce(opts, {}); - var fully = I.coalesce(opts.fully, false); - - if (fully) { - return ((obj.x >= screen.x) && - (obj.y >= screen.y) && - (obj.x + obj.width <= screen.x + screen.width) && - (obj.y + obj.height <= screen.y + screen.height)); - } else { - return !((obj.x >= screen.x + screen.width) || - (obj.y >= screen.y + screen.height) || - (obj.x + obj.width <= screen.x) || - (obj.y + obj.height <= screen.y)); - } -}; -/** - * Gets object position on screen. - * - * @function - * @arg {object} obj - Object which should be on screen. - * @arg {object} screen - Screen object. - * @return {object} Object position on screen. - * @throws {Error} If object isn't located on screen. - */ -module.exports.objOnScreenPos = (obj, screen) => { - - if (!I.isInScreen(obj, screen)) { - throw new Error( - `Object { x: ${obj.x}, y: ${obj.y}, width: ${obj.width}, ` + - `height: ${obj.height} } isn't on screen { x: ${screen.x}, ` + - `y: ${screen.y}, width: ${screen.width}, height: ${screen.height} }`); - } - - var res = _.clone(obj); - - if (res.x < screen.x) res.x = screen.x; - if (res.y < screen.y) res.y = screen.y; - - if (res.x + res.width > screen.x + screen.width) { - res.width = screen.x + screen.width - res.x; - } - - if (res.y + res.height > screen.y + screen.height) { - res.height = screen.y + screen.height - res.y; - } - - return res; -}; -/** - * Transforms string to kebab case. Replace all symbols, except numbers, - * chars and dots with dashes. - * - * @function - * @arg {string} str - String to transform. - * @return {string} Transformed string. - */ -module.exports.toKebab = str => { - return str - .trim() - .toLowerCase() - .replace(/[^A-Za-z0-9_.]+/g, "-") - .replace(/-\./g, ".") - .replace(/-_/g, "_") - .replace(/-$/g, "") - .replace(/^-/g, ""); -}; -/** - * Waits for predicate returns truly value. - * - * @async - * @function - * @arg {function} predicate - Function which should return truly value during - * timeout. - * @arg {object} [opts] - Options. - * @arg {number} [opts.timeout=1] - Time to wait for predicate result, sec. - * @arg {number} [opts.polling=0.1] - Time to poll predicate result, sec. - * @return {Promise} `false` if predicate didn't return truly value - * during expected time. - * @return {Promise} Predicate truly value. - */ -module.exports.waitFor = async (predicate, opts) => { - opts = I.coalesce(opts, {}); - var timeout = I.coalesce(opts.timeout, 1) * 1000; - var polling = I.coalesce(opts.polling, 0.1) * 1000; - var limit = new Date().getTime() + timeout; - - while(limit > new Date().getTime()) { - var result = await predicate(); - if (result) return result; - await I.sleep(polling); - } - - return false; -}; - -/** - * Waits during a time that predicate returns truly value. - * - * @async - * @function - * @arg {function} predicate - Function which should return truly value during - * timeout. - * @arg {object} [opts] - Options. - * @arg {number} [opts.timeout=1] - Time to wait predicate result, sec. - * @arg {number} [opts.polling=0.1] - Time to poll predicate result, sec. - * @return {Promise} `false` if predicate didn't return truly value - * during expected time. - * @return {Promise} Predicate truly value. - */ -module.exports.waitDuring = async (predicate, opts) => { - - opts = I.coalesce(opts, {}); - var timeout = I.coalesce(opts.timeout, 1) * 1000; - var polling = I.coalesce(opts.polling, 0.1) * 1000; - var limit = new Date().getTime() + timeout; - - while(limit > new Date().getTime()) { - var result = await predicate(); - if (!result) return false; - await I.sleep(polling); - } - - return result; -}; var complete = line => { line = colors.strip(line); @@ -595,48 +437,6 @@ module.exports.debug = async function (helpMessage) { global[k] = v; } }; -/** - * Activates docstring support for js functions. - * - * @function - */ -module.exports.docString = () => { - if (Object.prototype.hasOwnProperty.call(Function.prototype, "__doc__")) return; - require("docstring"); - - Function.prototype.bond = function (ctx) { - var result = this.bind(ctx); - Object.defineProperty(result, "__doc__", { - value: this.__doc__, - writable: false, - }); - return result; - }; -}; - -/** - * `Glace` fixtures factory. - * - * Provides easy way to make a fixture with hooks related with shared context. - * - * @function - * @arg {object} [opts] - Options. - * @arg {function} [opts.before] - Callback of `before` hook. - * @arg {function} [opts.after] - Callback of `after` hook. - * @arg {function} [opts.beforeChunk] - Callback of `beforeChunk` hook. - * @arg {function} [opts.afterChunk] - Callback of `afterChunk` hook. - * @return {function} - Fixture. - */ -module.exports.makeFixture = (opts = {}) => { - return func => { - const ctx = {}; - if (opts.before) before(opts.before(ctx)); - if (opts.beforeChunk) beforeChunk(opts.beforeChunk(ctx)); - func(); - if (opts.afterChunk) afterChunk(opts.afterChunk(ctx)); - if (opts.after) after(opts.after(ctx)); - }; -}; Object.assign(exports, require("./lib/small")); diff --git a/lib/small.js b/lib/small.js index 3f7ee11..d9b6dba 100644 --- a/lib/small.js +++ b/lib/small.js @@ -179,6 +179,227 @@ const missedWords = (text, words, firstMissedOnly = false) => { return missed; }; +/** + * Transforms string to kebab case. It replaces all symbols, except numbers, + * chars and dots with dashes. + * + * @memberOf module:glace-utils + * @function + * @arg {string} str - String to transform. + * @return {string} Transformed string. + * + * @example + * U.toKebab("hello_world"); // "hello-world" + */ +const toKebab = str => { + return str + .trim() + .toLowerCase() + .replace(/[^A-Za-z0-9_.]+/g, "-") + .replace(/-\./g, ".") + .replace(/-_/g, "_") + .replace(/-$/g, "") + .replace(/^-/g, ""); +}; + +/** + * Makes delay (sleep) during code execution. + * + * @memberOf module:glace-utils + * @async + * @function + * @arg {number} timeout - Time to sleep, ms. + * @arg {boolean} [blocking=false] - Flag whether sleep should be + * block code execution. + * @return {Promise} If sleep isn't blocking. + * @return {undefined} If sleep is blocking. + * + * @example + * await U.sleep(1000); // async + * U.sleep(1000, true); // sync + */ +const sleep = (timeout, blocking = false) => { + if (blocking) { + (ms => { + ms += new Date().getTime(); + while (new Date() < ms) { /* nothing */ } + })(timeout); + } else { + return new Promise(resolve => { + setTimeout(resolve, timeout); + }); + } +}; + +/** + * Waits for predicate returns truly value. + * + * @memberOf module:glace-utils + * @async + * @function + * @arg {function} predicate - Function which should return truly value during + * timeout. + * @arg {object} [opts] - Options. + * @arg {number} [opts.timeout=1] - Time to wait for predicate result, sec. + * @arg {number} [opts.polling=0.1] - Time to poll predicate result, sec. + * @return {Promise} Predicate truly value or `false` if + * predicate didn't return truly value during expected time. + * + * @example + * await U.waitFor(() => 1, { timeout: 2 }); // 1 + */ +const waitFor = async (predicate, opts) => { + opts = coalesce(opts, {}); + const timeout = coalesce(opts.timeout, 1) * 1000; + const polling = coalesce(opts.polling, 0.1) * 1000; + const limit = new Date().getTime() + timeout; + + while(limit > new Date().getTime()) { + const result = await predicate(); + if (result) return result; + await sleep(polling); + } + + return false; +}; + +/** + * Waits during a time that predicate returns truly value. + * + * @memberOf module:glace-utils + * @async + * @function + * @arg {function} predicate - Function which should return truly value during + * timeout. + * @arg {object} [opts] - Options. + * @arg {number} [opts.timeout=1] - Time to wait predicate result, sec. + * @arg {number} [opts.polling=0.1] - Time to poll predicate result, sec. + * @return {Promise} Predicate truly value or `false` if + * predicate didn't return truly value during expected time. + * + * @example + * await U.waitDuring(() => 5, { timeout: 0.5 }); // 5 + */ +const waitDuring = async (predicate, opts) => { + + opts = coalesce(opts, {}); + const timeout = coalesce(opts.timeout, 1) * 1000; + const polling = coalesce(opts.polling, 0.1) * 1000; + const limit = new Date().getTime() + timeout; + + let result = null; + while(limit > new Date().getTime()) { + result = await predicate(); + if (!result) return false; + await sleep(polling); + } + + return result; +}; + +/** + * `glacejs` fixtures factory. Provides easy way to make a fixture with hooks + * related with shared context. + * + * @memberOf module:glace-utils + * @function + * @arg {object} [opts] - Options. + * @arg {function} [opts.before] - Callback of `before` hook. + * @arg {function} [opts.after] - Callback of `after` hook. + * @arg {function} [opts.beforeChunk] - Callback of `beforeChunk` hook. + * @arg {function} [opts.afterChunk] - Callback of `afterChunk` hook. + * @return {function} - Fixture. + */ +const makeFixture = (opts = {}) => { + return func => { + const ctx = {}; + if (opts.before) before(opts.before(ctx)); + if (opts.beforeChunk) beforeChunk(opts.beforeChunk(ctx)); + func(); + if (opts.afterChunk) afterChunk(opts.afterChunk(ctx)); + if (opts.after) after(opts.after(ctx)); + }; +}; + +/** + * Activates docstring support for js functions. + * + * @memberOf module:glace-utils + * @function + */ +const docString = () => { + if (Object.prototype.hasOwnProperty.call(Function.prototype, "__doc__")) return; + require("docstring"); + + Function.prototype.bond = function (ctx) { + const result = this.bind(ctx); + Object.defineProperty(result, "__doc__", { + value: this.__doc__, + writable: false, + }); + return result; + }; +}; + +/** + * Defines whether object is located on screen or no. + * + * @memberOf module:glace-utils + * @function + * @arg {object} obj - Object which may be on screen. + * @arg {object} screen - Screen object. + * @arg {boolean} [fully=false] - Flag to check full presence on screen. + * @return {boolean} `true` if it is on screen, `false` otherwise. + */ +const isInScreen = (obj, screen, fully = false) => { + if (fully) { + return ((obj.x >= screen.x) && + (obj.y >= screen.y) && + (obj.x + obj.width <= screen.x + screen.width) && + (obj.y + obj.height <= screen.y + screen.height)); + } else { + return !((obj.x >= screen.x + screen.width) || + (obj.y >= screen.y + screen.height) || + (obj.x + obj.width <= screen.x) || + (obj.y + obj.height <= screen.y)); + } +}; + +/** + * Gets object position on screen. + * + * @memberOf module:glace-utils + * @function + * @arg {object} obj - Object which should be on screen. + * @arg {object} screen - Screen object. + * @return {object} Object position on screen. + * @throws {Error} If object isn't located on screen. + */ +const objOnScreenPos = (obj, screen) => { + + if (!isInScreen(obj, screen)) { + throw new Error( + `Object { x: ${obj.x}, y: ${obj.y}, width: ${obj.width}, ` + + `height: ${obj.height} } isn't on screen { x: ${screen.x}, ` + + `y: ${screen.y}, width: ${screen.width}, height: ${screen.height} }`); + } + + const res = _.clone(obj); + + if (res.x < screen.x) res.x = screen.x; + if (res.y < screen.y) res.y = screen.y; + + if (res.x + res.width > screen.x + screen.width) { + res.width = screen.x + screen.width - res.x; + } + + if (res.y + res.height > screen.y + screen.height) { + res.height = screen.y + screen.height - res.y; + } + + return res; +}; + module.exports = { GlaceError, hostname, @@ -188,4 +409,12 @@ module.exports = { splitBy, textContains, missedWords, + toKebab, + sleep, + waitFor, + waitDuring, + makeFixture, + docString, + isInScreen, + objOnScreenPos, }; diff --git a/package.json b/package.json index 91fa2cc..f489dbc 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test": "nyc glace tests", "test:cover": "nyc --reporter=lcov --report-dir ./docs/tests-cover glace tests --allure", "allure:report": "npm run test:cover && allure generate report/allure --report-dir ./docs/allure-report", - "test:commit": "glace tests.js" + "test:commit": "glace tests" }, "nyc": { "exclude": [ diff --git a/tests.js b/tests.js index 7362d46..a76e370 100644 --- a/tests.js +++ b/tests.js @@ -1,7 +1,6 @@ "use strict"; var fs = require("fs"); -var format = require("util").format; var _ = require("lodash"); var temp = require("temp").track(); @@ -124,176 +123,7 @@ suite("Utils", () => { expect(() => U.loadJson(jPath2)).to.throw("Circular reference"); }); }); - - test(".isInScene()", () => { - - scope("partially", () => { - [ - [{x: 0, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, true], - [{x: -1, y: -1, width: 3, height: 3}, {x: 0, y: 0, width: 1, height: 1}, true], - [{x: 1, y: 1, width: 2, height: 2}, {x: 0, y: 0, width: 3, height: 3}, true], - [{x: 1, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], - [{x: 0, y: 1, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], - [{x: -1, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], - [{x: 1, y: -1, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], - ].forEach(([obj, screen, result]) => { - chunk(format(obj, "in", screen, "is", result), () => { - expect(U.isInScreen(obj, screen)).to.be.equal(result); - }); - }); - }); - - scope("fully", () => { - [ - [{x: 0, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, true], - [{x: 1, y: 1, width: 2, height: 2}, {x: 0, y: 0, width: 3, height: 3}, true], - [{x: -1, y: -1, width: 3, height: 3}, {x: 0, y: 0, width: 1, height: 1}, false], - ].forEach(([obj, screen, result]) => { - chunk(format(obj, "in", screen, "is", result), () => { - expect(U.isInScreen(obj, screen, { fully: true })).to.be.equal(result); - }); - }); - }); - }); - - test(".toKebab()", () => { - [ - ["", ""], - ["a", "a"], - [" ", ""], - [" a ", "a"], - [" !#@$!@#$ a @#@#$ ", "a"], - ["a @#$@#$ a", "a-a"], - ["a #.a", "a.a"], - ["a #_a", "a_a"], - ["a@^", "a"], - ["$@a", "a"], - ["a1", "a1"], - ["1a", "1a"], - ].forEach(([str, res]) => { - chunk(`'${str}' -> '${res}'`, () => { - expect(U.toKebab(str)).to.be.equal(res); - }); - }); - }); - - test(".objOnScreenPos()", () => { - - chunk("throws error if object isn't on screen", () => { - expect(() => U.objOnScreenPos( - { x: 5, y: 0, width: 1, height: 1 }, - { x: 0, y: 0, width: 1, height: 1 } - )).to.throw("isn't on screen"); - }); - - chunk("returns the same if object is fully on screen", () => { - expect(U.objOnScreenPos( - { x: 1, y: 2, width: 3, height: 4 }, - { x: 0, y: 0, width: 10, height: 10 } - )).to.include({ x: 1, y: 2, width: 3, height: 4 }); - }); - - chunk("returns restricted part if object oversizes screen", () => { - expect(U.objOnScreenPos( - { x: 0, y: 0, width: 10, height: 10 }, - { x: 1, y: 2, width: 3, height: 4 } - )).to.include({ x: 1, y: 2, width: 3, height: 4 }); - }); - }); - - test(".waitFor()", () => { - var now; - - beforeChunk(() => { - now = new Date().getTime(); - }); - - chunk("works with default options", async () => { - expect(await U.waitFor(() => true)).to.be.true; - expect(new Date().getTime() - now).to.below(1000); - }); - - chunk("returns predicate result if success", async () => { - var i = 0; - var predicate = () => { - if (i === 5) return 5; - i++; - }; - - expect(await U.waitFor(predicate, { timeout: 2 })).to.be.equal(5); - expect(new Date().getTime() - now).to.be.gte(500).and.below(2000); - }); - - chunk("returns false if didn't wait for timeout", async () => { - expect(await U.waitFor(() => false, { timeout: 2 })).to.be.false; - expect(new Date().getTime() - now).to.be.gte(2000); - }); - - chunk("throws the same error as predicate", async () => { - var predicate = () => { - throw new Error("BOOM!"); - }; - await expect(U.waitFor(predicate)).to.be.rejectedWith("BOOM!"); - }); - }); - - test(".docString()", () => { - var x, y; - - beforeChunk(() => { - U.docString(); - x = function () { - /** docstring */ - return 1; - }; - y = function () {}; - }); - - chunk("creates property __doc__", () => { - expect(x.__doc__).to.be.equal(" docstring "); - expect(y.__doc__).to.be.equal(""); - }); - - chunk("creates function bond", () => { - var z = x.bond({}); - expect(z.name).to.be.equal("bound x"); - expect(z.__doc__).to.be.equal(" docstring "); - z = y.bond({}); - expect(z.name).to.be.equal("bound y"); - expect(z.__doc__).to.be.equal(""); - }); - }); - - test(".waitDuring()", () => { - var now; - - beforeChunk(() => { - now = new Date().getTime(); - }); - - chunk("works with default options", async () => { - expect(await U.waitDuring(() => true)).to.be.true; - expect(new Date().getTime() - now).to.be.gte(1000); - }); - - chunk("returns predicate result if success", async () => { - expect(await U.waitDuring(() => 5, { timeout: 0.5 })).to.be.equal(5); - expect(new Date().getTime() - now).to.be.gte(500); - }); - - chunk("returns false if didn't wait for timeout", async () => { - expect(await U.waitDuring(() => false)).to.be.false; - expect(new Date().getTime() - now).to.be.below(1000); - }); - - chunk("throws the same error as predicate", async () => { - var predicate = () => { - throw new Error("BOOM!"); - }; - await expect(U.waitDuring(predicate)).to.be.rejectedWith("BOOM!"); - }); - }); - + test(".textContains()", () => { chunk("returns false if no text", () => { @@ -338,117 +168,4 @@ suite("Utils", () => { expect(U.splitBy("", ",")).to.be.empty; }); }); - - test(".makeFixture()", () => { - let fixture, before_, after_, beforeChunk_, afterChunk_; - - beforeChunk(() => { - before_ = sinon.stub(); - after_ = sinon.stub(); - beforeChunk_ = sinon.stub(); - afterChunk_ = sinon.stub(); - - U.__set__("before", before_); - U.__set__("after", after_); - U.__set__("beforeChunk", beforeChunk_); - U.__set__("afterChunk", afterChunk_); - }); - - chunk("without hooks", () => { - fixture = U.makeFixture(); - const cb = sinon.stub(); - fixture(cb); - expect(cb).to.be.calledOnce; - expect(before_).to.not.be.called; - expect(after_).to.not.be.called; - expect(beforeChunk_).to.not.be.called; - expect(afterChunk_).to.not.be.called; - }); - - chunk("with all hooks", () => { - const beforeCb = sinon.stub(); - const afterCb = sinon.stub(); - const beforeChunkCb = sinon.stub(); - const afterChunkCb = sinon.stub(); - - fixture = U.makeFixture({ - before: beforeCb, - after: afterCb, - beforeChunk: beforeChunkCb, - afterChunk: afterChunkCb, - }); - - const cb = sinon.stub(); - fixture(cb); - expect(cb).to.be.calledOnce; - - expect(before_).to.be.calledOnce; - expect(after_).to.be.calledOnce; - expect(beforeChunk_).to.be.calledOnce; - expect(afterChunk_).to.be.calledOnce; - - expect(beforeCb).to.be.calledOnce; - expect(afterCb).to.be.calledOnce; - expect(beforeChunkCb).to.be.calledOnce; - expect(afterChunkCb).to.be.calledOnce; - - expect(beforeCb.args[0][0]).to.be.eql({}); - expect(afterCb.args[0][0]).to.be.eql({}); - expect(beforeChunkCb.args[0][0]).to.be.eql({}); - expect(afterChunkCb.args[0][0]).to.be.eql({}); - - expect(beforeChunk_).to.be.calledAfter(before_); - expect(cb).to.be.calledAfter(beforeChunk_); - expect(afterChunk_).to.be.calledAfter(cb); - expect(after_).to.be.calledAfter(afterChunk_); - }); - - chunk("with 'before' hook", () => { - fixture = U.makeFixture({ before: () => {} }); - const cb = sinon.stub(); - fixture(cb); - expect(cb).to.be.calledOnce; - - expect(before_).to.be.calledOnce; - expect(after_).to.not.be.called; - expect(beforeChunk_).to.not.be.called; - expect(afterChunk_).to.not.be.called; - }); - - chunk("with 'after' hook", () => { - fixture = U.makeFixture({ after: () => {} }); - const cb = sinon.stub(); - fixture(cb); - expect(cb).to.be.calledOnce; - - expect(before_).to.not.be.called; - expect(after_).to.be.calledOnce; - expect(beforeChunk_).to.not.be.called; - expect(afterChunk_).to.not.be.called; - }); - - chunk("with 'beforeChunk' hook", () => { - fixture = U.makeFixture({ beforeChunk: () => {} }); - const cb = sinon.stub(); - fixture(cb); - expect(cb).to.be.calledOnce; - - expect(before_).to.not.be.called; - expect(after_).to.not.be.called; - expect(beforeChunk_).to.be.calledOnce; - expect(afterChunk_).to.not.be.called; - }); - - chunk("with 'afterChunk' hook", () => { - fixture = U.makeFixture({ afterChunk: () => {} }); - const cb = sinon.stub(); - fixture(cb); - expect(cb).to.be.calledOnce; - - expect(before_).to.not.be.called; - expect(after_).to.not.be.called; - expect(beforeChunk_).to.not.be.called; - expect(afterChunk_).to.be.calledOnce; - }); - }); }); diff --git a/tests/unit/testSmall.js b/tests/unit/testSmall.js index 0d3e00e..1def9db 100644 --- a/tests/unit/testSmall.js +++ b/tests/unit/testSmall.js @@ -1,5 +1,7 @@ "use strict"; +const format = require("util").format; + const modulePath = "../../lib/small"; const small = rehire(modulePath, { os: { @@ -113,4 +115,304 @@ suite("small", () => { .to.be.eql(["helo"]); }); }); + + test("toKebab", () => { + [ + ["", ""], + ["a", "a"], + [" ", ""], + [" a ", "a"], + [" !#@$!@#$ a @#@#$ ", "a"], + ["a @#$@#$ a", "a-a"], + ["a #.a", "a.a"], + ["a #_a", "a_a"], + ["a@^", "a"], + ["$@a", "a"], + ["a1", "a1"], + ["1a", "1a"], + ].forEach(([str, res]) => { + chunk(`'${str}' -> '${res}'`, () => { + expect(small.toKebab(str)).to.be.equal(res); + }); + }); + }); + + test("sleep", () => { + let now; + + beforeChunk(() => { + now = new Date().getTime(); + }); + + chunk("asynchronous", async () => { + await small.sleep(100); + expect(new Date().getTime() - now).to.be.gte(100); + }); + + chunk("synchronous", () => { + small.sleep(100, true); + expect(new Date().getTime() - now).to.be.gte(100); + }); + }); + + test("waitFor", () => { + let now; + + beforeChunk(() => { + now = new Date().getTime(); + }); + + chunk("works with default options", async () => { + expect(await small.waitFor(() => true)).to.be.true; + expect(new Date().getTime() - now).to.below(1000); + }); + + chunk("returns predicate result if success", async () => { + let i = 0; + const predicate = () => { + if (i === 5) return 5; + i++; + }; + + expect(await small.waitFor(predicate, { timeout: 2 })).to.be.equal(5); + expect(new Date().getTime() - now).to.be.gte(500).and.below(2000); + }); + + chunk("returns false if didn't wait for timeout", async () => { + expect(await small.waitFor(() => false, { timeout: 2 })).to.be.false; + expect(new Date().getTime() - now).to.be.gte(2000); + }); + + chunk("throws the same error as predicate", async () => { + const predicate = () => { + throw new Error("BOOM!"); + }; + await expect(small.waitFor(predicate)).to.be.rejectedWith("BOOM!"); + }); + }); + + test("waitDuring", () => { + let now; + + beforeChunk(() => { + now = new Date().getTime(); + }); + + chunk("works with default options", async () => { + expect(await small.waitDuring(() => true)).to.be.true; + expect(new Date().getTime() - now).to.be.gte(1000); + }); + + chunk("returns predicate result if success", async () => { + expect(await small.waitDuring(() => 5, { timeout: 0.5 })).to.be.equal(5); + expect(new Date().getTime() - now).to.be.gte(500); + }); + + chunk("returns false if didn't wait for timeout", async () => { + expect(await small.waitDuring(() => false)).to.be.false; + expect(new Date().getTime() - now).to.be.below(1000); + }); + + chunk("throws the same error as predicate", async () => { + const predicate = () => { + throw new Error("BOOM!"); + }; + await expect(small.waitDuring(predicate)).to.be.rejectedWith("BOOM!"); + }); + }); + + test("makeFixture", () => { + let fixture, before_, after_, beforeChunk_, afterChunk_; + + beforeChunk(() => { + before_ = sinon.stub(); + after_ = sinon.stub(); + beforeChunk_ = sinon.stub(); + afterChunk_ = sinon.stub(); + + small.__set__("before", before_); + small.__set__("after", after_); + small.__set__("beforeChunk", beforeChunk_); + small.__set__("afterChunk", afterChunk_); + }); + + chunk("without hooks", () => { + fixture = small.makeFixture(); + const cb = sinon.stub(); + fixture(cb); + expect(cb).to.be.calledOnce; + expect(before_).to.not.be.called; + expect(after_).to.not.be.called; + expect(beforeChunk_).to.not.be.called; + expect(afterChunk_).to.not.be.called; + }); + + chunk("with all hooks", () => { + const beforeCb = sinon.stub(); + const afterCb = sinon.stub(); + const beforeChunkCb = sinon.stub(); + const afterChunkCb = sinon.stub(); + + fixture = small.makeFixture({ + before: beforeCb, + after: afterCb, + beforeChunk: beforeChunkCb, + afterChunk: afterChunkCb, + }); + + const cb = sinon.stub(); + fixture(cb); + expect(cb).to.be.calledOnce; + + expect(before_).to.be.calledOnce; + expect(after_).to.be.calledOnce; + expect(beforeChunk_).to.be.calledOnce; + expect(afterChunk_).to.be.calledOnce; + + expect(beforeCb).to.be.calledOnce; + expect(afterCb).to.be.calledOnce; + expect(beforeChunkCb).to.be.calledOnce; + expect(afterChunkCb).to.be.calledOnce; + + expect(beforeCb.args[0][0]).to.be.eql({}); + expect(afterCb.args[0][0]).to.be.eql({}); + expect(beforeChunkCb.args[0][0]).to.be.eql({}); + expect(afterChunkCb.args[0][0]).to.be.eql({}); + + expect(beforeChunk_).to.be.calledAfter(before_); + expect(cb).to.be.calledAfter(beforeChunk_); + expect(afterChunk_).to.be.calledAfter(cb); + expect(after_).to.be.calledAfter(afterChunk_); + }); + + chunk("with 'before' hook", () => { + fixture = small.makeFixture({ before: () => {} }); + const cb = sinon.stub(); + fixture(cb); + expect(cb).to.be.calledOnce; + + expect(before_).to.be.calledOnce; + expect(after_).to.not.be.called; + expect(beforeChunk_).to.not.be.called; + expect(afterChunk_).to.not.be.called; + }); + + chunk("with 'after' hook", () => { + fixture = small.makeFixture({ after: () => {} }); + const cb = sinon.stub(); + fixture(cb); + expect(cb).to.be.calledOnce; + + expect(before_).to.not.be.called; + expect(after_).to.be.calledOnce; + expect(beforeChunk_).to.not.be.called; + expect(afterChunk_).to.not.be.called; + }); + + chunk("with 'beforeChunk' hook", () => { + fixture = small.makeFixture({ beforeChunk: () => {} }); + const cb = sinon.stub(); + fixture(cb); + expect(cb).to.be.calledOnce; + + expect(before_).to.not.be.called; + expect(after_).to.not.be.called; + expect(beforeChunk_).to.be.calledOnce; + expect(afterChunk_).to.not.be.called; + }); + + chunk("with 'afterChunk' hook", () => { + fixture = small.makeFixture({ afterChunk: () => {} }); + const cb = sinon.stub(); + fixture(cb); + expect(cb).to.be.calledOnce; + + expect(before_).to.not.be.called; + expect(after_).to.not.be.called; + expect(beforeChunk_).to.not.be.called; + expect(afterChunk_).to.be.calledOnce; + }); + }); + + test("isInScene", () => { + + scope("partially", () => { + [ + [{x: 0, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, true], + [{x: -1, y: -1, width: 3, height: 3}, {x: 0, y: 0, width: 1, height: 1}, true], + [{x: 1, y: 1, width: 2, height: 2}, {x: 0, y: 0, width: 3, height: 3}, true], + [{x: 1, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], + [{x: 0, y: 1, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], + [{x: -1, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], + [{x: 1, y: -1, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, false], + ].forEach(([obj, screen, result]) => { + chunk(format(obj, "in", screen, "is", result), () => { + expect(small.isInScreen(obj, screen)).to.be.equal(result); + }); + }); + }); + + scope("fully", () => { + [ + [{x: 0, y: 0, width: 1, height: 1}, {x: 0, y: 0, width: 1, height: 1}, true], + [{x: 1, y: 1, width: 2, height: 2}, {x: 0, y: 0, width: 3, height: 3}, true], + [{x: -1, y: -1, width: 3, height: 3}, {x: 0, y: 0, width: 1, height: 1}, false], + ].forEach(([obj, screen, result]) => { + chunk(format(obj, "in", screen, "is", result), () => { + expect(small.isInScreen(obj, screen, true)).to.be.equal(result); + }); + }); + }); + }); + + test("objOnScreenPos", () => { + + chunk("throws error if object isn't on screen", () => { + expect(() => small.objOnScreenPos( + { x: 5, y: 0, width: 1, height: 1 }, + { x: 0, y: 0, width: 1, height: 1 } + )).to.throw("isn't on screen"); + }); + + chunk("returns the same if object is fully on screen", () => { + expect(small.objOnScreenPos( + { x: 1, y: 2, width: 3, height: 4 }, + { x: 0, y: 0, width: 10, height: 10 } + )).to.include({ x: 1, y: 2, width: 3, height: 4 }); + }); + + chunk("returns restricted part if object oversizes screen", () => { + expect(small.objOnScreenPos( + { x: 0, y: 0, width: 10, height: 10 }, + { x: 1, y: 2, width: 3, height: 4 } + )).to.include({ x: 1, y: 2, width: 3, height: 4 }); + }); + }); + + test("docString", () => { + let x, y; + + beforeChunk(() => { + small.docString(); + x = function () { + /** docstring */ + return 1; + }; + y = function () {}; + }); + + chunk("creates property __doc__", () => { + expect(x.__doc__).to.be.equal(" docstring "); + expect(y.__doc__).to.be.equal(""); + }); + + chunk("creates function bond", () => { + let z = x.bond({}); + expect(z.name).to.be.equal("bound x"); + expect(z.__doc__).to.be.equal(" docstring "); + z = y.bond({}); + expect(z.name).to.be.equal("bound y"); + expect(z.__doc__).to.be.equal(""); + }); + }); }); From cb8d7f5cebc48077734ee70efe31ee72b028aab7 Mon Sep 17 00:00:00 2001 From: Sergei Chipiga Date: Mon, 9 Mar 2020 21:39:08 +0200 Subject: [PATCH 2/2] commit --- index.js | 446 ---------------------------------------- lib/debug.js | 165 +++++++++++++++ lib/help.js | 40 ++++ lib/index.js | 26 +++ lib/small.js | 240 +++++++++++++++++++++ tests/unit/testSmall.js | 1 + 6 files changed, 472 insertions(+), 446 deletions(-) delete mode 100644 index.js create mode 100644 lib/debug.js create mode 100644 lib/help.js create mode 100644 lib/index.js diff --git a/index.js b/index.js deleted file mode 100644 index cd74681..0000000 --- a/index.js +++ /dev/null @@ -1,446 +0,0 @@ -/** - * `GlaceJS` utils. - * - * @module glace-utils - */ - -var fs = require("fs"); -var path = require("path"); -var readline = require("readline"); -var util = require("util"); - -var colors = require("colors"); -var espree = require("espree"); -var highlight = require("cli-highlight").highlight; -var _ = require("lodash"); -var yargs = require("yargs").help(" "); // disable default `--help` capture -module.exports.__findProcess = require("find-process"); -var fse = require("fs-extra"); - -/** - * Clears empty folders recursive. - * - * @function - * @arg {string} folder - Path to root folder. - */ -var clearEmptyFolders = module.exports.clearEmptyFolders = folder => { - var files = fs.readdirSync(folder); - - for (var fileName of files) { - var filePath = path.join(folder, fileName); - if (fs.statSync(filePath).isDirectory()) { - clearEmptyFolders(filePath); - } - } - if (!_.isEmpty(files)) { - files = fs.readdirSync(folder); - } - if (_.isEmpty(files)) { - fs.rmdirSync(folder); - } -}; -/** - * Composes file path from segments. If folder of file is absent, it will - * be created. - * - * @function - * @arg {...string} paths - A sequence of paths or path segments. - * @return {string} Composed path. - */ -module.exports.mkpath = function () { - var result = path.resolve.apply(path, arguments); - var dirname = path.dirname(result); - fse.mkdirsSync(dirname); - return result; -}; -/** - * Helper to generate request key for storage. - * - * @function - * @arg {Request} req - Client request. - * @return {string} Request key according to its method, host, url. - */ -module.exports.getReqKey = req => req.method + "_" + req.headers.host + req.url; -/** - * Sorts files by date in folder. - * - * @function - * @arg {string} dir - Path to directory. - * @arg {object} [opts] - Options. - * @arg {boolean} [opts.desc=false] - Flag to reverse order. - * @return {string[]} Sequence of files sorted by date - */ -module.exports.filesByDate = (dir, opts) => { - opts = opts || {}; - opts.desc = opts.desc || false; - - var filesList = fs - .readdirSync(dir) - .filter(filename => { - var filePath = path.resolve(dir, filename); - return !fs.statSync(filePath).isDirectory(); - }) - .map(filename => { - var filePath = path.resolve(dir, filename); - return { path: filePath, - time: fs.statSync(filePath).mtime.getTime() }; - }) - .sort((a, b) => a.time - b.time) - .map(el => el.path); - - if (opts.desc) filesList.reverse(); - return filesList; -}; -/** - * Files sorted by order. - * - * @function - * @arg {string} dir - Path to directory. - * @arg {object} [opts] - Options. - * @arg {boolean} [opts.desc=false] - Flag to reverse order. - * @return {string[]} Sequence of files sorted by order. - */ -module.exports.filesByOrder = (dir, opts) => { - opts = opts || {}; - opts.desc = opts.desc || false; - - var filesList = fs - .readdirSync(dir) - .filter(filename => { - var filePath = path.resolve(dir, filename); - return !fs.statSync(filePath).isDirectory(); - }) - .map(filename => { - return { path: path.resolve(dir, filename), - number: parseInt(_.split(filename, "-", 1)[0]) || 0 }; - }) - .sort((a, b) => a.number - b.number) - .map(el => el.path); - - if (opts.desc) filesList.reverse(); - return filesList; -}; -/** - * Gets subfolders of directory. - * - * @function - * @arg {string} dir - Path to directory. - * @arg {object} [opts] - Options. - * @arg {boolean} [opts.nameOnly=false] - Gets only folder names. By default, - * full paths. - * @return {string[]} Sequence of results. - */ -module.exports.subFolders = (dir, opts) => { - opts = opts || {}; - opts.nameOnly = opts.nameOnly || false; - - if (!fs.existsSync(dir)) return []; - - var dirsList = fs - .readdirSync(dir) - .filter(filename => { - var filePath = path.resolve(dir, filename); - return fs.statSync(filePath).isDirectory(); - }); - - if (!opts.nameOnly) { - dirsList = dirsList.map(name => path.resolve(dir, name)); - } - - return dirsList; -}; -/** - * Returns function which switches message color. - * - * @function - * @arg {object} [opts] - Options. - * @arg {string} [opts.c1=magenta] - Color #1. - * @arg {string} [opts.c2=cyan] - Color #2. - * @return {function} Function to switch color of passed text in terminal. - */ -const switchColor = module.exports.switchColor = opts => { - opts = opts || {}; - var c1 = opts.c1 || "magenta"; - var c2 = opts.c2 || "cyan"; - - var trigger = true; - return function () { - var msg = Array.from(arguments).join(" "); - msg = msg[trigger ? c1 : c2].bold; - trigger = !trigger; - return msg; - }; -}; -/** - * Exits process with error printing. - * - * @function - * @arg {string} source - Source of fatal error. - * @return {function} Function with takes error to print and exits process. - */ -module.exports.exit = source => err => { - console.log(source + ":", err); - process.exit(1); -}; -/** - * @prop {string} cwd - Current work directory. - */ -module.exports.cwd = process.cwd(); - -module.exports.loadJson = require("./lib/loadJson"); -module.exports.config = require("./lib/config"); -module.exports.logger = require("./lib/logger"); - -/** - * Wraps function inside other functions. - * - * @function - * @arg {function[]} wrappers - List of functions which will wrap target. - * @arg {function} target - Target function which will be wrapped. - * @return {function} Wrapping function. - */ -module.exports.wrap = (wrappers, target) => { - _.clone(wrappers).reverse().forEach(wrapper => { - target = (target => () => wrapper(target))(target); - }); - return target; -}; -/** - * Helper to kill processes by name. - * - * @async - * @function - * @arg {string} procName - Process name or chunk of name. - * @return {Promise} - */ -module.exports.killProcs = procName => { - var logger = I.logger; - logger.debug(`Looking for ${procName} processes to kill...`); - - return I.__findProcess("name", procName).then(procList => { - - return procList.forEach(proc => { - - if ([process.pid, process.ppid].includes(+proc.pid)) return; - logger.debug(`Killing ${procName} with PID ${proc.pid}...`); - - try { - process.kill(proc.pid, "SIGTERM"); - logger.debug("Process is killed"); - - } catch (e) { - if (e.message !== "kill ESRCH") throw e; - logger.error(`Can't kill ${procName} with PID ${proc.pid} because it doesn't exist`); - } - }); - }); -}; -/** - * Help - * - * @function - * @arg {function} [d] - Function to manage describe message: join, colorize, etc. - * @return {yargs} Preconfigured yargs. - */ -module.exports.help = d => { - d = d || switchColor(); - return yargs - .options({ - "config [path]": { - alias: "c", - describe: d("Path to JSON file with CLI arguments.", - "Default is 'cwd/config.json' (if it exists)."), - type: "string", - group: "Arguments:", - }, - "stdout-log": { - describe: d("Print log messages to stdout."), - type: "boolean", - group: "Log:", - }, - "log [path]": { - describe: d("Path to log file. Default is 'cwd/glace.log'."), - type: "string", - group: "Log:", - }, - "log-level [level]": { - describe: d("Log level. Supported values are 'error', 'warn',", - "'info', 'verbose', 'debug', 'silly'. Default is 'debug'."), - type: "string", - group: "Log:", - }, - }) - .help("h") - .alias("h", "help"); -}; - -var complete = line => { - line = colors.strip(line); - var tokens = line.split(/[^A-Za-z0-9._$]+/).filter(i => i); - - if (!tokens.length) return [[], line]; - - var targetToken = tokens[tokens.length - 1]; - - var namespace = global; - var filterPrefix = targetToken; - - var targetObject; - if (targetToken.includes(".")) { - - targetObject = targetToken.split("."); - filterPrefix = targetObject.pop(); - targetObject = targetObject.join("."); - - if (!targetObject) return [[], targetToken]; - - try { - namespace = eval(targetObject); - } catch (e) { - return [[], targetToken]; - } - } - - try { - var completions = []; - for (var key in namespace) { - completions.push(key); - } - completions = _.union( - completions, - Object.getOwnPropertyNames(namespace), - Object.getOwnPropertyNames(Object.getPrototypeOf(namespace)) - ).sort() - .filter(i => i.startsWith(filterPrefix)) - .filter(i => /^(\w|\$)+$/.test(i)) - .filter(i => /^\D/.test(i)); - } catch (e) { - return [[], targetToken]; - } - - if (targetObject) { - completions = completions.map(i => targetObject + "." + i); - } - return [completions, targetToken]; -}; -/** - * Interactive debugger with syntax highlighting and autocomplete. - * - * - * - * @async - * @function - * @arg {string} [helpMessage] - Help message. - * @return {Promise} - */ -module.exports.debug = async function (helpMessage) { - - const defaultHelp = "In interactive mode you can execute any nodejs code.\n" + - "Also next commands are available:\n"; - - helpMessage = helpMessage || defaultHelp; - - helpMessage += "- h, help - show interactive mode help;\n" + - "- go - continue code execution;\n" + - "- exit - finish current nodejs process;"; - - console.log("interactive mode".yellow); - - var rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - completer: complete, - }); - - var ttyWrite = rl._ttyWrite; - rl._ttyWrite = function (s, key) { - - if (this.cursor <= this.line.length) { - this.line = colors.strip(this.line); - if (this.cursor > this.line.length) { - this._moveCursor(+Infinity); - } - } - - ttyWrite.call(this, s, key); - - if (this.cursor < this.line.length) { - this.line = colors.strip(this.line); - if (this.cursor > this.line.length) { - this._moveCursor(+Infinity); - } - } else { - this.line = highlight(colors.strip(this.line), { language: "js" }); - this._moveCursor(+Infinity); - } - if (key.name !== "return") { - this._refreshLine(); - } - }; - - var origGlobals = {}; - var isFinished = false; - - while (!isFinished) { - isFinished = await new Promise(resolve => { - rl.question("> ".red, answer => { - answer = colors.strip(answer); - - if (answer === "exit") { - console.log("emergency exit".red); - process.exit(1); - } - - if (answer === "go") { - console.log("continue execution".green); - resolve(true); - return; - } - - if (["help", "h"].includes(answer)) { - console.log(helpMessage); - resolve(false); - return; - } - - var ast, varName; - - try { - ast = espree.parse(answer, { ecmaVersion: 9 }); - varName = ast.body[0].expression.left.name; - } catch (e) { - try { - varName = ast.body[0].declarations[0].id.name; - } catch (e) { /* nothing */ } - } - - Promise - .resolve() - .then(() => { - var result = eval(answer); - if (varName) { - if (!Object.prototype.hasOwnProperty.call(origGlobals, varName)) { - origGlobals[varName] = global[varName]; - } - global[varName] = eval(varName); - } - return result; - }) - .then(result => console.log(util.format(result).yellow)) - .catch(e => console.log(util.format(e).red)) - .then(() => resolve(false)); - }); - }); - } - - for (var [k, v] of Object.entries(origGlobals)) { - global[k] = v; - } -}; - -Object.assign(exports, require("./lib/small")); - -module.exports.download = require("./lib/download"); -module.exports.Pool = require("./lib/pool"); - -const I = module.exports; diff --git a/lib/debug.js b/lib/debug.js new file mode 100644 index 0000000..9aec98d --- /dev/null +++ b/lib/debug.js @@ -0,0 +1,165 @@ + + +var complete = line => { + line = colors.strip(line); + var tokens = line.split(/[^A-Za-z0-9._$]+/).filter(i => i); + + if (!tokens.length) return [[], line]; + + var targetToken = tokens[tokens.length - 1]; + + var namespace = global; + var filterPrefix = targetToken; + + var targetObject; + if (targetToken.includes(".")) { + + targetObject = targetToken.split("."); + filterPrefix = targetObject.pop(); + targetObject = targetObject.join("."); + + if (!targetObject) return [[], targetToken]; + + try { + namespace = eval(targetObject); + } catch (e) { + return [[], targetToken]; + } + } + + try { + var completions = []; + for (var key in namespace) { + completions.push(key); + } + completions = _.union( + completions, + Object.getOwnPropertyNames(namespace), + Object.getOwnPropertyNames(Object.getPrototypeOf(namespace)) + ).sort() + .filter(i => i.startsWith(filterPrefix)) + .filter(i => /^(\w|\$)+$/.test(i)) + .filter(i => /^\D/.test(i)); + } catch (e) { + return [[], targetToken]; + } + + if (targetObject) { + completions = completions.map(i => targetObject + "." + i); + } + return [completions, targetToken]; +}; +/** + * Interactive debugger with syntax highlighting and autocomplete. + * + * + * + * @async + * @function + * @arg {string} [helpMessage] - Help message. + * @return {Promise} + */ +module.exports.debug = async function (helpMessage) { + + const defaultHelp = "In interactive mode you can execute any nodejs code.\n" + + "Also next commands are available:\n"; + + helpMessage = helpMessage || defaultHelp; + + helpMessage += "- h, help - show interactive mode help;\n" + + "- go - continue code execution;\n" + + "- exit - finish current nodejs process;"; + + console.log("interactive mode".yellow); + + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + completer: complete, + }); + + var ttyWrite = rl._ttyWrite; + rl._ttyWrite = function (s, key) { + + if (this.cursor <= this.line.length) { + this.line = colors.strip(this.line); + if (this.cursor > this.line.length) { + this._moveCursor(+Infinity); + } + } + + ttyWrite.call(this, s, key); + + if (this.cursor < this.line.length) { + this.line = colors.strip(this.line); + if (this.cursor > this.line.length) { + this._moveCursor(+Infinity); + } + } else { + this.line = highlight(colors.strip(this.line), { language: "js" }); + this._moveCursor(+Infinity); + } + if (key.name !== "return") { + this._refreshLine(); + } + }; + + var origGlobals = {}; + var isFinished = false; + + while (!isFinished) { + isFinished = await new Promise(resolve => { + rl.question("> ".red, answer => { + answer = colors.strip(answer); + + if (answer === "exit") { + console.log("emergency exit".red); + process.exit(1); + } + + if (answer === "go") { + console.log("continue execution".green); + resolve(true); + return; + } + + if (["help", "h"].includes(answer)) { + console.log(helpMessage); + resolve(false); + return; + } + + var ast, varName; + + try { + ast = espree.parse(answer, { ecmaVersion: 9 }); + varName = ast.body[0].expression.left.name; + } catch (e) { + try { + varName = ast.body[0].declarations[0].id.name; + } catch (e) { /* nothing */ } + } + + Promise + .resolve() + .then(() => { + var result = eval(answer); + if (varName) { + if (!Object.prototype.hasOwnProperty.call(origGlobals, varName)) { + origGlobals[varName] = global[varName]; + } + global[varName] = eval(varName); + } + return result; + }) + .then(result => console.log(util.format(result).yellow)) + .catch(e => console.log(util.format(e).red)) + .then(() => resolve(false)); + }); + }); + } + + for (var [k, v] of Object.entries(origGlobals)) { + global[k] = v; + } +}; diff --git a/lib/help.js b/lib/help.js new file mode 100644 index 0000000..d7d2a94 --- /dev/null +++ b/lib/help.js @@ -0,0 +1,40 @@ +"use strict"; + +/** + * Help + * + * @function + * @arg {function} [d] - Function to manage describe message: join, colorize, etc. + * @return {yargs} Preconfigured yargs. + */ +module.exports.help = d => { + d = d || switchColor(); + return yargs + .options({ + "config [path]": { + alias: "c", + describe: d("Path to JSON file with CLI arguments.", + "Default is 'cwd/config.json' (if it exists)."), + type: "string", + group: "Arguments:", + }, + "stdout-log": { + describe: d("Print log messages to stdout."), + type: "boolean", + group: "Log:", + }, + "log [path]": { + describe: d("Path to log file. Default is 'cwd/glace.log'."), + type: "string", + group: "Log:", + }, + "log-level [level]": { + describe: d("Log level. Supported values are 'error', 'warn',", + "'info', 'verbose', 'debug', 'silly'. Default is 'debug'."), + type: "string", + group: "Log:", + }, + }) + .help("h") + .alias("h", "help"); +}; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..748069d --- /dev/null +++ b/lib/index.js @@ -0,0 +1,26 @@ +/** + * `GlaceJS` utils. + * + * @module glace-utils + */ + +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); +const util = require("util"); + +const colors = require("colors"); +const espree = require("espree"); +const highlight = require("cli-highlight").highlight; +const _ = require("lodash"); +const yargs = require("yargs").help(" "); // disable default `--help` capture +const fse = require("fs-extra"); + +Object.assign(exports, require("./small")); +exports.help = require("./help"); +exports.debug = require("./debug"); +exports.download = require("./download"); +exports.Pool = require("./pool"); +exports.loadJson = require("./loadJson"); +exports.config = require("./config"); +exports.logger = require("./logger"); diff --git a/lib/small.js b/lib/small.js index d9b6dba..0d4482c 100644 --- a/lib/small.js +++ b/lib/small.js @@ -8,8 +8,12 @@ const os = require("os"); const util = require("util"); +require("colors"); const _ = require("lodash"); const BaseError = require("es6-error"); +const findProcess = require("find-process"); + +const logger = require("./logger"); /** * Creates a new instance of `glacejs` error. @@ -36,6 +40,11 @@ util.inherits(GlaceError, BaseError); */ const hostname = os.hostname().toLowerCase(); +/** + * @prop {string} cwd - Current work directory. + */ +const cwd = process.cwd(); + /** * Pick default value for variable among listed values. * @@ -400,8 +409,229 @@ const objOnScreenPos = (obj, screen) => { return res; }; +/** + * Helper to kill processes by name. + * + * @memberOf module:glace-utils + * @async + * @function + * @arg {string} procName - Process name or chunk of name. + * @return {Promise} + */ +const killProcs = procName => { + logger.debug(`Looking for ${procName} processes to kill...`); + + return findProcess("name", procName).then(procList => { + + return procList.forEach(proc => { + + if ([process.pid, process.ppid].includes(+proc.pid)) return; + logger.debug(`Killing ${procName} with PID ${proc.pid}...`); + + try { + process.kill(proc.pid, "SIGTERM"); + logger.debug("Process is killed"); + + } catch (e) { + if (e.message !== "kill ESRCH") throw e; + logger.error(`Can't kill ${procName} with PID ${proc.pid} because it doesn't exist`); + } + }); + }); +}; + +/** + * Wraps function inside other functions. + * + * @memberOf module:glace-utils + * @function + * @arg {function[]} wrappers - List of functions which will wrap target. + * @arg {function} target - Target function which will be wrapped. + * @return {function} Wrapping function. + */ +const wrap = (wrappers, target) => { + _.clone(wrappers).reverse().forEach(wrapper => { + target = (target => () => wrapper(target))(target); + }); + return target; +}; + +/** + * Gets subfolders of directory. + * + * @memberOf module:glace-utils + * @function + * @arg {string} dir - Path to directory. + * @arg {object} [opts] - Options. + * @arg {boolean} [opts.nameOnly=false] - Gets only folder names. By default, + * full paths. + * @return {string[]} Sequence of results. + */ +const subFolders = (dir, opts) => { + opts = opts || {}; + opts.nameOnly = opts.nameOnly || false; + + if (!fs.existsSync(dir)) return []; + + var dirsList = fs + .readdirSync(dir) + .filter(filename => { + var filePath = path.resolve(dir, filename); + return fs.statSync(filePath).isDirectory(); + }); + + if (!opts.nameOnly) { + dirsList = dirsList.map(name => path.resolve(dir, name)); + } + + return dirsList; +}; + +/** + * Exits process with error printing. + * + * @memberOf module:glace-utils + * @function + * @arg {string} source - Source of fatal error. + * @return {function} Function with takes error to print and exits process. + */ +const exit = source => err => { + console.log(source + ":", err); + process.exit(1); +}; + +/** + * Returns function which switches message color. + * + * @memberOf module:glace-utils + * @function + * @arg {object} [opts] - Options. + * @arg {string} [opts.c1=magenta] - Color #1. + * @arg {string} [opts.c2=cyan] - Color #2. + * @return {function} Function to switch color of passed text in terminal. + */ +const switchColor = opts => { + opts = opts || {}; + const c1 = opts.c1 || "magenta"; + const c2 = opts.c2 || "cyan"; + + let trigger = true; + return function () { + let msg = Array.from(arguments).join(" "); + msg = msg[trigger ? c1 : c2].bold; + trigger = !trigger; + return msg; + }; +}; + +/** + * Helper to generate request key for storage. + * + * @memberOf module:glace-utils + * @function + * @arg {Request} req - Client request. + * @return {string} Request key according to its method, host, url. + */ +const getReqKey = req => req.method + "_" + req.headers.host + req.url; + +/** + * Files sorted by order. + * + * @memberOf module:glace-utils + * @function + * @arg {string} dir - Path to directory. + * @arg {boolean} [desc=false] - Flag to reverse order. + * @return {string[]} Sequence of files sorted by order. + */ +const filesByOrder = (dir, desc = false) => { + const filesList = fs + .readdirSync(dir) + .filter(filename => { + const filePath = path.resolve(dir, filename); + return !fs.statSync(filePath).isDirectory(); + }) + .map(filename => { + return { path: path.resolve(dir, filename), + number: parseInt(_.split(filename, "-", 1)[0]) || 0 }; + }) + .sort((a, b) => a.number - b.number) + .map(el => el.path); + + if (desc) filesList.reverse(); + return filesList; +}; + +/** + * Sorts files by date in folder. + * + * @memberOf module:glace-utils + * @function + * @arg {string} dir - Path to directory. + * @arg {boolean} [desc=false] - Flag to reverse order. + * @return {string[]} Sequence of files sorted by date + */ +const filesByDate = (dir, desc = false) => { + const filesList = fs + .readdirSync(dir) + .filter(filename => { + const filePath = path.resolve(dir, filename); + return !fs.statSync(filePath).isDirectory(); + }) + .map(filename => { + const filePath = path.resolve(dir, filename); + return { path: filePath, + time: fs.statSync(filePath).mtime.getTime() }; + }) + .sort((a, b) => a.time - b.time) + .map(el => el.path); + + if (opts.desc) filesList.reverse(); + return filesList; +}; + +/** + * Composes file path from segments. If folder of file is absent, it will + * be created. + * + * @memberOf module:glace-utils + * @function + * @arg {...string} paths - A sequence of paths or path segments. + * @return {string} Composed path. + */ +const mkpath = function () { + var result = path.resolve.apply(path, arguments); + var dirname = path.dirname(result); + fse.mkdirsSync(dirname); + return result; +}; + +/** + * Clears empty folders recursive. + * + * @memberOf module:glace-utils + * @function + * @arg {string} folder - Path to root folder. + */ +const clearEmptyFolders = folder => { + const files = fs.readdirSync(folder); + + for (const fileName of files) { + const filePath = path.join(folder, fileName); + if (fs.statSync(filePath).isDirectory()) { + clearEmptyFolders(filePath); + } + } + if (!_.isEmpty(files)) { + files = fs.readdirSync(folder); + } + if (_.isEmpty(files)) { + fs.rmdirSync(folder); + } +}; + module.exports = { GlaceError, + cwd, hostname, coalesce, capitalize, @@ -417,4 +647,14 @@ module.exports = { docString, isInScreen, objOnScreenPos, + killProcs, + wrap, + subFolders, + exit, + switchColor, + getReqKey, + mkpath, + filesByDate, + filesByOrder, + clearEmptyFolders, }; diff --git a/tests/unit/testSmall.js b/tests/unit/testSmall.js index 1def9db..3a29a2d 100644 --- a/tests/unit/testSmall.js +++ b/tests/unit/testSmall.js @@ -393,6 +393,7 @@ suite("small", () => { let x, y; beforeChunk(() => { + delete Function.prototype.__doc__; small.docString(); x = function () { /** docstring */