metarhia-common
Advanced tools
Comparing version 0.0.12 to 0.0.13
'use strict'; | ||
const api = {}; | ||
api.os = require('os'); | ||
api.path = require('path'); | ||
api.events = require('events'); | ||
api.crypto = require('crypto'); | ||
api.common = {}; | ||
module.exports = api.common; | ||
@@ -23,7 +16,4 @@ const submodules = [ | ||
'cache', // Cache (enhanced Map) | ||
]; | ||
].map(path => './lib/' + path).map(require); | ||
submodules | ||
.map(path => './lib/' + path) | ||
.map(require) | ||
.map(exports => exports(api)); | ||
module.exports = Object.assign({}, ...submodules); |
'use strict'; | ||
module.exports = (api) => { | ||
const splitAt = ( | ||
// Splits array into two parts | ||
index, // index defining end of first part and start of second | ||
array // array which is splitting | ||
// Returns tuple with two parts of the array | ||
) => { | ||
const part1 = array.slice(0, index); | ||
const part2 = array.slice(index, array.length); | ||
return [part1, part2]; | ||
}; | ||
api.common.splitAt = ( | ||
// Splits array into two parts | ||
index, // index defining end of first part and start of second | ||
array // array which is splitting | ||
// Returns tuple with two parts of the array | ||
) => { | ||
const part1 = array.slice(0, index); | ||
const part2 = array.slice(index, array.length); | ||
return [part1, part2]; | ||
}; | ||
module.exports = { | ||
splitAt | ||
}; |
120
lib/cache.js
'use strict'; | ||
module.exports = (api) => { | ||
const withClear = ( | ||
cache // mixin to this Map | ||
) => { | ||
api.common.cache = ( | ||
// Extend Map interface total allocated size: map.allocated | ||
options // { calcSize } | ||
) => { | ||
const cache = new Map(); | ||
const calcSize = (options && options.calcSize) || false; | ||
cache.add = cache.set; | ||
if (calcSize) api.common.cache.withSize(cache); | ||
else api.common.cache.withClear(cache); | ||
cache.del = cache.delete; | ||
return cache; | ||
cache.clr = ( | ||
prefix, // string to compare with key start | ||
fn // listener function `(key)` on delete key | ||
) => { | ||
let key; | ||
for (key of cache.keys()) { | ||
if (key.startsWith(prefix)) { | ||
cache.delete(key); | ||
if (fn) fn(key); | ||
} | ||
} | ||
}; | ||
api.common.cache.withClear = ( | ||
cache // mixin to this Map | ||
) => { | ||
}; | ||
cache.add = cache.set; | ||
const withSize = ( | ||
cache // mixin to this Map | ||
) => { | ||
cache.del = cache.delete; | ||
cache.allocated = 0; | ||
cache.clr = ( | ||
prefix, // string to compare with key start | ||
fn // listener function `(key)` on delete key | ||
) => { | ||
let key; | ||
for (key of cache.keys()) { | ||
if (key.startsWith(prefix)) { | ||
cache.delete(key); | ||
if (fn) fn(key); | ||
} | ||
} | ||
}; | ||
const dataSize = data => (data && data.length ? data.length : 0); | ||
cache.add = (key, val) => { | ||
if (cache.has(key)) { | ||
const prev = cache.get(key); | ||
cache.allocated -= dataSize(prev); | ||
} | ||
cache.allocated += dataSize(val); | ||
cache.set(key, val); | ||
}; | ||
api.common.cache.withSize = ( | ||
cache // mixin to this Map | ||
cache.del = (key) => { | ||
if (cache.has(key)) { | ||
const val = cache.get(key); | ||
cache.allocated -= dataSize(val); | ||
} | ||
}; | ||
cache.clr = ( | ||
prefix, // string to compare with key start | ||
fn // function `(key, val)` to be called on each key (optional) | ||
) => { | ||
cache.allocated = 0; | ||
const dataSize = data => (data && data.length ? data.length : 0); | ||
cache.add = (key, val) => { | ||
if (cache.has(key)) { | ||
const prev = cache.get(key); | ||
cache.allocated -= dataSize(prev); | ||
} | ||
cache.allocated += dataSize(val); | ||
cache.set(key, val); | ||
}; | ||
cache.del = (key) => { | ||
if (cache.has(key)) { | ||
const val = cache.get(key); | ||
cache.forEach((val, key) => { | ||
if (key.startsWith(prefix)) { | ||
cache.allocated -= dataSize(val); | ||
cache.delete(key); | ||
if (fn) fn(key, val); | ||
} | ||
}; | ||
}); | ||
}; | ||
cache.clr = ( | ||
prefix, // string to compare with key start | ||
fn // function `(key, val)` to be called on each key (optional) | ||
) => { | ||
cache.forEach((val, key) => { | ||
if (key.startsWith(prefix)) { | ||
cache.allocated -= dataSize(val); | ||
cache.delete(key); | ||
if (fn) fn(key, val); | ||
} | ||
}); | ||
}; | ||
}; | ||
}; | ||
const cache = ( | ||
// Extend Map interface total allocated size: map.allocated | ||
options // { calcSize } | ||
) => { | ||
const cache = new Map(); | ||
const calcSize = (options && options.calcSize) || false; | ||
if (calcSize) withSize(cache); | ||
else withClear(cache); | ||
return cache; | ||
}; | ||
module.exports = { | ||
cache | ||
}; |
363
lib/data.js
'use strict'; | ||
module.exports = (api) => { | ||
const SCALAR_TYPES = ['boolean', 'number', 'string']; | ||
const SCALAR_TYPES = ['boolean', 'number', 'string']; | ||
const isScalar = variable => SCALAR_TYPES.includes(typeof(variable)); | ||
api.common.isScalar = variable => SCALAR_TYPES.includes(typeof(variable)); | ||
const copy = ( | ||
// Copy dataset, copy objects to new array | ||
ds // array, source dataset | ||
) => ds.slice(); | ||
/* TODO: test speed in following implementations: | ||
1. slice() and slice(0) | ||
2. [].concat(arr); | ||
3. following solution: | ||
let result = [], | ||
l1 = ds.length; | ||
for (i = 0; i < l1; i++) { | ||
result.push(ds[i]); | ||
} | ||
return result; | ||
*/ | ||
api.common.copy = ( | ||
// Copy dataset, copy objects to new array | ||
ds // array, source dataset | ||
) => ds.slice(); | ||
/* TODO: test speed in following implementations: | ||
1. slice() and slice(0) | ||
2. [].concat(arr); | ||
3. following solution: | ||
let result = [], | ||
l1 = ds.length; | ||
for (i = 0; i < l1; i++) { | ||
result.push(ds[i]); | ||
} | ||
return result; | ||
*/ | ||
const clone = ( | ||
// Clone object | ||
obj // object or array | ||
) => { | ||
if (typeof(obj) !== 'object' || obj === null) return obj; | ||
return Array.isArray(obj) ? cloneArray(obj) : cloneObject(obj); | ||
}; | ||
api.common.clone = ( | ||
// Clone object | ||
obj // object or array | ||
) => { | ||
if (typeof(obj) !== 'object' || obj === null) return obj; | ||
return Array.isArray(obj) ? cloneArray(obj) : cloneObject(obj); | ||
}; | ||
function cloneArray(arr) { | ||
const size = arr.length; | ||
const array = new Array(size); | ||
let i, val; | ||
for (i = 0; i < size; i++) { | ||
val = arr[i]; | ||
if (typeof(val) !== 'object' || val === null) { | ||
array[i] = val; | ||
} else if (Array.isArray(val)) { | ||
array[i] = cloneArray(val); | ||
} else { | ||
array[i] = cloneObject(val); | ||
} | ||
function cloneArray(arr) { | ||
const size = arr.length; | ||
const array = new Array(size); | ||
let i, val; | ||
for (i = 0; i < size; i++) { | ||
val = arr[i]; | ||
if (typeof(val) !== 'object' || val === null) { | ||
array[i] = val; | ||
} else if (Array.isArray(val)) { | ||
array[i] = cloneArray(val); | ||
} else { | ||
array[i] = cloneObject(val); | ||
} | ||
return array; | ||
} | ||
return array; | ||
} | ||
function cloneObject(obj) { | ||
const object = {}; | ||
const keys = Object.keys(obj); | ||
const size = keys.length; | ||
let i, key, val; | ||
for (i = 0; i < size; i++) { | ||
key = keys[i]; | ||
val = obj[key]; | ||
if (typeof(val) !== 'object' || val === null) { | ||
object[key] = val; | ||
} else if (Array.isArray(val)) { | ||
object[key] = cloneArray(val); | ||
} else { | ||
object[key] = cloneObject(val); | ||
} | ||
function cloneObject(obj) { | ||
const object = {}; | ||
const keys = Object.keys(obj); | ||
const size = keys.length; | ||
let i, key, val; | ||
for (i = 0; i < size; i++) { | ||
key = keys[i]; | ||
val = obj[key]; | ||
if (typeof(val) !== 'object' || val === null) { | ||
object[key] = val; | ||
} else if (Array.isArray(val)) { | ||
object[key] = cloneArray(val); | ||
} else { | ||
object[key] = cloneObject(val); | ||
} | ||
return object; | ||
} | ||
return object; | ||
} | ||
api.common.duplucate = ( | ||
// Duplicate object | ||
obj // object or array | ||
) => { | ||
if (typeof(obj) !== 'object' || obj === null) return obj; | ||
const references = new Map(); | ||
const dup = Array.isArray(obj) ? duplicateArray : duplicateObject; | ||
return dup(obj, references); | ||
}; | ||
const duplucate = ( | ||
// Duplicate object | ||
obj // object or array | ||
) => { | ||
if (typeof(obj) !== 'object' || obj === null) return obj; | ||
const references = new Map(); | ||
const dup = Array.isArray(obj) ? duplicateArray : duplicateObject; | ||
return dup(obj, references); | ||
}; | ||
function duplicateArray(arr, references) { | ||
const size = arr.length; | ||
const array = new Array(size); | ||
references.set(arr, array); | ||
let i, val; | ||
for (i = 0; i < size; i++) { | ||
val = arr[i]; | ||
if (references.has(val)) { | ||
array[i] = references.get(val); | ||
} else if (typeof(val) !== 'object' || val === null) { | ||
array[i] = val; | ||
} else if (Array.isArray(val)) { | ||
array[i] = duplicateArray(val, references); | ||
} else { | ||
array[i] = duplicateObject(val, references); | ||
} | ||
function duplicateArray(arr, references) { | ||
const size = arr.length; | ||
const array = new Array(size); | ||
references.set(arr, array); | ||
let i, val; | ||
for (i = 0; i < size; i++) { | ||
val = arr[i]; | ||
if (references.has(val)) { | ||
array[i] = references.get(val); | ||
} else if (typeof(val) !== 'object' || val === null) { | ||
array[i] = val; | ||
} else if (Array.isArray(val)) { | ||
array[i] = duplicateArray(val, references); | ||
} else { | ||
array[i] = duplicateObject(val, references); | ||
} | ||
return array; | ||
} | ||
return array; | ||
} | ||
function duplicateObject(obj, references) { | ||
const object = ( | ||
obj.constructor ? new obj.constructor() : Object.create(null) | ||
); | ||
references.set(obj, object); | ||
const keys = Object.keys(obj); | ||
const size = keys.length; | ||
let i, key, val; | ||
for (i = 0; i < size; i++) { | ||
key = keys[i]; | ||
val = obj[key]; | ||
if (references.has(val)) { | ||
object[key] = references.get(val); | ||
} else if (typeof(val) !== 'object' || val === null) { | ||
object[key] = val; | ||
} else if (Array.isArray(val)) { | ||
object[key] = duplicateArray(val, references); | ||
} else { | ||
object[key] = duplicateObject(val, references); | ||
} | ||
function duplicateObject(obj, references) { | ||
const object = ( | ||
obj.constructor ? new obj.constructor() : Object.create(null) | ||
); | ||
references.set(obj, object); | ||
const keys = Object.keys(obj); | ||
const size = keys.length; | ||
let i, key, val; | ||
for (i = 0; i < size; i++) { | ||
key = keys[i]; | ||
val = obj[key]; | ||
if (references.has(val)) { | ||
object[key] = references.get(val); | ||
} else if (typeof(val) !== 'object' || val === null) { | ||
object[key] = val; | ||
} else if (Array.isArray(val)) { | ||
object[key] = duplicateArray(val, references); | ||
} else { | ||
object[key] = duplicateObject(val, references); | ||
} | ||
return object; | ||
} | ||
return object; | ||
} | ||
api.common.getByPath = ( | ||
// Read property by dot-separated path | ||
data, // object | ||
dataPath // string, dot-separated path | ||
) => { | ||
const path = dataPath.split('.'); | ||
let obj = data; | ||
let i, len, next, prop; | ||
for (i = 0, len = path.length; i < len; i++) { | ||
prop = path[i]; | ||
next = obj[prop]; | ||
if (next === undefined || next === null) return next; | ||
const getByPath = ( | ||
// Read property by dot-separated path | ||
data, // object | ||
dataPath // string, dot-separated path | ||
) => { | ||
const path = dataPath.split('.'); | ||
let obj = data; | ||
let i, len, next, prop; | ||
for (i = 0, len = path.length; i < len; i++) { | ||
prop = path[i]; | ||
next = obj[prop]; | ||
if (next === undefined || next === null) return next; | ||
obj = next; | ||
} | ||
return obj; | ||
}; | ||
const setByPath = ( | ||
// Set property by dot-separated path | ||
data, // object | ||
dataPath, // string, dot-separated path | ||
value // new value | ||
) => { | ||
const path = dataPath.split('.'); | ||
let obj = data; | ||
let i, len, next, prop; | ||
for (i = 0, len = path.length; i < len; i++) { | ||
prop = path[i]; | ||
next = obj[prop]; | ||
if (i === path.length - 1) { | ||
obj[prop] = value; | ||
return true; | ||
} else { | ||
if (next === undefined || next === null) { | ||
if (typeof(obj) !== 'object') return false; | ||
next = {}; | ||
obj[prop] = next; | ||
} | ||
obj = next; | ||
} | ||
return obj; | ||
}; | ||
} | ||
return false; | ||
}; | ||
api.common.setByPath = ( | ||
// Set property by dot-separated path | ||
data, // object | ||
dataPath, // string, dot-separated path | ||
value // new value | ||
) => { | ||
const path = dataPath.split('.'); | ||
let obj = data; | ||
let i, len, next, prop; | ||
for (i = 0, len = path.length; i < len; i++) { | ||
prop = path[i]; | ||
next = obj[prop]; | ||
if (i === path.length - 1) { | ||
obj[prop] = value; | ||
const deleteByPath = ( | ||
// Delete property by dot-separated path | ||
data, // object | ||
dataPath // string, dot-separated path | ||
) => { | ||
const path = dataPath.split('.'); | ||
let obj = data; | ||
let i, len, next, prop; | ||
for (i = 0, len = path.length; i < len; i++) { | ||
prop = path[i]; | ||
next = obj[prop]; | ||
if (i === path.length - 1) { | ||
if (obj.hasOwnProperty(prop)) { | ||
delete obj[prop]; | ||
return true; | ||
} else { | ||
if (next === undefined || next === null) { | ||
if (typeof(obj) !== 'object') return false; | ||
next = {}; | ||
obj[prop] = next; | ||
} | ||
obj = next; | ||
} | ||
} else { | ||
if (next === undefined || next === null) return false; | ||
obj = next; | ||
} | ||
return false; | ||
}; | ||
} | ||
return false; | ||
}; | ||
api.common.deleteByPath = ( | ||
// Delete property by dot-separated path | ||
data, // object | ||
dataPath // string, dot-separated path | ||
) => { | ||
const path = dataPath.split('.'); | ||
let obj = data; | ||
let i, len, next, prop; | ||
for (i = 0, len = path.length; i < len; i++) { | ||
prop = path[i]; | ||
next = obj[prop]; | ||
if (i === path.length - 1) { | ||
if (obj.hasOwnProperty(prop)) { | ||
delete obj[prop]; | ||
return true; | ||
} | ||
} else { | ||
if (next === undefined || next === null) return false; | ||
obj = next; | ||
} | ||
const merge = ( | ||
// Distinct merge miltiple arrays | ||
...args // arrays | ||
) => { | ||
const array = args[0]; | ||
let i, ilen, j, jlen, arr, val; | ||
for (i = 1, ilen = args.length; i < ilen; i++) { | ||
arr = args[i]; | ||
for (j = 0, jlen = arr.length; j < jlen; j++) { | ||
val = arr[j]; | ||
if (!array.includes(val)) array.push(val); | ||
} | ||
return false; | ||
}; | ||
} | ||
return array; | ||
}; | ||
api.common.merge = ( | ||
// Distinct merge miltiple arrays | ||
...args // arrays | ||
) => { | ||
const array = args[0]; | ||
let i, ilen, j, jlen, arr, val; | ||
for (i = 1, ilen = args.length; i < ilen; i++) { | ||
arr = args[i]; | ||
for (j = 0, jlen = arr.length; j < jlen; j++) { | ||
val = arr[j]; | ||
if (!array.includes(val)) array.push(val); | ||
} | ||
} | ||
return array; | ||
}; | ||
module.exports = { | ||
isScalar, | ||
copy, | ||
clone, | ||
duplucate, | ||
getByPath, | ||
setByPath, | ||
deleteByPath, | ||
merge | ||
}; |
'use strict'; | ||
module.exports = (api) => { | ||
const omap = ( | ||
// Function for mapping object fields | ||
mapFn, // funtions applied to every field value | ||
obj // object which fields used for mapping | ||
// Returns object with same reference but with transformed fields | ||
) => { | ||
let key; | ||
for (key in obj) { | ||
obj[key] = mapFn(obj[key]); | ||
} | ||
return obj; | ||
}; | ||
api.common.omap = ( | ||
// Function for mapping object fields | ||
mapFn, // funtions applied to every field value | ||
obj // object which fields used for mapping | ||
// Returns object with same reference but with transformed fields | ||
) => { | ||
let key; | ||
for (key in obj) { | ||
obj[key] = mapFn(obj[key]); | ||
} | ||
return obj; | ||
}; | ||
const compose = ( | ||
// Compose functions | ||
...fns // functions | ||
) => ( | ||
...args // arguments to bassed to first function | ||
) => { | ||
if (fns.length === 0) return args[0]; | ||
api.common.compose = ( | ||
// Compose functions | ||
...fns // functions | ||
) => ( | ||
...args // arguments to bassed to first function | ||
) => { | ||
if (fns.length === 0) return args[0]; | ||
let res = fns[0](...args); | ||
let i; | ||
for (i = 1; i < fns.length; i++) { | ||
res = fns[i](res); | ||
} | ||
return res; | ||
}; | ||
let res = fns[0](...args); | ||
let i; | ||
for (i = 1; i < fns.length; i++) { | ||
res = fns[i](res); | ||
} | ||
return res; | ||
}; | ||
const maybe = (fn, defVal, value) => ( | ||
value !== undefined && value !== null ? fn(value) : defVal | ||
); | ||
api.common.maybe = (fn, defVal, value) => ( | ||
value !== undefined && value !== null ? fn(value) : defVal | ||
); | ||
const zip = ( | ||
// Zipping several arrays into one | ||
...arrays // input arrays | ||
// Returns array length is minimal of input arrays length | ||
// Element with index i of resulting array is array with | ||
// elements with index i from input arrays | ||
) => { | ||
if (arrays.length === 0) return arrays; | ||
let i, j; | ||
api.common.zip = ( | ||
// Zipping several arrays into one | ||
...arrays // input arrays | ||
// Returns array length is minimal of input arrays length | ||
// Element with index i of resulting array is array with | ||
// elements with index i from input arrays | ||
) => { | ||
if (arrays.length === 0) return arrays; | ||
let i, j; | ||
let minLen = arrays[0].length; | ||
for (i = 1; i < arrays.length; i++) { | ||
minLen = Math.min(arrays[i].length, minLen); | ||
} | ||
let minLen = arrays[0].length; | ||
for (i = 1; i < arrays.length; i++) { | ||
minLen = Math.min(arrays[i].length, minLen); | ||
const res = new Array(minLen); | ||
for (i = 0; i < res.length; i++) { | ||
res[i] = new Array(arrays.length); | ||
for (j = 0; j < res[i].length; j++) { | ||
res[i][j] = arrays[j][i]; | ||
} | ||
} | ||
return res; | ||
}; | ||
const res = new Array(minLen); | ||
for (i = 0; i < res.length; i++) { | ||
res[i] = new Array(arrays.length); | ||
for (j = 0; j < res[i].length; j++) { | ||
res[i][j] = arrays[j][i]; | ||
} | ||
} | ||
return res; | ||
}; | ||
const replicate = (count, elem) => Array.from( | ||
{ length: count }, | ||
() => elem | ||
); | ||
api.common.replicate = (count, elem) => Array.from( | ||
{ length: count }, | ||
() => elem | ||
); | ||
const zipWith = ( | ||
// Zipping arrays using specific function | ||
fn, // function for zipping elements with index i | ||
...arrays // input arrays | ||
// Element with index i of resulting array is result | ||
// of fn called with arguments from arrays | ||
) => zip(...arrays).map(args => fn(...args)); | ||
api.common.zipWith = ( | ||
// Zipping arrays using specific function | ||
fn, // function for zipping elements with index i | ||
...arrays // input arrays | ||
// Element with index i of resulting array is result | ||
// of fn called with arguments from arrays | ||
) => ( | ||
api.common | ||
.zip(...arrays) | ||
.map(args => fn(...args)) | ||
); | ||
api.common.curryUntil = ( | ||
// Curries function until the condition | ||
condition, // function(argsI, argsParts) returns boolean | ||
// argsI is arguments for i-th currying | ||
// argsParts is array of args given for currying from first to i-th currying | ||
fn, // function which will be curried | ||
...args // arguments for fn | ||
// Returns curried function | ||
) => { | ||
const argsParts = []; | ||
const curryUntil = ( | ||
// Curries function until the condition | ||
condition, // function(argsI, argsParts) returns boolean | ||
// argsI is arguments for i-th currying | ||
// argsParts is array of args given for currying from first to i-th currying | ||
fn, // function which will be curried | ||
...args // arguments for fn | ||
// Returns curried function | ||
) => { | ||
const argsParts = []; | ||
const curryMore = (...argsI) => { | ||
argsParts.push(argsI); | ||
if (condition(argsI, argsParts)) { | ||
const allArgs = [].concat(...argsParts); | ||
return fn(...allArgs); | ||
} | ||
return curryMore; | ||
}; | ||
return curryMore(...args); | ||
const curryMore = (...argsI) => { | ||
argsParts.push(argsI); | ||
if (condition(argsI, argsParts)) { | ||
const allArgs = [].concat(...argsParts); | ||
return fn(...allArgs); | ||
} | ||
return curryMore; | ||
}; | ||
api.common.curryN = ( | ||
// Curry fn count times. | ||
// First curry uses args as arguments for first currying | ||
fn, // curried function | ||
count, // number of times function should be curried | ||
...args // arguments for first currying | ||
// Returns curried count times function | ||
) => { | ||
let i = -1; | ||
const condition = () => (i++, i === count); | ||
return api.common.curryUntil(condition, fn, ...args); | ||
}; | ||
return curryMore(...args); | ||
}; | ||
api.common.curryTwice = fn => api.common.curry(api.common.curry, fn); | ||
const curry = ( | ||
// Curry function | ||
fn, // function | ||
...args // arguments | ||
) => { | ||
let argsTotalCount = 0; | ||
const condition = (argsI) => ( | ||
argsTotalCount += argsI.length, | ||
argsTotalCount >= fn.length | ||
); | ||
return curryUntil(condition, fn, ...args); | ||
}; | ||
api.common.curry = ( | ||
// Curry function | ||
fn, // function | ||
...args // arguments | ||
) => { | ||
let argsTotalCount = 0; | ||
const condition = (argsI) => ( | ||
argsTotalCount += argsI.length, | ||
argsTotalCount >= fn.length | ||
); | ||
return api.common.curryUntil(condition, fn, ...args); | ||
}; | ||
const curryN = ( | ||
// Curry fn count times. | ||
// First curry uses args as arguments for first currying | ||
fn, // curried function | ||
count, // number of times function should be curried | ||
...args // arguments for first currying | ||
// Returns curried count times function | ||
) => { | ||
let i = -1; | ||
const condition = () => (i++, i === count); | ||
return curryUntil(condition, fn, ...args); | ||
}; | ||
api.common.applyArgs = (...args) => fn => fn(...args); | ||
const curryTwice = fn => curry(curry, fn); | ||
api.common.either = ( | ||
// Return first not errored result of fn | ||
fn // function to be called | ||
) => ( | ||
...args // arguments to iterate | ||
) => { | ||
let arg; | ||
let lastError; | ||
for (arg of args) { | ||
try { | ||
return fn(arg); | ||
} catch (error) { | ||
lastError = error; | ||
} | ||
const applyArgs = (...args) => fn => fn(...args); | ||
const either = ( | ||
// Return first not errored result of fn | ||
fn // function to be called | ||
) => ( | ||
...args // arguments to iterate | ||
) => { | ||
let arg, lastError; | ||
for (arg of args) { | ||
try { | ||
return fn(arg); | ||
} catch (error) { | ||
lastError = error; | ||
} | ||
throw lastError; | ||
}; | ||
} | ||
throw lastError; | ||
}; | ||
module.exports = { | ||
omap, | ||
compose, | ||
maybe, | ||
zip, | ||
replicate, | ||
zipWith, | ||
curryUntil, | ||
curryN, | ||
curryTwice, | ||
curry, | ||
applyArgs, | ||
either | ||
}; |
202
lib/id.js
'use strict'; | ||
module.exports = (api) => { | ||
const crypto = require('crypto'); | ||
api.common.longDivModBE = ( | ||
// Divide a long big endian encoded unsigned integer by a small one | ||
// (i.e., not longer than a machine word) in-place and return the remainder | ||
buffer, // Buffer containing a divident | ||
divisor // a divisor as a Number | ||
// Return: the remainder (Number) | ||
) => { | ||
if (divisor === 0) { | ||
throw new Error('Division by zero'); | ||
} | ||
const bytesCount = Buffer.byteLength(buffer); | ||
let remainder = 0; | ||
let i, j, resultByte, byte; | ||
for (i = 0; i < bytesCount; i++) { | ||
byte = buffer[i]; | ||
resultByte = 0; | ||
for (j = 7; j > -1; j--) { | ||
remainder <<= 1; | ||
resultByte <<= 1; | ||
remainder |= (byte & (1 << j)) >> j; | ||
if (remainder >= divisor) { | ||
remainder -= divisor; | ||
resultByte |= 1; | ||
} | ||
const longDivModBE = ( | ||
// Divide a long big endian encoded unsigned integer by a small one | ||
// (i.e., not longer than a machine word) in-place and return the remainder | ||
buffer, // Buffer containing a divident | ||
divisor // a divisor as a Number | ||
// Return: the remainder (Number) | ||
) => { | ||
if (divisor === 0) { | ||
throw new Error('Division by zero'); | ||
} | ||
const bytesCount = Buffer.byteLength(buffer); | ||
let remainder = 0; | ||
let i, j, resultByte, byte; | ||
for (i = 0; i < bytesCount; i++) { | ||
byte = buffer[i]; | ||
resultByte = 0; | ||
for (j = 7; j > -1; j--) { | ||
remainder <<= 1; | ||
resultByte <<= 1; | ||
remainder |= (byte & (1 << j)) >> j; | ||
if (remainder >= divisor) { | ||
remainder -= divisor; | ||
resultByte |= 1; | ||
} | ||
buffer[i] = resultByte; | ||
} | ||
return remainder; | ||
}; | ||
buffer[i] = resultByte; | ||
} | ||
return remainder; | ||
}; | ||
api.common.generateKey = ( | ||
length, // random key length | ||
possible // string of possible characters | ||
) => { | ||
const base = possible.length; | ||
const bitsCount = Math.ceil(Math.log2(Math.pow(base, length + 1) - 1)); | ||
const bytes = api.crypto.randomBytes(Math.ceil(bitsCount / 8)); | ||
let key = ''; | ||
let i, index; | ||
for (i = 0; i < length; i++) { | ||
index = api.common.longDivModBE(bytes, base); | ||
key += possible[index]; | ||
} | ||
return key; | ||
}; | ||
const generateKey = ( | ||
length, // random key length | ||
possible // string of possible characters | ||
) => { | ||
const base = possible.length; | ||
const bitsCount = Math.ceil(Math.log2(Math.pow(base, length + 1) - 1)); | ||
const bytes = crypto.randomBytes(Math.ceil(bitsCount / 8)); | ||
let key = ''; | ||
let i, index; | ||
for (i = 0; i < length; i++) { | ||
index = longDivModBE(bytes, base); | ||
key += possible[index]; | ||
} | ||
return key; | ||
}; | ||
api.common.generateGUID = ( | ||
// Generate an RFC4122-compliant GUID (UUID v4) | ||
) => { | ||
const bytes = api.crypto.randomBytes(128); | ||
const generateGUID = ( | ||
// Generate an RFC4122-compliant GUID (UUID v4) | ||
) => { | ||
const bytes = crypto.randomBytes(128); | ||
bytes[6] &= 0x0F; | ||
bytes[6] |= 0x40; | ||
bytes[8] &= 0x3F; | ||
bytes[8] |= 0x80; | ||
bytes[6] &= 0x0F; | ||
bytes[6] |= 0x40; | ||
bytes[8] &= 0x3F; | ||
bytes[8] |= 0x80; | ||
return [ | ||
bytes.toString('hex', 0, 4), | ||
bytes.toString('hex', 4, 6), | ||
bytes.toString('hex', 6, 8), | ||
bytes.toString('hex', 8, 10), | ||
bytes.toString('hex', 10, 16) | ||
].join('-'); | ||
}; | ||
return [ | ||
bytes.toString('hex', 0, 4), | ||
bytes.toString('hex', 4, 6), | ||
bytes.toString('hex', 6, 8), | ||
bytes.toString('hex', 8, 10), | ||
bytes.toString('hex', 10, 16) | ||
].join('-'); | ||
}; | ||
api.common.generateSID = ( | ||
config // { length, characters, secret } | ||
) => { | ||
const key = api.common.generateKey( | ||
config.length - 4, | ||
config.characters | ||
); | ||
return key + api.common.crcSID(config, key); | ||
}; | ||
const crcSID = ( | ||
config, // { length, characters, secret } | ||
key // key to calculate CRC | ||
) => ( | ||
crypto | ||
.createHash('md5') | ||
.update(key + config.secret) | ||
.digest('hex') | ||
.substring(0, 4) | ||
); | ||
api.common.crcSID = ( | ||
config, // { length, characters, secret } | ||
key // key to calculate CRC | ||
) => ( | ||
api.crypto | ||
.createHash('md5') | ||
.update(key + config.secret) | ||
.digest('hex') | ||
.substring(0, 4) | ||
const generateSID = ( | ||
config // { length, characters, secret } | ||
) => { | ||
const key = generateKey( | ||
config.length - 4, | ||
config.characters | ||
); | ||
return key + crcSID(config, key); | ||
}; | ||
api.common.validateSID = ( | ||
// Validate SID | ||
config, // { length, characters, secret } | ||
sid // session id string | ||
) => { | ||
if (!sid) return false; | ||
const crc = sid.substr(sid.length - 4); | ||
const key = sid.substr(0, sid.length - 4); | ||
return api.common.crcSID(config, key) === crc; | ||
}; | ||
const validateSID = ( | ||
// Validate SID | ||
config, // { length, characters, secret } | ||
sid // session id string | ||
) => { | ||
if (!sid) return false; | ||
const crc = sid.substr(sid.length - 4); | ||
const key = sid.substr(0, sid.length - 4); | ||
return crcSID(config, key) === crc; | ||
}; | ||
const hash = (password, salt) => ( | ||
crypto | ||
.createHmac('sha512', salt) | ||
.update(password) | ||
.digest('hex') | ||
); | ||
api.common.hash = (password, salt) => ( | ||
api.crypto | ||
.createHmac('sha512', salt) | ||
.update(password) | ||
.digest('hex') | ||
); | ||
const validateHash = (hash, password, salt) => ( | ||
hash(password, salt) === hash | ||
); | ||
api.common.validateHash = (hash, password, salt) => ( | ||
api.common.hash(password, salt) === hash | ||
); | ||
module.exports = { | ||
longDivModBE, | ||
generateKey, | ||
generateGUID, | ||
generateSID, | ||
crcSID, | ||
validateSID, | ||
hash, | ||
validateHash | ||
}; |
244
lib/misc.js
'use strict'; | ||
module.exports = (api) => { | ||
const events = require('events'); | ||
api.common.falseness = () => false; | ||
api.common.trueness = () => true; | ||
api.common.emptiness = () => {}; | ||
const { either } = require('./functional'); | ||
api.common.cb = ( | ||
// Wrap callback: call once, not null | ||
callback // function (optional) | ||
// Returns wrapped callback | ||
) => { | ||
let done = false; | ||
const wrap = (...args) => { | ||
if (done) return; | ||
done = true; | ||
callback(...args); | ||
}; | ||
return callback ? wrap : api.common.emptiness; | ||
const falseness = () => false; | ||
const trueness = () => true; | ||
const emptiness = () => {}; | ||
const cb = ( | ||
// Wrap callback: call once, not null | ||
callback // function (optional) | ||
// Returns wrapped callback | ||
) => { | ||
let done = false; | ||
const wrap = (...args) => { | ||
if (done) return; | ||
done = true; | ||
callback(...args); | ||
}; | ||
return callback ? wrap : emptiness; | ||
}; | ||
api.common.cbExtract = ( | ||
// Exctracts callback and wraps it with api.common.cb | ||
// callback is last argument if it's function | ||
// otherwise it's api.common.falseness | ||
args // arguments | ||
// Returns wrapped callback | ||
) => { | ||
const callback = args[args.length - 1]; | ||
if (typeof(callback) !== 'function') return api.common.emptiness; | ||
const cbExtract = ( | ||
// Exctracts callback and wraps it with common.cb | ||
// callback is last argument if it's function | ||
// otherwise it's common.falseness | ||
args // arguments | ||
// Returns wrapped callback | ||
) => { | ||
const callback = args[args.length - 1]; | ||
if (typeof(callback) !== 'function') return emptiness; | ||
args.pop(); | ||
return api.common.cb(callback); | ||
}; | ||
args.pop(); | ||
return cb(callback); | ||
}; | ||
api.common.override = ( | ||
// Override method: save old to `fn.inherited` | ||
obj, // object containing method to override | ||
fn // function, name will be used to find method | ||
// Previous function will be accessible by obj.fnName.inherited | ||
) => { | ||
fn.inherited = obj[fn.name]; | ||
obj[fn.name] = fn; | ||
}; | ||
const override = ( | ||
// Override method: save old to `fn.inherited` | ||
obj, // object containing method to override | ||
fn // function, name will be used to find method | ||
// Previous function will be accessible by obj.fnName.inherited | ||
) => { | ||
fn.inherited = obj[fn.name]; | ||
obj[fn.name] = fn; | ||
}; | ||
api.common.range = ( | ||
// Generate int array from given range | ||
from, // range start | ||
to // range end | ||
// Example: range(1, 5) = [1, 2, 3, 4, 5] | ||
) => { | ||
if (to < from) return []; | ||
const len = to - from + 1; | ||
const range = new Array(len); | ||
let i; | ||
for (i = from; i <= to; i++) { | ||
range[i - from] = i; | ||
} | ||
return range; | ||
}; | ||
const range = ( | ||
// Generate int array from given range | ||
from, // range start | ||
to // range end | ||
// Example: range(1, 5) = [1, 2, 3, 4, 5] | ||
) => { | ||
if (to < from) return []; | ||
const len = to - from + 1; | ||
const range = new Array(len); | ||
let i; | ||
for (i = from; i <= to; i++) { | ||
range[i - from] = i; | ||
} | ||
return range; | ||
}; | ||
api.common.sequence = ( | ||
// Generate int array from sequence syntax | ||
seq, // array | ||
max // optional max | ||
// Examples: | ||
// list: sequence([81, 82, 83]) = [81, 82, 83] | ||
// range from..to: sequence([81,,83]) = [81, 82, 83] | ||
// range from..count: sequence([81, [3]]) = [81, 82, 83] | ||
// range from..max-to: sequence([81, [-2]], 5) = [81, 82, 83] | ||
) => { | ||
const from = seq[0]; | ||
let to = seq[1]; | ||
let res = seq; | ||
if (Array.isArray(to)) { | ||
const count = to[0] < 0 ? max + to[0] : to[0]; | ||
res = api.common.range(from, from + count - 1); | ||
} else if (!to) { | ||
to = seq[2]; | ||
res = api.common.range(from, to); | ||
} | ||
return res; | ||
}; | ||
const sequence = ( | ||
// Generate int array from sequence syntax | ||
seq, // array | ||
max // optional max | ||
// Examples: | ||
// list: sequence([81, 82, 83]) = [81, 82, 83] | ||
// range from..to: sequence([81,,83]) = [81, 82, 83] | ||
// range from..count: sequence([81, [3]]) = [81, 82, 83] | ||
// range from..max-to: sequence([81, [-2]], 5) = [81, 82, 83] | ||
) => { | ||
const from = seq[0]; | ||
let to = seq[1]; | ||
let res = seq; | ||
if (Array.isArray(to)) { | ||
const count = to[0] < 0 ? max + to[0] : to[0]; | ||
res = range(from, from + count - 1); | ||
} else if (!to) { | ||
to = seq[2]; | ||
res = range(from, to); | ||
} | ||
return res; | ||
}; | ||
api.common.random = ( | ||
// Generate random int in given range | ||
min, // range start | ||
max // range end | ||
) => { | ||
if (max === undefined) { | ||
max = min; | ||
min = 0; | ||
} | ||
return min + Math.floor(Math.random() * (max - min + 1)); | ||
}; | ||
const random = ( | ||
// Generate random int in given range | ||
min, // range start | ||
max // range end | ||
) => { | ||
if (max === undefined) { | ||
max = min; | ||
min = 0; | ||
} | ||
return min + Math.floor(Math.random() * (max - min + 1)); | ||
}; | ||
api.common.shuffle = ( | ||
// Shuffle an array | ||
arr // array | ||
) => ( | ||
arr.sort(() => Math.random() - 0.5) | ||
); | ||
const shuffle = ( | ||
// Shuffle an array | ||
arr // array | ||
) => ( | ||
arr.sort(() => Math.random() - 0.5) | ||
); | ||
api.common.eventEmitter = ( | ||
// EventEmitter with wildcard | ||
) => { | ||
const ee = new api.events.EventEmitter(); | ||
const emit = ee.emit; | ||
ee.emit = (...args) => { | ||
const ar = args.slice(0); | ||
ar.unshift('*'); | ||
emit.apply(ee, ar); | ||
emit.apply(ee, args); | ||
}; | ||
return ee; | ||
const eventEmitter = ( | ||
// EventEmitter with wildcard | ||
) => { | ||
const ee = new events.EventEmitter(); | ||
const emit = ee.emit; | ||
ee.emit = (...args) => { | ||
const ar = args.slice(0); | ||
ar.unshift('*'); | ||
emit.apply(ee, ar); | ||
emit.apply(ee, args); | ||
}; | ||
return ee; | ||
}; | ||
api.common.restLeft = ( | ||
// Rest left, transfor function | ||
fn // (args, arg1..argN, callback) to (arg1..argN, ...args, callback) | ||
) => (...spreadArgs) => { | ||
const callback = api.common.cbExtract(spreadArgs); | ||
const namedArgsCount = fn.length - 2; | ||
const namedArgs = spreadArgs.slice(0, namedArgsCount); | ||
const args = spreadArgs.slice(namedArgsCount); | ||
fn(args, ...namedArgs, callback); | ||
}; | ||
const restLeft = ( | ||
// Rest left, transfor function | ||
fn // (args, arg1..argN, callback) to (arg1..argN, ...args, callback) | ||
) => (...spreadArgs) => { | ||
const callback = cbExtract(spreadArgs); | ||
const namedArgsCount = fn.length - 2; | ||
const namedArgs = spreadArgs.slice(0, namedArgsCount); | ||
const args = spreadArgs.slice(namedArgsCount); | ||
fn(args, ...namedArgs, callback); | ||
}; | ||
api.common.requireEither = api.common.either(require); | ||
const requireEither = either(require); | ||
module.exports = { | ||
falseness, | ||
trueness, | ||
emptiness, | ||
cb, | ||
cbExtract, | ||
override, | ||
range, | ||
sequence, | ||
random, | ||
shuffle, | ||
eventEmitter, | ||
restLeft, | ||
requireEither | ||
}; |
'use strict'; | ||
module.exports = (api) => { | ||
const os = require('os'); | ||
api.common.ip2int = ( | ||
// Pack IP string to single int | ||
ip = '127.0.0.1' | ||
) => ( | ||
ip.split('.').reduce((res, item) => (res << 8) + (+item), 0) | ||
); | ||
const ip2int = ( | ||
// Pack IP string to single int | ||
ip = '127.0.0.1' | ||
) => ( | ||
ip.split('.').reduce((res, item) => (res << 8) + (+item), 0) | ||
); | ||
api.common.localIPs = ( | ||
// Get local network interfaces | ||
// Returns array of strings | ||
) => { | ||
let ips = api.common.localIPs.cache; | ||
if (ips) return ips; | ||
ips = []; | ||
let protocol, ifName, ifItem, i, len; | ||
const ifHash = api.os.networkInterfaces(); | ||
for (ifName in ifHash) { | ||
ifItem = ifHash[ifName]; | ||
for (i = 0, len = ifItem.length; i < len; i++) { | ||
protocol = ifItem[i]; | ||
if (protocol.family === 'IPv4') { | ||
ips.push(protocol.address); | ||
} | ||
let LOCAL_IPS_CACHE; | ||
const localIPs = ( | ||
// Get local network interfaces | ||
// Returns array of strings | ||
) => { | ||
if (LOCAL_IPS_CACHE) return LOCAL_IPS_CACHE; | ||
const ips = []; | ||
let protocol, ifName, ifItem, i, len; | ||
const ifHash = os.networkInterfaces(); | ||
for (ifName in ifHash) { | ||
ifItem = ifHash[ifName]; | ||
for (i = 0, len = ifItem.length; i < len; i++) { | ||
protocol = ifItem[i]; | ||
if (protocol.family === 'IPv4') { | ||
ips.push(protocol.address); | ||
} | ||
} | ||
api.common.localIPs.cache = ips; | ||
return ips; | ||
}; | ||
} | ||
LOCAL_IPS_CACHE = ips; | ||
return ips; | ||
}; | ||
api.common.parseHost = ( | ||
// Parse host string | ||
host // host or empty string, may contain :port | ||
// Returns host without port but not empty | ||
) => { | ||
if (!host) host = 'no-host-name-in-http-headers'; | ||
const portOffset = host.indexOf(':'); | ||
if (portOffset > -1) host = host.substr(0, portOffset); | ||
return host; | ||
}; | ||
const parseHost = ( | ||
// Parse host string | ||
host // host or empty string, may contain :port | ||
// Returns host without port but not empty | ||
) => { | ||
if (!host) host = 'no-host-name-in-http-headers'; | ||
const portOffset = host.indexOf(':'); | ||
if (portOffset > -1) host = host.substr(0, portOffset); | ||
return host; | ||
}; | ||
module.exports = { | ||
ip2int, | ||
localIPs, | ||
parseHost | ||
}; |
'use strict'; | ||
module.exports = (api) => { | ||
const sortComparePriority = ( | ||
// Compare for array.sort with priority | ||
priority, // array of strings with priority | ||
s1, s2 // comparing strings | ||
// Example: | ||
// comp = sortComparePriority(impress.CONFIG_FILES_PRIORITY); | ||
// files.sort(comp); | ||
) => { | ||
let a = priority.indexOf(s1); | ||
let b = priority.indexOf(s2); | ||
if (a === -1) a = Infinity; | ||
if (b === -1) b = Infinity; | ||
if (a < b) return -1; | ||
if (a > b) return 1; | ||
return 0; | ||
}; | ||
api.common.sortComparePriority = ( | ||
// Compare for array.sort with priority | ||
priority, // array of strings with priority | ||
s1, s2 // comparing strings | ||
// Example: | ||
// comp = api.common.sortComparePriority(impress.CONFIG_FILES_PRIORITY); | ||
// files.sort(comp); | ||
) => { | ||
let a = priority.indexOf(s1); | ||
let b = priority.indexOf(s2); | ||
if (a === -1) a = Infinity; | ||
if (b === -1) b = Infinity; | ||
if (a < b) return -1; | ||
if (a > b) return 1; | ||
return 0; | ||
}; | ||
const sortCompareDirectories = ( | ||
// Compare for array.sort, directories first | ||
a, b // comparing strings | ||
// Example: files.sort(sortCompareDirectories); | ||
) => { | ||
let s1 = a.name; | ||
let s2 = b.name; | ||
if (s1.charAt(0) !== '/') s1 = '0' + s1; | ||
if (s2.charAt(0) !== '/') s2 = '0' + s2; | ||
if (s1 < s2) return -1; | ||
if (s1 > s2) return 1; | ||
return 0; | ||
}; | ||
api.common.sortCompareDirectories = ( | ||
// Compare for array.sort, directories first | ||
a, b // comparing strings | ||
// Example: files.sort(api.common.sortCompareDirectories); | ||
) => { | ||
let s1 = a.name; | ||
let s2 = b.name; | ||
if (s1.charAt(0) !== '/') s1 = '0' + s1; | ||
if (s2.charAt(0) !== '/') s2 = '0' + s2; | ||
if (s1 < s2) return -1; | ||
if (s1 > s2) return 1; | ||
return 0; | ||
}; | ||
const sortCompareByName = ( | ||
// Compare for array.sort | ||
a, b // objects `{ name }` to compare | ||
// Example: files.sort(sortCompareByName); | ||
) => { | ||
const s1 = a.name; | ||
const s2 = b.name; | ||
if (s1 < s2) return -1; | ||
if (s1 > s2) return 1; | ||
return 0; | ||
}; | ||
api.common.sortCompareByName = ( | ||
// Compare for array.sort | ||
a, b // objects `{ name }` to compare | ||
// Example: files.sort(api.common.sortCompareByName); | ||
) => { | ||
const s1 = a.name; | ||
const s2 = b.name; | ||
if (s1 < s2) return -1; | ||
if (s1 > s2) return 1; | ||
return 0; | ||
}; | ||
module.exports = { | ||
sortComparePriority, | ||
sortCompareDirectories, | ||
sortCompareByName | ||
}; |
'use strict'; | ||
module.exports = (api) => { | ||
const path = require('path'); | ||
const SUBST_REGEXP = /@([-.0-9a-zA-Z]+)@/g; | ||
const { getByPath } = require('./data'); | ||
const { nowDateTime } = require('./time'); | ||
api.common.subst = ( | ||
// Substitute variables | ||
tpl, // template body | ||
data, // global data structure to visualize | ||
dataPath, // current position in data structure | ||
escapeHtml // escape html special characters if true | ||
// Returns string | ||
) => ( | ||
tpl.replace(SUBST_REGEXP, (s, key) => { | ||
const pos = key.indexOf('.'); | ||
const name = pos === 0 ? dataPath + key : key; | ||
let value = api.common.getByPath(data, name); | ||
if (value === undefined) { | ||
if (key === '.value') { | ||
value = api.common.getByPath(data, dataPath); | ||
} else { | ||
value = '[undefined]'; | ||
} | ||
} | ||
if (value === null) { | ||
value = '[null]'; | ||
} else if (value === undefined) { | ||
const HTML_ESCAPE_REGEXP = new RegExp('[&<>"\'/]', 'g'); | ||
const HTML_ESCAPE_CHARS = { | ||
'&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' | ||
}; | ||
const htmlEscape = ( | ||
// Escape html characters | ||
content // string to escape | ||
// Example: htmlEscape('5>=5') : '5<=5' | ||
) => ( | ||
content.replace(HTML_ESCAPE_REGEXP, char => HTML_ESCAPE_CHARS[char]) | ||
); | ||
const SUBST_REGEXP = /@([-.0-9a-zA-Z]+)@/g; | ||
const subst = ( | ||
// Substitute variables | ||
tpl, // template body | ||
data, // global data structure to visualize | ||
dataPath, // current position in data structure | ||
escapeHtml // escape html special characters if true | ||
// Returns string | ||
) => ( | ||
tpl.replace(SUBST_REGEXP, (s, key) => { | ||
const pos = key.indexOf('.'); | ||
const name = pos === 0 ? dataPath + key : key; | ||
let value = getByPath(data, name); | ||
if (value === undefined) { | ||
if (key === '.value') { | ||
value = getByPath(data, dataPath); | ||
} else { | ||
value = '[undefined]'; | ||
} else if (typeof(value) === 'object') { | ||
if (value.constructor.name === 'Date') { | ||
value = api.common.nowDateTime(value); | ||
} else if (value.constructor.name === 'Array') { | ||
value = '[array]'; | ||
} else { | ||
value = '[object]'; | ||
} | ||
} | ||
if (escapeHtml) value = api.common.htmlEscape(value); | ||
return value; | ||
}) | ||
); | ||
} | ||
if (value === null) { | ||
value = '[null]'; | ||
} else if (value === undefined) { | ||
value = '[undefined]'; | ||
} else if (typeof(value) === 'object') { | ||
if (value.constructor.name === 'Date') { | ||
value = nowDateTime(value); | ||
} else if (value.constructor.name === 'Array') { | ||
value = '[array]'; | ||
} else { | ||
value = '[object]'; | ||
} | ||
} | ||
if (escapeHtml) value = htmlEscape(value); | ||
return value; | ||
}) | ||
); | ||
const HTML_ESCAPE_REGEXP = new RegExp('[&<>"\'/]', 'g'); | ||
const HTML_ESCAPE_CHARS = { | ||
'&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' | ||
}; | ||
const fileExt = ( | ||
// Extract file extension in lower case with no dot | ||
fileName // string, file name | ||
// Example: fileExt('/dir/file.txt') | ||
// Result: 'txt' | ||
) => ( | ||
path.extname(fileName).replace('.', '').toLowerCase() | ||
); | ||
api.common.htmlEscape = ( | ||
// Escape html characters | ||
content // string to escape | ||
// Example: api.common.htmlEscape('5>=5') : '5<=5' | ||
) => ( | ||
content.replace(HTML_ESCAPE_REGEXP, char => HTML_ESCAPE_CHARS[char]) | ||
); | ||
const removeExt = ( | ||
// Remove file extension from file name | ||
fileName // string, file name | ||
// Example: fileExt('file.txt') | ||
// Result: 'file' | ||
) => ( | ||
fileName.substr(0, fileName.lastIndexOf('.')) | ||
); | ||
api.common.fileExt = ( | ||
// Extract file extension in lower case with no dot | ||
fileName // string, file name | ||
// Example: api.common.fileExt('/dir/file.txt') | ||
// Result: 'txt' | ||
) => ( | ||
api.path.extname(fileName).replace('.', '').toLowerCase() | ||
); | ||
const CAPITALIZE_REGEXP = /\w+/g; | ||
api.common.removeExt = ( | ||
// Remove file extension from file name | ||
fileName // string, file name | ||
// Example: api.common.fileExt('file.txt') | ||
// Result: 'file' | ||
) => ( | ||
fileName.substr(0, fileName.lastIndexOf('.')) | ||
); | ||
const capitalize = ( | ||
// Capitalize string | ||
s // string | ||
) => ( | ||
s.replace(CAPITALIZE_REGEXP, (word) => ( | ||
word.charAt(0).toUpperCase() + word.substr(1).toLowerCase() | ||
)) | ||
); | ||
const UNDERLINE_REGEXP = /_/g; | ||
const UNDERLINE_REGEXP = /_/g; | ||
api.common.spinalToCamel = ( | ||
// convert spinal case to camel case | ||
name // string | ||
) => ( | ||
name | ||
.replace(UNDERLINE_REGEXP, '-') | ||
.split('-') | ||
.map((part, i) => (i > 0 ? api.common.capitalize(part) : part)) | ||
.join('') | ||
); | ||
const spinalToCamel = ( | ||
// convert spinal case to camel case | ||
name // string | ||
) => ( | ||
name | ||
.replace(UNDERLINE_REGEXP, '-') | ||
.split('-') | ||
.map((part, i) => (i > 0 ? capitalize(part) : part)) | ||
.join('') | ||
); | ||
const ESCAPE_REGEXP_SPECIALS = [ | ||
// order matters for these | ||
'-', '[', ']', | ||
// order doesn't matter for any of these | ||
'/', '{', '}', '(', ')', '*', '+', '?', '.', '\\', '^', '$', '|' | ||
]; | ||
const ESCAPE_REGEXP_SPECIALS = [ | ||
// order matters for these | ||
'-', '[', ']', | ||
// order doesn't matter for any of these | ||
'/', '{', '}', '(', ')', '*', '+', '?', '.', '\\', '^', '$', '|' | ||
]; | ||
const ESCAPE_REGEXP = new RegExp( | ||
'[' + ESCAPE_REGEXP_SPECIALS.join('\\') + ']', 'g' | ||
); | ||
const ESCAPE_REGEXP = new RegExp( | ||
'[' + ESCAPE_REGEXP_SPECIALS.join('\\') + ']', 'g' | ||
); | ||
api.common.escapeRegExp = ( | ||
// Escape regular expression control characters | ||
s // string | ||
// Example: escapeRegExp('/path/to/res?search=this.that') | ||
) => ( | ||
s.replace(ESCAPE_REGEXP, '\\$&') | ||
); | ||
const escapeRegExp = ( | ||
// Escape regular expression control characters | ||
s // string | ||
// Example: escapeRegExp('/path/to/res?search=this.that') | ||
) => ( | ||
s.replace(ESCAPE_REGEXP, '\\$&') | ||
); | ||
api.common.newEscapedRegExp = ( | ||
// Generate escaped regular expression | ||
s // string | ||
// Returns: instance of RegExp | ||
) => ( | ||
new RegExp(api.common.escapeRegExp(s), 'g') | ||
); | ||
const newEscapedRegExp = ( | ||
// Generate escaped regular expression | ||
s // string | ||
// Returns: instance of RegExp | ||
) => ( | ||
new RegExp(escapeRegExp(s), 'g') | ||
); | ||
api.common.addTrailingSlash = (s) => s + (s.endsWith('/') ? '' : '/'); | ||
const addTrailingSlash = (s) => s + (s.endsWith('/') ? '' : '/'); | ||
api.common.stripTrailingSlash = ( | ||
// Remove trailing slash from string | ||
s // string | ||
) => ( | ||
s.endsWith('/') ? s.substr(0, s.length - 1) : s | ||
); | ||
const stripTrailingSlash = ( | ||
// Remove trailing slash from string | ||
s // string | ||
) => ( | ||
s.endsWith('/') ? s.substr(0, s.length - 1) : s | ||
); | ||
api.common.dirname = ( | ||
// Get directory name with trailing slash from path | ||
path // string | ||
) => { | ||
let dir = api.path.dirname(path); | ||
if (dir !== '/') dir += '/'; | ||
return dir; | ||
}; | ||
const dirname = ( | ||
// Get directory name with trailing slash from path | ||
path // string | ||
) => { | ||
let dir = path.dirname(path); | ||
if (dir !== '/') dir += '/'; | ||
return dir; | ||
}; | ||
const CAPITALIZE_REGEXP = /\w+/g; | ||
const between = ( | ||
// Extract substring between prefix and suffix | ||
s, // source string | ||
prefix, // substring before needed fragment | ||
suffix // substring after needed fragment | ||
) => { | ||
let i = s.indexOf(prefix); | ||
if (i === -1) return ''; | ||
s = s.substring(i + prefix.length); | ||
if (suffix) { | ||
i = s.indexOf(suffix); | ||
if (i === -1) return ''; | ||
s = s.substring(0, i); | ||
} | ||
return s; | ||
}; | ||
api.common.capitalize = ( | ||
// Capitalize string | ||
s // string | ||
) => ( | ||
s.replace(CAPITALIZE_REGEXP, (word) => ( | ||
word.charAt(0).toUpperCase() + word.substr(1).toLowerCase() | ||
)) | ||
); | ||
const BOM_REGEXP = /^[\uBBBF\uFEFF]*/; | ||
api.common.between = ( | ||
// Extract substring between prefix and suffix | ||
s, // source string | ||
prefix, // substring before needed fragment | ||
suffix // substring after needed fragment | ||
) => { | ||
let i = s.indexOf(prefix); | ||
if (i === -1) return ''; | ||
s = s.substring(i + prefix.length); | ||
if (suffix) { | ||
i = s.indexOf(suffix); | ||
if (i === -1) return ''; | ||
s = s.substring(0, i); | ||
} | ||
return s; | ||
}; | ||
const removeBOM = ( | ||
// Remove UTF-8 BOM | ||
s // string possibly starts with BOM | ||
) => ( | ||
typeof(s) === 'string' ? s.replace(BOM_REGEXP, '') : s | ||
); | ||
const BOM_REGEXP = /^[\uBBBF\uFEFF]*/; | ||
const ITEM_ESCAPE_REGEXP = /\\\*/g; | ||
api.common.removeBOM = ( | ||
// Remove UTF-8 BOM | ||
s // string possibly starts with BOM | ||
) => ( | ||
typeof(s) === 'string' ? s.replace(BOM_REGEXP, '') : s | ||
const arrayRegExp = ( | ||
// Generate RegExp from array with '*' wildcards | ||
items // array of strings | ||
// Example: ['/css/*', '/index.html'] | ||
) => { | ||
if (!items || items.length === 0) return null; | ||
items = items.map( | ||
item => escapeRegExp(item).replace(ITEM_ESCAPE_REGEXP, '.*') | ||
); | ||
const ex = items.length === 1 ? items[0] : '((' + items.join(')|(') + '))'; | ||
return new RegExp('^' + ex + '$'); | ||
}; | ||
const ITEM_ESCAPE_REGEXP = /\\\*/g; | ||
api.common.arrayRegExp = ( | ||
// Generate RegExp from array with '*' wildcards | ||
items // array of strings | ||
// Example: ['/css/*', '/index.html'] | ||
) => { | ||
if (!items || items.length === 0) return null; | ||
items = items.map( | ||
item => api.common.escapeRegExp(item).replace(ITEM_ESCAPE_REGEXP, '.*') | ||
); | ||
const ex = items.length === 1 ? items[0] : '((' + items.join(')|(') + '))'; | ||
return new RegExp('^' + ex + '$'); | ||
}; | ||
module.exports = { | ||
subst, | ||
htmlEscape, | ||
fileExt, | ||
removeExt, | ||
spinalToCamel, | ||
escapeRegExp, | ||
newEscapedRegExp, | ||
addTrailingSlash, | ||
stripTrailingSlash, | ||
dirname, | ||
capitalize, | ||
between, | ||
removeBOM, | ||
arrayRegExp | ||
}; |
'use strict'; | ||
module.exports = (api) => { | ||
const isTimeEqual = ( | ||
// Compare time1 and time2 | ||
time1, // time string or milliseconds | ||
time2 // time string or milliseconds | ||
// Example: isTimeEqual(sinceTime, buffer.stats.mtime); | ||
// Result: boolean | ||
) => ( | ||
new Date(time1).getTime() === new Date(time2).getTime() | ||
); | ||
api.common.isTimeEqual = ( | ||
// Compare time1 and time2 | ||
time1, // time string or milliseconds | ||
time2 // time string or milliseconds | ||
// Example: api.common.isTimeEqual(sinceTime, buffer.stats.mtime); | ||
// Result: boolean | ||
) => ( | ||
new Date(time1).getTime() === new Date(time2).getTime() | ||
const pad2 = n => (n < 10 ? '0' + n : '' + n); | ||
const nowDate = ( | ||
// Current date in YYYY-MM-DD format | ||
now // date object (optional) | ||
// Returns string | ||
) => { | ||
if (!now) now = new Date(); | ||
return ( | ||
now.getUTCFullYear() + '-' + | ||
pad2(now.getUTCMonth() + 1) + '-' + | ||
pad2(now.getUTCDate()) | ||
); | ||
}; | ||
const pad2 = n => (n < 10 ? '0' + n : '' + n); | ||
const nowDateTime = ( | ||
// Current date in YYYY-MM-DD hh:mm format | ||
now // date object (optional) | ||
// Returns string | ||
) => { | ||
if (!now) now = new Date(); | ||
return ( | ||
now.getUTCFullYear() + '-' + | ||
pad2(now.getUTCMonth() + 1) + '-' + | ||
pad2(now.getUTCDate()) + ' ' + | ||
pad2(now.getUTCHours()) + ':' + | ||
pad2(now.getUTCMinutes()) | ||
); | ||
}; | ||
api.common.nowDate = ( | ||
// Current date in YYYY-MM-DD format | ||
now // date object (optional) | ||
// Returns string | ||
) => { | ||
if (!now) now = new Date(); | ||
return ( | ||
now.getUTCFullYear() + '-' + | ||
pad2(now.getUTCMonth() + 1) + '-' + | ||
pad2(now.getUTCDate()) | ||
); | ||
}; | ||
api.common.nowDateTime = ( | ||
// Current date in YYYY-MM-DD hh:mm format | ||
now // date object (optional) | ||
// Returns string | ||
) => { | ||
if (!now) now = new Date(); | ||
return ( | ||
now.getUTCFullYear() + '-' + | ||
pad2(now.getUTCMonth() + 1) + '-' + | ||
pad2(now.getUTCDate()) + ' ' + | ||
pad2(now.getUTCHours()) + ':' + | ||
pad2(now.getUTCMinutes()) | ||
); | ||
}; | ||
module.exports = { | ||
isTimeEqual, | ||
pad2, | ||
nowDate, | ||
nowDateTime | ||
}; |
140
lib/units.js
'use strict'; | ||
module.exports = (api) => { | ||
const DURATION_UNITS = { | ||
days: { rx: /(\d+)\s*d/, mul: 86400 }, | ||
hours: { rx: /(\d+)\s*h/, mul: 3600 }, | ||
minutes: { rx: /(\d+)\s*m/, mul: 60 }, | ||
seconds: { rx: /(\d+)\s*s/, mul: 1 } | ||
}; | ||
const DURATION_UNITS = { | ||
days: { rx: /(\d+)\s*d/, mul: 86400 }, | ||
hours: { rx: /(\d+)\s*h/, mul: 3600 }, | ||
minutes: { rx: /(\d+)\s*m/, mul: 60 }, | ||
seconds: { rx: /(\d+)\s*s/, mul: 1 } | ||
}; | ||
api.common.duration = ( | ||
// Parse duration to seconds | ||
s // string in duration syntax | ||
// Example: duration('1d 10h 7m 13s') | ||
// Returns milliseconds | ||
) => { | ||
if (typeof(s) === 'number') return s; | ||
let result = 0; | ||
let unit, match, key; | ||
if (typeof(s) === 'string') { | ||
for (key in DURATION_UNITS) { | ||
unit = DURATION_UNITS[key]; | ||
match = s.match(unit.rx); | ||
if (match) result += parseInt(match[1], 10) * unit.mul; | ||
} | ||
const duration = ( | ||
// Parse duration to seconds | ||
s // string in duration syntax | ||
// Example: duration('1d 10h 7m 13s') | ||
// Returns milliseconds | ||
) => { | ||
if (typeof(s) === 'number') return s; | ||
let result = 0; | ||
let unit, match, key; | ||
if (typeof(s) === 'string') { | ||
for (key in DURATION_UNITS) { | ||
unit = DURATION_UNITS[key]; | ||
match = s.match(unit.rx); | ||
if (match) result += parseInt(match[1], 10) * unit.mul; | ||
} | ||
return result * 1000; | ||
}; | ||
} | ||
return result * 1000; | ||
}; | ||
api.common.bytesToSize = ( | ||
// Convert int to string size Kb, Mb, Gb and Tb | ||
bytes // int size | ||
) => { | ||
if (bytes === 0) return '0'; | ||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10); | ||
return ( | ||
Math.round(bytes / Math.pow(1000, i), 2) + | ||
api.common.bytesToSize.sizes[i] | ||
); | ||
}; | ||
const SIZE_UNITS = [ | ||
'', ' Kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb', ' Zb', ' Yb' | ||
]; | ||
api.common.bytesToSize.sizes = [ | ||
'', ' Kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb', ' Zb', ' Yb' | ||
]; | ||
const bytesToSize = ( | ||
// Convert int to string size Kb, Mb, Gb and Tb | ||
bytes // int size | ||
) => { | ||
if (bytes === 0) return '0'; | ||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10); | ||
return Math.round(bytes / Math.pow(1000, i), 2) + SIZE_UNITS[i]; | ||
}; | ||
api.common.sizeToBytes = ( | ||
// Convert string with units to int | ||
size // string size | ||
) => { | ||
if (typeof(size) === 'number') return size; | ||
size = size.toUpperCase(); | ||
let result = 0; | ||
const units = api.common.sizeToBytes.units; | ||
if (typeof(size) === 'string') { | ||
let key, unit, match; | ||
let found = false; | ||
for (key in units) { | ||
unit = units[key]; | ||
match = size.match(unit.rx); | ||
if (match) { | ||
result += parseInt(match[1], 10) * Math.pow(10, unit.pow); | ||
found = true; | ||
} | ||
const UNIT_SIZES = { | ||
yb: { rx: /(\d+)\s*YB/, pow: 24 }, | ||
zb: { rx: /(\d+)\s*ZB/, pow: 21 }, | ||
eb: { rx: /(\d+)\s*EB/, pow: 18 }, | ||
pb: { rx: /(\d+)\s*PB/, pow: 15 }, | ||
tb: { rx: /(\d+)\s*TB/, pow: 12 }, | ||
gb: { rx: /(\d+)\s*GB/, pow: 9 }, | ||
mb: { rx: /(\d+)\s*MB/, pow: 6 }, | ||
kb: { rx: /(\d+)\s*KB/, pow: 3 } | ||
}; | ||
const sizeToBytes = ( | ||
// Convert string with units to int | ||
size // string size | ||
) => { | ||
if (typeof(size) === 'number') return size; | ||
size = size.toUpperCase(); | ||
let result = 0; | ||
if (typeof(size) === 'string') { | ||
let key, unit, match; | ||
let found = false; | ||
for (key in UNIT_SIZES) { | ||
unit = UNIT_SIZES[key]; | ||
match = size.match(unit.rx); | ||
if (match) { | ||
result += parseInt(match[1], 10) * Math.pow(10, unit.pow); | ||
found = true; | ||
} | ||
if (!found) result = parseInt(size, 10); | ||
} | ||
return result; | ||
}; | ||
if (!found) result = parseInt(size, 10); | ||
} | ||
return result; | ||
}; | ||
api.common.sizeToBytes.units = { | ||
yb: { rx: /(\d+)\s*YB/, pow: 24 }, | ||
zb: { rx: /(\d+)\s*ZB/, pow: 21 }, | ||
eb: { rx: /(\d+)\s*EB/, pow: 18 }, | ||
pb: { rx: /(\d+)\s*PB/, pow: 15 }, | ||
tb: { rx: /(\d+)\s*TB/, pow: 12 }, | ||
gb: { rx: /(\d+)\s*GB/, pow: 9 }, | ||
mb: { rx: /(\d+)\s*MB/, pow: 6 }, | ||
kb: { rx: /(\d+)\s*KB/, pow: 3 } | ||
}; | ||
module.exports = { | ||
duration, | ||
bytesToSize, | ||
sizeToBytes | ||
}; |
{ | ||
"name": "metarhia-common", | ||
"version": "0.0.12", | ||
"version": "0.0.13", | ||
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>", | ||
@@ -17,3 +17,3 @@ "description": "Metarhia Common Library", | ||
"type": "git", | ||
"url": "https://github.com/metarhia/Common" | ||
"url": "https://github.com/metarhia/common" | ||
}, | ||
@@ -23,3 +23,3 @@ "main": "./common.js", | ||
"scripts": { | ||
"test": "tap test/*.js && npm run lint", | ||
"test": "eslint . && tap test/*.js", | ||
"code-coverage": "tap test/*.js --100", | ||
@@ -34,12 +34,4 @@ "code-coverage-report": "tap test/*.js --coverage-report=html", | ||
"eslint": "^4.1.1", | ||
"eslint-plugin-metarhia": "0.0.2", | ||
"tap": "^10.7.0" | ||
}, | ||
"jshintConfig": { | ||
"esversion": 6, | ||
"predef": [], | ||
"node": true, | ||
"indent": 2, | ||
"maxcomplexity": 7 | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2
1282
38417