SankeySheets/scripts/Utils.js
2016-10-14 12:10:49 +01:00

259 lines
7.8 KiB
JavaScript

var Utils = (function (ns) {
/**
* create a column label for sheet address, starting at 1 = A, 27 = AA etc..
* @param {number} columnNumber the column number
* @return {string} the address label
*/
ns.columnLabelMaker = function (columnNumber, s) {
s = String.fromCharCode(((columnNumber - 1) % 26) + 'A'.charCodeAt(0)) + (s || '');
return columnNumber > 26 ? columnLabelMaker(Math.floor((columnNumber - 1) / 26), s) : s;
};
/**
* get the stack
* @return {string} the stack trace
*/
ns.errorStack = function (e) {
try {
// throw a fake error
throw new Error(); //x is undefined and will fail under use struct- ths will provoke an error so i can get the call stack
}
catch (err) {
return 'Error:' + e + '\n' + err.stack.split('\n').slice(1).join('\n');
}
};
/**
* get an array of unique values
* @param {[*]} a the array
* @param {function} [func] return true if two items are equal
* @return {[*]} the unique items
*/
ns.unique = function (a, func) {
return a.filter(function (d) {
return a.reduce(function (p, c) {
if ((func && func(d, c)) || (!func && d === c)) {
p++;
}
return p;
}, 0) === 1;
})
};
ns.isSameAs = function (a, b) {
return ns.keyDigest(a) === ns.keyDigest(b);
};
/**
* @param {[*]} arguments unspecified number and type of args
* @return {string} a digest of the arguments to use as a key
*/
ns.keyDigest = function () {
// conver args to an array and digest them
return Utilities.base64Encode(
Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, Array.prototype.slice.call(arguments).map(function (d) {
return (Object(d) === d) ? JSON.stringify(d) : (ns.isUndefined(d) ? 'undefined' : d.toString());
}).join("-")));
};
/**
* this is clone that will really be an extend
* @param {object} cloneThis
* @return {object} a clone
*/
ns.clone = function (cloneThis) {
return ns.vanExtend({}, cloneThis);
}
/**
* recursively extend an object with other objects
* @param {[object]} obs the array of objects to be merged
* @return {object} the extended object
*/
ns.vanMerge = function (obs) {
return (obs || []).reduce(function (p, c) {
return ns.vanExtend(p, c);
}, {});
};
/**
* recursively extend a single obbject with another
* @param {object} result the object to be extended
* @param {object} opt the object to extend by
* @return {object} the extended object
*/
ns.vanExtend = function (result, opt) {
result = result || {};
opt = opt || {};
return Object.keys(opt).reduce(function (p, c) {
// if its an object
if (ns.isVanObject(opt[c])) {
p[c] = ns.vanExtend(p[c], opt[c]);
} else {
p[c] = opt[c];
}
return p;
}, result);
};
/**
* use a default value if undefined
* @param {*} value the value to test
* @param {*} defValue use this one if undefined
* @return {*} the new value
*/
ns.fixDef = function (value, defValue) {
return typeof value === typeof undefined ? defValue : value;
};
/**
* see if something is undefined
* @param {*} value the value to check
* @return {bool} whether it was undefined
*/
ns.isUndefined = function (value) {
return typeof value === typeof undefined;
};
/**
* simple test for an object type
* @param {*} the thing to test
* @return {bool} whether it was an object
*/
ns.isVanObject = function (value) {
return typeof value === "object" && !Array.isArray(value);
};
/**
* recursive rateLimitExpBackoff()
* @param {function} callBack some function to call that might return rate limit exception
* @param {object} options properties as below
* @param {number} [attempts=1] optional the attempt number of this instance - usually only used recursively and not user supplied
* @param {number} [options.sleepFor=750] optional amount of time to sleep for on the first failure in missliseconds
* @param {number} [options.maxAttempts=5] optional maximum number of amounts to try
* @param {boolean} [options.logAttempts=true] log re-attempts to Logger
* @param {function} [options.checker] function to check whether error is retryable
* @param {function} [options.lookahead] function to check response and force retry (passes response,attemprs)
* @return {*} results of the callback
*/
ns.expBackoff = function ( callBack,options,attempts) {
//sleepFor = Math.abs(options.sleepFor ||
options = options || {};
optionsDefault = {
sleepFor: 750,
maxAttempts:5,
checker:errorQualifies,
logAttempts:true
}
// mixin
Object.keys(optionsDefault).forEach(function(k) {
if (!options.hasOwnProperty(k)) {
options[k] = optionsDefault[k];
}
});
// for recursion
attempts = attempts || 1;
// make sure that the checker is really a function
if (typeof(options.checker) !== "function") {
throw ns.errorStack("if you specify a checker it must be a function");
}
// check properly constructed
if (!callBack || typeof(callBack) !== "function") {
throw ns.errorStack("you need to specify a function for rateLimitBackoff to execute");
}
function waitABit (theErr) {
//give up?
if (attempts > options.maxAttempts) {
throw errorStack(theErr + " (tried backing off " + (attempts-1) + " times");
}
else {
// wait for some amount of time based on how many times we've tried plus a small random bit to avoid races
Utilities.sleep (
Math.pow(2,attempts)*options.sleepFor +
Math.round(Math.random() * options.sleepFor)
);
}
}
// try to execute it
try {
var response = callBack(options, attempts);
// maybe not throw an error but is problem nevertheless
if (options.lookahead && options.lookahead(response,attempts)) {
if(options.logAttempts) {
Logger.log("backoff lookahead:" + attempts);
}
waitABit('lookahead:');
return ns.expBackoff ( callBack, options, attempts+1) ;
}
return response;
}
// there was an error
catch(err) {
if(options.logAttempts) {
Logger.log("backoff " + attempts + ":" +err);
}
// failed due to rate limiting?
if (options.checker(err)) {
waitABit(err);
return ns.expBackoff ( callBack, options, attempts+1) ;
}
else {
// some other error
throw ns.errorStack(err);
}
}
}
/**
* get the stack
* @param {Error} e the error
* @return {string} the stack trace
*/
ns.errorStack = function (e) {
try {
// throw a fake error
throw new Error(); //x is undefined and will fail under use struct- ths will provoke an error so i can get the call stack
}
catch(err) {
return 'Error:' + e + '\n' + err.stack.split('\n').slice(1).join('\n');
}
}
// default checker
function errorQualifies (errorText) {
return ["Exception: Service invoked too many times",
"Exception: Rate Limit Exceeded",
"Exception: Quota Error: User Rate Limit Exceeded",
"Service error:",
"Exception: Service error:",
"Exception: User rate limit exceeded",
"Exception: Internal error. Please try again.",
"Exception: Cannot execute AddColumn because another task",
"Service invoked too many times in a short time:",
"Exception: Internal error.",
"User Rate Limit Exceeded",
"Exception: ???????? ?????: DriveApp.",
"Exception: Address unavailable"
]
.some(function(e){
return errorText.toString().slice(0,e.length) == e ;
}) ;
};
return ns;
})(Utils || {});