Files
Zos/Skills/@be/node_modules/jibo-cai-utils/lib/jibo-cai-utils.js

2174 lines
75 KiB
JavaScript

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jiboCaiUtils = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jibo_log_1 = require("jibo-log");
exports.default = new jibo_log_1.Log('CAIUtils');
},{"jibo-log":undefined}],2:[function(require,module,exports){
"use strict";
/**
* Created on 7/20/16.
* @author Tom Donahue <tom.donahue@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
var ActionTimelineState;
(function (ActionTimelineState) {
ActionTimelineState[ActionTimelineState["DISPATCHING"] = 0] = "DISPATCHING";
ActionTimelineState[ActionTimelineState["NOT_DISPATCHING"] = 1] = "NOT_DISPATCHING";
ActionTimelineState[ActionTimelineState["CANCELLED"] = 2] = "CANCELLED";
ActionTimelineState[ActionTimelineState["FAST_FORWARD"] = 3] = "FAST_FORWARD";
})(ActionTimelineState = exports.ActionTimelineState || (exports.ActionTimelineState = {}));
class ActionTimeline {
constructor() {
this._state = ActionTimelineState.NOT_DISPATCHING;
}
/**
* Dispatch a timeline of actions
*
* @param {TimelineElement[]} timeline
* @returns {Promise<void>}
*/
dispatchTimeline(timeline) {
return __awaiter(this, void 0, void 0, function* () {
this._state = ActionTimelineState.DISPATCHING;
let startTime = process.hrtime();
this._promiseChain = Promise.resolve();
timeline.forEach(el => {
this._promiseChain = this._promiseChain
.then(() => this._dispatchTimeline(el, startTime));
});
this._promiseChain = this._promiseChain.then(() => {
this._state = ActionTimelineState.NOT_DISPATCHING;
this._timeoutID = null;
this._next = null;
});
return this._promiseChain;
});
}
_dispatchTimeline(el, startTime) {
return __awaiter(this, void 0, void 0, function* () {
let timeDiff = process.hrtime(startTime);
let passedTimeMs = timeDiff[0] * 1e3 + timeDiff[1] * 1e-6;
let timeToWait = Math.max(0, el.timeMs - passedTimeMs);
return this._wait(timeToWait).then(() => {
if (this._state !== ActionTimelineState.CANCELLED) {
el.action();
}
});
});
}
/**
* Waits for timeMs and then resolves the promise
*
* @param {number} timeMs Time to wait in ms
* @returns {Promise<void>}
*/
_wait(timeMs) {
return new Promise(res => {
this._next = res;
if (this._state === ActionTimelineState.FAST_FORWARD || this._state === ActionTimelineState.CANCELLED) {
this._next();
}
else {
this._timeoutID = setTimeout(res, timeMs);
}
});
}
/**
* Cancel a timeline of actions (remaining actions will not be dispatched)
*
* @returns {void}
*/
cancel() {
return __awaiter(this, void 0, void 0, function* () {
if (this._state === ActionTimelineState.DISPATCHING) {
this._state = ActionTimelineState.CANCELLED;
if (this._timeoutID) {
clearTimeout(this._timeoutID);
this._timeoutID = null;
this._next();
}
return this._promiseChain.then(() => {
this._state = ActionTimelineState.NOT_DISPATCHING;
this._timeoutID = null;
this._next = null;
});
}
});
}
/**
* Fast-forward a timeline (i.e. trigger all remaining actions in sequence immediately)
*
* @returns {void}
*/
fastForward() {
if (this._state === ActionTimelineState.DISPATCHING) {
this._state = ActionTimelineState.FAST_FORWARD;
if (this._timeoutID) {
clearTimeout(this._timeoutID);
this._timeoutID = null;
this._next();
}
}
}
/**
* Get state of ActionTimeline
*
* @returns {ActionTimelineState}
*/
_getState() {
return this._state;
}
}
exports.ActionTimeline = ActionTimeline;
},{}],3:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Manage a set of objects with ids and a cache
* @type {[type]}
*/
class CacheManager {
constructor() {
this.idToObject = new Map();
this.objectToId = new Map();
this.idToCache = new Map();
this.cache = new Map();
}
addObjectToCache(obj, id, cache) {
if (!this.cache.has(cache)) {
this.cache.set(cache, new Set());
}
this.cache.get(cache).add(obj);
this.idToObject.set(id, obj);
this.objectToId.set(obj, id);
this.idToCache.set(id, cache);
}
getById(id) {
return this.idToObject.get(id);
}
removeCache(cache) {
const cacheSet = this.cache.get(cache);
if (!cacheSet) {
return [];
}
this.cache.delete(cache);
const objects = [];
cacheSet.forEach((obj) => {
objects.push(obj);
this.removeByObj(obj);
});
return objects;
}
/**
* Removes both the object and the id from cache, but does not
* delete an entire cache
* @param {IDType} id
*/
removeById(id) {
const obj = this.idToObject.get(id);
this.objectToId.delete(obj);
this.idToObject.delete(id);
let cacheKey = this.idToCache.get(id);
this.idToCache.delete(id);
if (cacheKey !== undefined) {
let setForCache = this.cache.get(cacheKey);
setForCache.delete(obj);
if (setForCache.size === 0) {
this.cache.delete(cacheKey);
}
}
return obj;
}
/**
* This removes the object and id from cache, but does not delete
* an entire cache
* @param obj
*/
removeByObj(obj) {
const id = this.objectToId.get(obj);
this.objectToId.delete(obj);
this.idToObject.delete(id);
let cacheKey = this.idToCache.get(id);
this.idToCache.delete(id);
if (cacheKey !== undefined) {
let setForCache = this.cache.get(cacheKey);
if (setForCache !== undefined) {
setForCache.delete(obj);
if (setForCache.size === 0) {
this.cache.delete(cacheKey);
}
}
}
}
}
exports.CacheManager = CacheManager;
},{}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class CacheUtils {
/**
* Ensures that the global cache exists for jibo.loader.
* @param {Jibo} jibo The Jibo Runtime instance on which to ensure global cache is initted.
*/
static initGlobalCache(jibo) {
// Set up global cache if it doesn't exist
jibo.loader.addCache(CacheUtils.GlobalCacheName);
CacheUtils.initialized = true;
}
}
/**
* Name of global cache
* @type {string}
*/
CacheUtils.GlobalCacheName = 'global';
/**
* If the global cache has been initialized.
* @type {boolean}
*/
CacheUtils.initialized = false;
exports.CacheUtils = CacheUtils;
},{}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
exports.CancelTokenEvents = {
UNPAUSED: 'UNPAUSED',
ALL_DONE: 'ALL_DONE',
CANCEL_STARTED: 'CANCEL_STARTED',
CANCEL_FINISHED: 'CANCEL_FINISHED',
};
/**
* @description Represests
* @class CancelToken
*/
class CancelToken {
/**
* Creates an instance of CancelToken.
* @param {Promise<T>} wrappedPromise - The promise to wrap
* @param {CancelHandler} [onCancel] - Handler function for cancel events
* @memberOf CancelToken
*/
constructor(wrappedPromise, onCancel) {
this._paused = false;
this._canceled = false;
this._emitter = new events.EventEmitter();
this._wrappedPromise = wrappedPromise;
this._onCancel = onCancel;
this.promise = new Promise((resolve, reject) => {
const done = (error, data) => {
this._emitter.removeAllListeners();
if (error) {
reject(error);
}
else {
resolve(data);
}
};
wrappedPromise.then((data) => {
if (this._paused) {
this._emitter.once(exports.CancelTokenEvents.UNPAUSED, () => done(null, data));
}
else if (!this._canceled) {
done(null, data);
}
}).catch(err => {
if (this._paused) {
this._emitter.once(exports.CancelTokenEvents.UNPAUSED, () => done(err));
}
else if (!this._canceled) {
done(err);
}
});
});
}
/**
* Returns the promise this token created wrapping the internal promise
* @returns {Promise<any>} The internal wrapped promise
* @memberOf CancelToken
*/
getTokenPromise() {
return this.promise;
}
/**
* Returns the internal promise that this cancel token wraps
* @returns {Promise<any>} The internal wrapped promise
* @memberOf CancelToken
*/
getWrappedPromise() {
return this._wrappedPromise;
}
/**
* Sets the paused status of this promise session
* @param {boolean} paused
* @memberOf PromiseSession
*/
setPaused(paused) {
const oldPaused = this._paused;
this._paused = paused;
if (oldPaused && !paused) {
this._emitter.emit(exports.CancelTokenEvents.UNPAUSED);
}
}
/**
* Returns the paused status of this promise session
* @returns {boolean}
* @memberOf PromiseSession
*/
isPaused() {
return this._paused;
}
/**
* Is this token canceled
* @returns {boolean}
* @memberOf CancelToken
*/
isCanceled() {
return this._canceled;
}
/**
* Cancels the current session. Session should not be reused after calling cancel
* @memberOf PromiseSession
*/
cancel() {
this._canceled = true;
if (this._onCancel) {
this._onCancel();
}
}
}
exports.CancelToken = CancelToken;
},{"events":undefined}],6:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
const CancelToken_1 = require("./CancelToken");
/**
* @description A class that represents a session for a set of promise tokens
* @class PromiseSession
*/
class CancelTokenSession {
constructor() {
this._outstanding = new Set();
this._emitter = new events.EventEmitter();
this._isCanceling = false;
}
/**
* Returns true / false whether or not the session is in the process of canceling
* @returns {boolean} true if the session is canceling. false otherwise
* @memberOf CancelTokenSession
*/
isCanceling() {
return this._isCanceling;
}
/**
* Pauses all
* @param {boolean} paused
* @memberOf CancelTokenSession
*/
setPaused(paused) {
this._outstanding.forEach(token => token.setPaused(paused));
}
/**
* Cancels the current session. Session should not be reused after calling cancel
* @returns {Promise<any>} resolved when all internal promises have resolved
* @memberOf CancelTokenSession
*/
cancel() {
this._isCanceling = true;
let wrappedPromises = [];
this._outstanding.forEach(token => {
token.cancel();
wrappedPromises.push(token.getWrappedPromise());
});
this._outstanding.clear();
this._emitter.emit(CancelToken_1.CancelTokenEvents.CANCEL_STARTED);
const done = () => {
this._emitter.emit(CancelToken_1.CancelTokenEvents.CANCEL_FINISHED);
this._isCanceling = false;
this.clear();
};
// We wait for all wrapped promises to resolve
return Promise.all(wrappedPromises).then(() => {
done();
}).catch(e => {
done();
throw e;
});
}
/**
* Number of outstanding promises
* @returns {number}
* @memberOf CancelTokenSession
*/
size() {
return this._outstanding.size;
}
/**
* Waits for all outstanding promises to complete
* This refers to the internal promises, not the wrapped promises that were returned
* This promise is rejected immediately when the session gets canceled
* @param {boolean} [rejectImmediatelyOnCancel=true] - Whether we should reject immediately when
* canceled or when the cancel promise is resolved.
* @returns {Promise<void>}
* @memberOf CancelTokenSession
*/
waitForAll(rejectImmediatelyOnCancel = true) {
if (this._outstanding.size === 0) {
return Promise.resolve();
}
else {
return new Promise((resolve, reject) => {
let allDoneHandler = () => done();
let canceledHandler = () => done(new Error(`Promise session canceled`));
// When done, either with error or no error, we clean up listeners and resolve/reject
const done = (error) => {
this._emitter.removeListener(CancelToken_1.CancelTokenEvents.ALL_DONE, allDoneHandler);
this._emitter.removeListener(CancelToken_1.CancelTokenEvents.CANCEL_STARTED, canceledHandler);
this._emitter.removeListener(CancelToken_1.CancelTokenEvents.CANCEL_FINISHED, canceledHandler);
if (error) {
reject(error);
}
else {
resolve();
}
};
// We wait for either of these events
this._emitter.once(CancelToken_1.CancelTokenEvents.ALL_DONE, allDoneHandler);
if (rejectImmediatelyOnCancel) {
this._emitter.once(CancelToken_1.CancelTokenEvents.CANCEL_STARTED, canceledHandler);
}
else {
this._emitter.once(CancelToken_1.CancelTokenEvents.CANCEL_FINISHED, canceledHandler);
}
}).then(() => {
this.clear();
});
}
}
/**
* Wraps a promise with a token and adds to this session
* @param {Promise<T>} promise
* @returns {Promise<T>}
* @memberOf CancelTokenSession
*/
wrap(promise) {
const token = new CancelToken_1.CancelToken(promise);
return this.addToken(token);
}
wrapCallback(callback, noErrorParam = false) {
let externalCallback;
const token = new CancelToken_1.CancelToken(new Promise((resolve, reject) => {
externalCallback = (error, data) => {
const args = Array.prototype.slice.call(arguments);
if (noErrorParam) {
// If we are in a non-standard callback
resolve(args);
}
else {
// If we are in a standard callback
if (error) {
reject(error);
}
else {
// Remove first argument in arguments list (since it's a null error)
args.splice(0, 1);
resolve(args);
}
}
};
}));
token.promise = token.promise
.then(data => {
callback(null, ...data);
}).catch(error => {
callback(error);
});
this.addToken(token);
return externalCallback;
}
/**
* Adds a cancel token to this session
* @template T
* @param {CancelToken} token
* @memberOf CancelTokenSession
*/
addToken(token) {
if (this._isCanceling) {
throw new Error(`Can't add new token when session is canceling`);
}
this._outstanding.add(token);
// We remove the token from the list out outstanding promises regardless of outcome
return token.promise
.then((data) => {
this._removeOutstanding(token);
return data; // Pass data forward to the caller
})
.catch(error => {
this._removeOutstanding(token);
throw error; // Allows caller code to handle error
});
}
/**
* Clears the current state of the promise session
*/
clear() {
this._outstanding.clear();
this._emitter.removeAllListeners();
this._isCanceling = false;
}
_removeOutstanding(token) {
this._outstanding.delete(token);
if (this._outstanding.size === 0) {
this._emitter.emit(CancelToken_1.CancelTokenEvents.ALL_DONE);
}
}
}
exports.CancelTokenSession = CancelTokenSession;
},{"./CancelToken":5,"events":undefined}],7:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class CircularBuffer {
/**
* Creates an instance of CircularBuffer.
* The circular buffer can contain up to 'capacity' elements
* and will overwrite oldest element upon insertion when at capacity
* @param {number} capacity - Capacity of this circular buffer
*
* @memberof CircularBuffer
*/
constructor(capacity) {
if (capacity < 0) {
throw new Error(`Capacity must be greater than or equal to 0`);
}
this._capacity = capacity;
this.clear();
}
/**
* Clears the buffer
* @memberof CircularBuffer
*/
clear() {
this._size = 0;
this._tail = 0;
this._data = [];
}
/**
* Returns the current number of elements in buffer, will never exceed capacity
* @returns {number}
* @memberof CircularBuffer
*/
size() {
return this._size;
}
/**
* Returns the capacity of this buffer.
* @returns {number}
* @memberof CircularBuffer
*/
capacity() {
return this._capacity;
}
/**
* Adds an element to the buffer. If the size of the buffer is at capacity
* then the element will overwrite the oldest element
* @param {T} element
* @memberof CircularBuffer
*/
add(element) {
const index = (this._tail + this._size) % this._capacity;
this._data[index] = element;
if (this._size === this._capacity) {
this._incrementTail();
}
else {
this._size++;
}
}
/**
* Gets an element from the buffer at an index.
* index 0 is the first element
* index (this.size() - 1) is the last element
* @param {number} index
* @returns {T}
* @memberof CircularBuffer
*/
get(index) {
if (index < 0 || index >= this._size) {
throw Error(`Index needs to be >= 0 and < ${this._size}`);
}
const indToUse = (this._tail + index) % this._capacity;
return this._data[indToUse];
}
/**
* Removes the first (oldest) element in the buffer. Returns the element that was removed
* @returns {T}
* @memberof CircularBuffer
*/
removeTail() {
if (this._size === 0) {
throw Error(`Buffer is empty`);
}
const el = this.get(0);
this._incrementTail();
this._size--;
return el;
}
/**
* Removes the last (most recently added) element in the buffer. Returns the element that was removed
* @returns {T}
* @memberof CircularBuffer
*/
removeHead() {
if (this._size === 0) {
throw Error(`Buffer is empty`);
}
const el = this.get(this._size - 1);
this._size--;
return el;
}
/**
* Returns an array representation of the buffer.
* Copies all elements in order into an array
* @returns {T[]}
* @memberof CircularBuffer
*/
toArray() {
let out = new Array(this._size);
for (let i = 0; i < this._size; i++) {
out[i] = this.get(i);
}
return out;
}
_incrementTail() {
this._tail = (this._tail + 1) % this._capacity;
}
}
exports.CircularBuffer = CircularBuffer;
},{}],8:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* A promise wrapper that exposes the resolve and reject methods of a promise
*/
class ExtPromiseWrapper {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
exports.ExtPromiseWrapper = ExtPromiseWrapper;
},{}],9:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 5/12/16.
* @author Siggi Orn <siggi@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const asyncParallel = require("async-parallel");
const PromiseUtils_1 = require("./PromiseUtils");
const TestUtils_1 = require("./TestUtils");
const PathUtils_1 = require("./PathUtils");
const log_1 = require("../log");
const log = log_1.default.createChild('FileUtils');
const prify = PromiseUtils_1.PromiseUtils.promisify;
let onRobot = fs.existsSync('/var/jibo/identity.json');
class FileUtils {
/**
* Returns the root path of project
* @param {NodeModule|string} pathOrModule Path in project or NodeModule
* @returns {string}
* @deprecated
*/
static getProjectRoot(pathOrModule) {
log.warn(`'FileUtils.getProjectRoot' is deprecated, please use 'PathUtils.getProjectRoot'`);
return PathUtils_1.PathUtils.getProjectRoot(pathOrModule);
}
/**
* Returns the main export of a project, same as you would get if you
* did `let exp = require('project-name');`
* @param {NodeModule|string} pathOrModule Path in project or NodeModule
* @returns {any}
* @deprecated
*/
static getProjectMainExport(pathOrModule) {
log.warn(`'FileUtils.getProjectMainExport' is deprecated, please use 'TestUtils.getProjectMainExport'`);
return TestUtils_1.TestUtils.getProjectMainExport(pathOrModule);
}
/**
* Read file from disk and return promise
* fs.readFile doesn't ensure closing file descriptors and can run
* out of allowed maximum number of open files
* @param {string} filePath Path to file
* @param {string} [encoding='utf8'] Encoding of file
* @param {number} [chunkSize=512] How many bytes to read from file at a time
* @returns {Promise<string>}
*/
static readFile(filePath, encoding = 'utf8', chunkSize = 512) {
return __awaiter(this, void 0, void 0, function* () {
let fd = yield prify(h => fs.open(filePath, 'r', h));
let buffer;
let bufferSize;
try {
const stats = yield prify(h => fs.fstat(fd, h));
bufferSize = stats.size;
buffer = new Buffer(bufferSize);
let bytesRead = 0;
while (bytesRead < bufferSize) {
let chunkSizeToUse = chunkSize;
if ((bytesRead + chunkSize) > bufferSize) {
chunkSizeToUse = (bufferSize - bytesRead);
}
yield prify(h => fs.read(fd, buffer, bytesRead, chunkSizeToUse, bytesRead, h));
bytesRead += chunkSizeToUse;
}
}
catch (error) {
throw error;
}
finally {
yield prify(h => fs.close(fd, h));
}
if (buffer) {
return buffer.toString(encoding, 0, bufferSize);
}
});
}
/**
* Finds all files of a certain extension in directories rooted at a certain
* base directory
* @param {String} baseDir The base directory path of the search
* @param {String} extension The extension of files to find
*/
static findAllFilesWithExt(baseDir, extension) {
let selector = (fileName) => {
let _extension = extension.toLowerCase();
let ext = path.extname(fileName.toLowerCase());
return (ext[0] === '.' && ext.substr(1) === _extension);
};
return FileUtils.findAllFiles(baseDir, selector);
}
/**
* Finds all files of a certain extension in directories rooted at a certain
* base directory
* @param {String} baseDir The base directory path of the search
* @param {Function} selector A function which takes a filename and returns boolean
* @param {number} [maxConcurrent=50] The number of maximum concurrent file system operations
* @return {Promise<string[]>}
*/
static findAllFiles(baseDir, selector, maxConcurrent = 50) {
return __awaiter(this, void 0, void 0, function* () {
let files = [];
yield FileUtils._findAllFiles(baseDir, selector, files, maxConcurrent);
return files;
});
}
static _findAllFiles(baseDir, selector, out, maxConcurrent = 50) {
return __awaiter(this, void 0, void 0, function* () {
let directories = [];
const _fileNames = yield prify(h => fs.readdir(baseDir, h));
// We open a file descriptor for each path
// Each of these will process one file
const filePromiseGenerators = _fileNames.map(f => {
let fullPath = path.resolve(baseDir, f);
return () => {
let fd;
return PromiseUtils_1.PromiseUtils.promisify(h => fs.open(fullPath, 'r', h))
.then(_fd => {
fd = _fd;
// Here we gather stats on the file descriptor
return prify(h => fs.fstat(fd, h));
})
.then(stat => {
// We determine which files are directories and which
// ones comply with our selector
if (stat.isDirectory()) {
directories.push(fullPath);
}
else if (selector(fullPath)) {
out.push(fullPath);
}
// Now we close the file descriptor
return prify(h => fs.close(fd, h));
});
};
});
// Here we chunk all the promise generators into max size chunks
// and process all the chunks in sequence
yield asyncParallel.invoke(filePromiseGenerators, maxConcurrent);
// We don't process directories in parallel since each will be
// opening file desctiptors in parallel
yield asyncParallel.invoke(directories.map(dir => {
return () => FileUtils._findAllFiles(dir, selector, out);
}), 1);
});
}
/**
* Checks to see if we are on the robot or in simulator
*/
static onRobot() {
return onRobot;
}
/**
* Search backward in path hierarchy.
*
* Example1: inputPath: '/root/foo/bar/bloop', name: 'glop.js'
* could output '/root/foo/glop.js' if it existed there, null otherwise
*
* Example2: inputPath: '/root/foo/bar/bloop', name: 'blip.js', nestedDirectories: ['test']
* could output '/root/test/blip.js' if it existed there, null otherwise
*
* @param {string} inputPath Starting point for search
* @param {string} name Folder or file name to find
* @param {string[]} [nestedDirectories] optional nested directories to search into at each level
* @returns {Promise<string|void>} Resolves with path or null if nothing found
*/
static searchBackwardInPath(inputPath, name, nestedDirectories) {
return __awaiter(this, void 0, void 0, function* () {
let pathToUse = path.dirname(inputPath);
let paths = [];
FileUtils._gatherBackwardPaths(pathToUse, paths);
let ind = 0;
let nested = nestedDirectories || [];
return new Promise((resolve, reject) => {
let find = () => {
FileUtils._searchBackward(paths[ind++], name, nested)
.then(result => {
if (result) {
resolve(result);
}
else if (ind < paths.length) {
find();
}
else {
resolve(null);
}
}).catch(reject);
};
find();
});
});
}
static _gatherBackwardPaths(inputPath, collection) {
collection.push(path.resolve(inputPath));
let next = path.resolve(path.join(inputPath, '..'));
if (next === inputPath) {
return;
}
else {
FileUtils._gatherBackwardPaths(next, collection);
}
}
static _searchBackward(inputPath, name, nestedDirectories) {
return __awaiter(this, void 0, void 0, function* () {
let paths = [path.join(inputPath, name)];
nestedDirectories.forEach(dir => paths.push(path.join(inputPath, dir, name)));
let ind = 0;
return new Promise(resolve => {
let find = () => {
let pathToUse = paths[ind++];
fs.exists(pathToUse, (exists) => {
if (exists) {
resolve(pathToUse);
}
else if (ind < paths.length) {
find();
}
else {
resolve(null);
}
});
};
find();
});
});
}
}
exports.FileUtils = FileUtils;
},{"../log":1,"./PathUtils":10,"./PromiseUtils":12,"./TestUtils":17,"async-parallel":undefined,"fs":undefined,"path":undefined}],10:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 9/25/16.
* @author Siggi Orn <siggi@jibo.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
const findRoot = require('find-root');
class PathUtils {
/**
* Returns the root path of project
* @param {NodeModule|string} pathOrModule Path in project or NodeModule
* @returns {string}
*/
static getProjectRoot(pathOrModule) {
if (typeof pathOrModule === 'string') {
return findRoot(pathOrModule);
}
else if (pathOrModule.filename) {
return findRoot(pathOrModule.filename);
}
else {
throw new Error(`Can't find project root, invalid input: '${pathOrModule}'`);
}
}
}
exports.PathUtils = PathUtils;
},{"find-root":undefined}],11:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 4/4/16.
* @author Siggi Orn <siggi@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
var PromiseQueueState;
(function (PromiseQueueState) {
PromiseQueueState[PromiseQueueState["PLAYING"] = 0] = "PLAYING";
PromiseQueueState[PromiseQueueState["NOT_PLAYING"] = 1] = "NOT_PLAYING";
})(PromiseQueueState = exports.PromiseQueueState || (exports.PromiseQueueState = {}));
class PromiseQueue {
constructor() {
this._queueEmptyPr = Promise.resolve();
this._state = PromiseQueueState.NOT_PLAYING;
this._queue = [];
}
/**
* Get the state of the PromiseQueue
*/
getState() {
return this._state;
}
/**
* Add a promise generator
* @param {PromiseProvider} promiseProvider Promise generator
*/
add(promiseProvider) {
this._queue.push(promiseProvider);
if (this._state !== PromiseQueueState.PLAYING) {
this._state = PromiseQueueState.PLAYING;
this._queueEmptyPr = new Promise((resolve, reject) => {
this._resolver = resolve;
this._rejector = reject;
});
this._playNext();
}
}
/**
* Returns a promise that gets resolved either immediately if queue is
* empty or as soon as the queue empties and/or rejects at first rejection of any
* promise in the queue
*
* @returns {Promise}
*/
waitUntilEmpty() {
return __awaiter(this, void 0, void 0, function* () {
return this._queueEmptyPr;
});
}
/**
* Clears current queue.
* If there is currently an outstanding promise then it will be unaffected
* by this method since it is not in the queue.
* Any promise from `waitUntilFinish` will be resolved when currently outstanding promise resolves;
*/
clear() {
this._queue = [];
}
/**
* Picks out next promise in queue and continues until all are resolved or one was rejected
* @private
*/
_playNext() {
if (this._queue.length > 0) {
let next = this._queue.shift();
next()
.then(() => this._playNext())
.catch((e) => {
this._state = PromiseQueueState.NOT_PLAYING;
this._queue = [];
this._rejector(e);
});
}
else {
this._state = PromiseQueueState.NOT_PLAYING;
this._resolver();
}
}
}
exports.PromiseQueue = PromiseQueue;
},{}],12:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 5/12/16.
* @author Siggi Orn <siggi@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const log_1 = require("../log");
const log = log_1.default.createChild('PromiseUtils');
class PromiseUtils {
/**
* Installs a global handler for unhandled rejected promises
* Makes sure to only install one instance.
*/
static catchUnhandledRejection() {
let gl = global;
if (!gl._hasInstalledUncaughtHandler) {
process.on('unhandledRejection', (reason, p) => {
log.error(`Unhandled Rejection at: Promise '${JSON.stringify(p)}'`);
if (reason instanceof Error) {
log.error(` message: ${reason.message}`);
log.error(` stack: ${reason.stack}`);
}
else {
log.error(reason);
}
});
gl._hasInstalledUncaughtHandler = true;
}
}
/**
* Returns promise that succeeds when the first input promise succeeds
* If all input promises fail, then the returned promise fails.
* @param {Promise<T>[]} promises
* @returns {Promise<T>}
*/
static firstToSucceed(promises) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((res, rej) => {
let errors = [];
let errorCount = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(res).catch(e => {
errors[i] = e;
errorCount++;
if (errorCount === promises.length) {
rej(errors);
}
});
}
});
});
}
static promisify(func, firstParamError = true) {
return new Promise((resolve, reject) => {
func((error, result) => {
if (firstParamError) {
if (error) {
reject(error);
}
else {
resolve(result);
}
}
else {
// In this case it is assumed that the data is in the first argument
if (result) {
log.warn(`Data in second argument ignored`);
}
resolve(error);
}
});
});
}
/**
* A method that allows awaiting a promise safely without using try/catch
* The return signature is an array that can be decontructed on use, and
* has a similar shape to a standard Node.js callback
* @param {Promise<T>} func
* @returns {Promise<[any, T]>}
*/
static to(promise) {
return promise
.then(data => [null, data])
.catch(err => [err, null]);
}
static promisifyTo(func, firstParamError = true) {
return PromiseUtils.to(PromiseUtils.promisify(func, firstParamError));
}
/**
* Resolve only if the passed-in promise resolves before the timeout
* Rejects with 'Timeout' if the timeout occurs before the promise resolves
* Passes through the rejection from the promise if it rejects before the
* timeout
* @param {Promise<T>} promise The promise to race against the timeout
* @param {number} timeoutMs Milliseconds before the timeout rejects the promise
* @param {string} [errorMessage] Error message to use on timeout reject
* @return {Promise<T>} The new promise that rejects on timeout
*/
static timeout(promise, timeoutMs, errorMessage) {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reject(new Error(errorMessage || `Timeout of ${timeoutMs}ms`));
}, timeoutMs);
promise
.then(data => resolve(data))
.catch(error => reject(error))
.then(() => clearTimeout(timeoutHandle));
});
}
}
exports.PromiseUtils = PromiseUtils;
},{"../log":1}],13:[function(require,module,exports){
"use strict";
/**
* @fileOverview
* Contains Utilities related to random selection of items
*
* Created on 7/17/16.
* @author Sam Spaulding <sam.spaulding@jibo.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
class RandomUtils {
/**
* Returns a 'data' element from an array of objects with { data: Object, weight: number} fields
* selected at random with weight proportional to the weight value of the corresponding element
* normalised by the weights of the other elements
*
* @param {WeightedRandomData[]} inputs
* @returns {Object}
*/
static weightedRandomSample(inputs) {
let result = {};
let totalWeight = 0;
for (let element of inputs) {
totalWeight += element.weight;
}
const rand = Math.random() * totalWeight;
let ongoingWeight = 0;
for (let i = 0; i < inputs.length; ++i) {
// do not factor in 0 or negative weight elements
if (inputs[i].weight > 0) {
ongoingWeight += inputs[i].weight;
if (rand < ongoingWeight) {
result = inputs[i].data;
break;
}
}
}
return result;
}
}
exports.RandomUtils = RandomUtils;
},{}],14:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events = require("events");
const uuid = require('uuid');
const log_1 = require("../log");
class Session {
constructor(options) {
this.options = options;
this.id = uuid.v4();
this.active = false;
this.hasBeenStopped = false;
}
/**
* Stops the session activity if possible
* @returns {(any | Promise<any>)}
* @memberOf Session
*/
stop() {
if (!this.hasBeenStopped && this.activity) {
this.hasBeenStopped = true;
return this.activity.stop();
}
}
/**
* Updates the session activity if possible
* @memberOf Session
*/
update() {
if (this.activity && this.activity.update) {
this.activity.update();
}
}
}
exports.Session = Session;
class SessionManager {
/**
* Creates an instance of SessionManager.
* @param {OpenMethod} openMethod - Is called to create a new session
* @param {() => void} done - Gets called when all sessions have naturally completed (i.e not when the Session is manually closed)
* @param {JiboLog} [parentLog] optional log implementation which we will create a child of
* @memberOf SessionManager
*/
constructor(openMethod, done, parentLog) {
this.openMethod = openMethod;
this.done = done;
this.isClosing = false;
this.emitter = new events.EventEmitter();
if (parentLog) {
this.log = parentLog.createChild('SessionManager');
}
else {
this.log = log_1.default.createChild('SessionManager');
}
}
/**
* Should be called when skill is opened
* @memberOf SessionManager
*/
open(options) {
if (this.current) {
this.current.active = false;
}
this.current = null;
this.pending = null;
this.isClosing = false;
this.emitter.removeAllListeners();
this.replaceSession(options);
}
/**
* Switches skill session
* @param {*} options
* @memberOf SessionManager
*/
replaceSession(options) {
this.isClosing = false;
const session = new Session(options);
if (this.current) {
// If we have a current session, we set this as pending and tell current session to stop
if (this.pending) {
this.log.debug('overriding pending');
}
else {
this.log.debug('setting pending');
}
this.pending = session;
this.current.stop();
}
else {
this.log.debug('starting session');
// We immediately open this session
this._startSession(session);
}
}
/**
* Stops current session and waits for it to finish.
* After any call to this method you have to call `open` again
* @returns {Promise<any>}
* @memberOf SessionManager
*/
close() {
this.isClosing = true;
this.pending = null;
let pr = Promise.resolve();
if (this.current) {
const ret = this.current.stop();
if (ret instanceof Promise) {
pr = ret;
}
}
return pr.then(() => this.waitForCurrent());
}
/**
* Waits for the current session to complete, if no session is current then we resolve immediately
* @returns {Promise<any>}
* @memberOf SessionManager
*/
waitForCurrent() {
if (this.current) {
return this.current.activity.promise;
}
else {
return Promise.resolve();
}
}
/**
* Waits for all sessions to complete, if no session is current then we resolve immediately
* @returns {Promise<any>}
* @memberOf SessionManager
*/
waitForAll() {
if (this.current) {
return new Promise(resolve => {
this.emitter.once('done', resolve);
});
}
else {
return Promise.resolve();
}
}
_startSession(session) {
this.current = session;
session.active = true;
session.activity = this.openMethod(session.options);
session.activity.promise = session.activity.promise
.catch(err => this.log.error(err))
.then(() => {
this.log.debug(`Session finished`);
const isCurrent = (this.current === session);
// If this session is the current one, then we set it to null
if (isCurrent) {
this.current = null;
}
session.active = false;
// If there is a pending session, we open that one
if (this.pending) {
this.log.debug(`Starting pending`);
const next = this.pending;
this.pending = null;
this._startSession(next);
}
else {
// If we are not in the process of closing (e.g. from manually closing the session) then we call the done callback
if (isCurrent && !this.isClosing) {
this.log.debug(`Calling done`);
this.done();
}
this.emitter.emit('done');
}
});
}
}
exports.SessionManager = SessionManager;
},{"../log":1,"events":undefined,"uuid":undefined}],15:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 7/11/16.
* @author Siggi Orn <siggi@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
class ProcessResult {
}
exports.ProcessResult = ProcessResult;
class TerminalUtils {
/**
* Runs a terminal command
* @param {string} cmd Command to run
* @param {string[]} [args] Arguments to command
* @param {string} [cwd] Working directory to execute command
* @returns {Promise<T>}
*/
static run(cmd, args, cwd) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
let inst = cwd ? child_process_1.spawn(cmd, args, { cwd }) : child_process_1.spawn(cmd, args);
let res = new ProcessResult();
inst.stdout.on('data', (data) => {
res.stdout = new Buffer(data).toString('utf8', 0);
});
inst.stderr.on('data', (data) => {
res.stderr = new Buffer(data).toString('utf8', 0);
});
inst.on('error', (err) => {
res.error = err;
res.code = err.code;
reject(res);
});
inst.on('close', (code) => {
res.code = code;
if (code === 0) {
resolve(res);
}
else {
reject(res);
}
});
});
});
}
}
exports.TerminalUtils = TerminalUtils;
},{"child_process":undefined}],16:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 9/24/16.
* @author Siggi Orn <siggi@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const FileUtils_1 = require("./FileUtils");
const events_1 = require("events");
var Type;
(function (Type) {
Type["NUMBER"] = "NUMBER";
Type["BOOLEAN"] = "BOOLEAN";
Type["STRING"] = "STRING";
})(Type = exports.Type || (exports.Type = {}));
/**
* Parses a boolean value
* @param {string|boolean} input
* @return {boolean}
*/
function getBoolean(input) {
if (typeof input === 'boolean') {
return input;
}
else if (typeof input === 'string') {
return !!JSON.parse(input.toLowerCase());
}
else {
throw new Error(`Can't parse boolean '${input}'`);
}
}
/**
* @class TestConfiguration
*/
class TestConfiguration extends events_1.EventEmitter {
constructor(namespace) {
super();
this.namespace = namespace;
this._hasTestData = false;
}
/**
* Initializes test configuration from test file (if it exists)
* Should be called after all value providers have been provided
* @param {string} [pathToTestConfig] Path to test config file
* @return {Promise<boolean>} True if test configuration loaded, false otherwise
*/
init(pathToTestConfig) {
return __awaiter(this, void 0, void 0, function* () {
let data;
try {
data = yield FileUtils_1.FileUtils.readFile(pathToTestConfig);
}
catch (error) {
// Skipping file read error (since file might not exist)
}
if (data) {
try {
data = JSON.parse(data);
}
catch (error) {
throw new Error(`Error parsing test config '${pathToTestConfig}': ${error}`);
}
// If the test data object is enabled and has our namespace
const enabled = getBoolean(data.enabled);
if (enabled && data[this.namespace]) {
this._data = data[this.namespace];
// Verify test values
// TOFIX check if test names are expected
this._hasTestData = true;
return true;
}
}
if (!this._data) {
this._data = {};
return false;
}
});
}
/**
* Returns true if this class has loaded a valid test configuration
* and that it is enabled
* @return {boolean}
*/
hasEnabledTestConfig() {
return this._hasTestData;
}
/**
* Returns true if this class has an overriding test configuration
* @return {boolean}
*/
hasOverridingTestConfig() {
return !!this._testConfig;
}
/**
* Clears the overriding test config
*/
clearOverridingTestConfig() {
this._testConfig = null;
}
/**
* Sets the overriding test config. If null then test config is disabled
* @param {Object} testConfig Null to disable
*/
setOverridingTestConfig(testConfig) {
this._testConfig = testConfig;
if (testConfig) {
// Verify values
// TOFIX check if test names are expected
}
}
/**
* Waits for event emitted on this object
* @param {string} name
* @return {Promise<void>}
*/
waitForEvent(name) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(resolve => {
this.once(name, () => resolve());
});
});
}
/**
* A number value getter
* @param {string} name
* @param {function} [provider] A provider of the real value in case it doesn't exist in test config
* @return {Promise<number|null>}
*/
getNumberFromTestConfig(name, provider) {
return __awaiter(this, void 0, void 0, function* () {
const val = yield this._getFromTestConfig(name, Type.NUMBER);
if (val !== null) {
return parseFloat(val);
}
else if (provider) {
return Promise.resolve(provider());
}
else {
return null;
}
});
}
/**
* A string value getter
* @param {string} name
* @param {function} [provider] A provider of the real value in case it doesn't exist in test config
* @return {Promise<string|null>}
*/
getStringFromTestConfig(name, provider) {
return __awaiter(this, void 0, void 0, function* () {
const val = yield this._getFromTestConfig(name, Type.STRING);
if (val !== null) {
return (typeof val === 'string') ? val : ('' + val);
}
else if (provider) {
return Promise.resolve(provider());
}
else {
return null;
}
});
}
/**
* A boolean value getter
* @param {string} name
* @param {function} [provider] A provider of the real value in case it doesn't exist in test config
* @return {Promise<boolean|null>}
*/
getBooleanFromTestConfig(name, provider) {
return __awaiter(this, void 0, void 0, function* () {
const val = yield this._getFromTestConfig(name, Type.BOOLEAN);
if (val !== null) {
return getBoolean(val);
}
else if (provider) {
return Promise.resolve(provider());
}
else {
return null;
}
});
}
/**
* A generic value getter
* @param {string} name
* @param {Type} type
* @return {Promise<any>}
* @private
*/
_getFromTestConfig(name, type) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._data) {
throw new Error(`Test configuration '${this.namespace}' is not initialized`);
}
// TOFIX check if value name is expected and type thereof
// If we find the data in the overriding test file
if (this._testConfig && this._testConfig[name] !== undefined) {
return this._testConfig[name];
}
else if (this._data[name] !== undefined) {
return this._data[name];
}
return null;
});
}
}
exports.TestConfiguration = TestConfiguration;
},{"./FileUtils":9,"events":undefined}],17:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 7/17/16.
* @author Siggi Orn <siggi@jibo.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
const PathUtils_1 = require("./PathUtils");
const path = require("path");
class NameSpace {
constructor(name) {
this.name = name;
/**
* A container for test dates
* @type {Map<string, Date>}
*/
this.dateById = new Map();
/**
* A container for test random numbers
* @type {Map<string, Date>}
*/
this.randomById = new Map();
// lint noop
}
/**
* Clear all installed tests
*/
clear() {
this.dateById.clear();
this.randomById.clear();
}
}
exports.NameSpace = NameSpace;
const defaultNamespace = new NameSpace('');
const nameSpaces = new Map();
nameSpaces.set('', defaultNamespace);
nameSpaces.set('default', defaultNamespace);
/**
* Whether testing is enabled or not
* @type {boolean}
*/
let enableTest = false;
/**
* A function that returns a date
* @type {Function}
* @returns {Date}
*/
let realDateProvider = () => {
return new Date();
};
/**
* A function that returns a random number [0,1)
* @type {Function}
* @returns {number}
*/
let realRandomProvider = () => {
return Math.random();
};
class TestUtils {
/**
* Returns the main export of a project, same as you would get if you
* did `let exp = require('project-name');`
* @param {NodeModule|string} pathOrModule Path in project or NodeModule
* @returns {any}
*/
static getProjectMainExport(pathOrModule) {
let root = PathUtils_1.PathUtils.getProjectRoot(pathOrModule);
let pack = require(path.join(root, 'package.json'));
return require(path.join(root, pack.main));
}
/**
* Clear all tests in all namespaces
*/
static clearAllTests() {
nameSpaces.forEach(ns => ns.clear());
}
/**
* Clear all tests in namespace
* @param {string} [namespace=''] name of namespace
*/
static clearTests(namespace) {
let nsName = namespace || '';
let ns = nameSpaces.get(nsName);
if (ns) {
ns.clear();
}
}
/**
* Turn on/off testing
* @param {boolean} enable
*/
static enableTesting(enable) {
enableTest = enable;
}
/**
* Returns namespace, if undefined then returns default namespace
* @param namespace
* @returns {any}
*/
static getNamespace(namespace) {
if (!namespace) {
return defaultNamespace;
}
else {
let ns = nameSpaces.get(namespace);
if (!ns) {
ns = new NameSpace(namespace);
nameSpaces.set(namespace, ns);
}
return ns;
}
}
/**
* Provides a test ID for a particular date
* @param {Date} date
* @param {string} id
* @param {string} [namespace='']
*/
static setTestDate(date, id, namespace) {
let ns = TestUtils.getNamespace(namespace);
ns.dateById.set(id, date);
}
/**
* Returns a Date object. If testing is enabled and an id is
* provided then the provided date will be returned
* @param {string} [id] Test id
* @param {string} [namespace='']
* @returns {Date}
*/
static getDate(id, namespace) {
if (enableTest && id) {
let ns = TestUtils.getNamespace(namespace);
let testDate = ns.dateById.get(id);
if (testDate) {
return testDate;
}
}
return realDateProvider();
}
/**
* Provides a test ID for a particular random number
* @param {number} randomNum
* @param {string} id
* @param {string} [namespace='']
*/
static setTestRandom(randomNum, id, namespace) {
let ns = TestUtils.getNamespace(namespace);
ns.randomById.set(id, randomNum);
}
/**
* Returns a random number. If testing is enabled and an id is
* provided then the provided random number will be returned
* @param {string} [id] Test id
* @param {string} [namespace]
* @returns {number}
*/
static getRandom(id, namespace) {
if (enableTest && id) {
let ns = TestUtils.getNamespace(namespace);
let random = ns.randomById.get(id);
if (random) {
return random;
}
}
return realRandomProvider();
}
}
exports.TestUtils = TestUtils;
},{"./PathUtils":10,"path":undefined}],18:[function(require,module,exports){
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("lodash/lodash.min");
let _timeOutFunc = setTimeout;
class DefaultDateProvider {
getDate() {
return new Date();
}
}
exports.DefaultDateProvider = DefaultDateProvider;
var PartOfDayBasic;
(function (PartOfDayBasic) {
PartOfDayBasic["MORNING"] = "MORNING";
PartOfDayBasic["AFTERNOON"] = "AFTERNOON";
PartOfDayBasic["EVENING"] = "EVENING";
PartOfDayBasic["NIGHT"] = "NIGHT";
})(PartOfDayBasic = exports.PartOfDayBasic || (exports.PartOfDayBasic = {}));
var PartOfDayDetail;
(function (PartOfDayDetail) {
PartOfDayDetail["EARLY"] = "EARLY";
PartOfDayDetail["MID"] = "MID";
PartOfDayDetail["LATE"] = "LATE";
})(PartOfDayDetail = exports.PartOfDayDetail || (exports.PartOfDayDetail = {}));
// array of {time: {hour: number, minute: number}, pod: PartOfDay}
// to find current part of day, iterate through the array
const EARLY_MORNING_MINUTES = 45;
const EARLY_MORNING_HOURS = 4;
exports.PartOfDayTimes = [
{ time: { hour: 0, minute: 0 }, pod: { basic: PartOfDayBasic.NIGHT, detail: PartOfDayDetail.MID } },
{ time: { hour: 2, minute: 0 }, pod: { basic: PartOfDayBasic.NIGHT, detail: PartOfDayDetail.LATE } },
{ time: { hour: EARLY_MORNING_HOURS, minute: EARLY_MORNING_MINUTES }, pod: { basic: PartOfDayBasic.MORNING, detail: PartOfDayDetail.EARLY } },
{ time: { hour: 6, minute: 45 }, pod: { basic: PartOfDayBasic.MORNING, detail: PartOfDayDetail.MID } },
{ time: { hour: 10, minute: 0 }, pod: { basic: PartOfDayBasic.MORNING, detail: PartOfDayDetail.LATE } },
{ time: { hour: 12, minute: 0 }, pod: { basic: PartOfDayBasic.AFTERNOON, detail: PartOfDayDetail.EARLY } },
{ time: { hour: 14, minute: 0 }, pod: { basic: PartOfDayBasic.AFTERNOON, detail: PartOfDayDetail.MID } },
{ time: { hour: 16, minute: 0 }, pod: { basic: PartOfDayBasic.AFTERNOON, detail: PartOfDayDetail.LATE } },
{ time: { hour: 18, minute: 0 }, pod: { basic: PartOfDayBasic.EVENING, detail: PartOfDayDetail.EARLY } },
{ time: { hour: 20, minute: 0 }, pod: { basic: PartOfDayBasic.EVENING, detail: PartOfDayDetail.MID } },
{ time: { hour: 21, minute: 0 }, pod: { basic: PartOfDayBasic.EVENING, detail: PartOfDayDetail.LATE } },
{ time: { hour: 22, minute: 0 }, pod: { basic: PartOfDayBasic.NIGHT, detail: PartOfDayDetail.EARLY } },
];
class TimeUtils {
/**
* Sets the date provider that should be used by TimeUtils
* @param {DateProvider} dateProvider
*/
static setDateProvider(dateProvider) {
TimeUtils._dateProvider = dateProvider;
}
/**
* Provide the timeout function to use in all time utilities
* @param {Function.<Function, number>} timeoutFunc
*/
static setTimeoutFunction(timeoutFunc) {
_timeOutFunc = timeoutFunc;
}
/**
* Waits for timeMs and then resolves the promise
* @param {number} timeMs Time to wait in ms
* @returns {Promise<void>}
*/
static wait(timeMs) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((res) => {
_timeOutFunc(res, timeMs);
});
});
}
/**
* Dispatch a timeline of actions
* @param {TimelineElement[]} timeline
* @returns {Promise<void>}
*/
static dispatchTimeline(timeline) {
return __awaiter(this, void 0, void 0, function* () {
let startTime = process.hrtime();
let promiseChain = Promise.resolve();
timeline.forEach(el => {
promiseChain = promiseChain
.then(() => TimeUtils._dispatchTimeline(el, startTime));
});
return promiseChain;
});
}
static _dispatchTimeline(el, startTime) {
return __awaiter(this, void 0, void 0, function* () {
let timeDiff = process.hrtime(startTime);
let passedTimeMs = timeDiff[0] * 1e3 + timeDiff[1] * 1e-6;
let timeToWait = Math.max(0, el.timeMs - passedTimeMs);
return TimeUtils.wait(timeToWait).then(() => el.action());
});
}
/**
* returns the current part of day (morning, afternoon, evening, night)
* @param {Date} [date] Optional date, defaults to using date provider
* @returns {PartOfDay}
*/
static getPartOfDay(date) {
date = date || TimeUtils._dateProvider.getDate();
let hours = date.getHours();
let minutes = date.getMinutes();
let pod;
// we iterate through the PoD time boundaries in chronological order
for (let i = exports.PartOfDayTimes.length - 1; i >= 0; i--) {
// iterate backwards through the defined parts of day.
// when we get to one whose start time is ealier than the input time, we've found our match
pod = exports.PartOfDayTimes[i].pod;
let podtime = exports.PartOfDayTimes[i].time;
if ((podtime.hour < hours) ||
(podtime.hour === hours && podtime.minute <= minutes)) {
break;
}
}
return _.cloneDeep(pod);
}
/**
* Returns whether it is early in the morning (between 4:30 and 7 am)
* This method exists for backward compatibility, but PartOfDay now provides this info directly
* @param {Date} [date] Optional date, defaults to using date provider
* @returns {boolean}
*
*/
static isEarlyMorning(date) {
date = date || TimeUtils._dateProvider.getDate();
let pod = TimeUtils.getPartOfDay(date);
return (pod.basic === PartOfDayBasic.MORNING && pod.detail === PartOfDayDetail.EARLY);
}
/**
* Converts milliseconds to seconds
* @param ms {number}
* @returns {number}
*/
static msToSeconds(ms) {
return ms / (1000);
}
/**
* Converts milliseconds to minutes
* @param ms {number}
* @returns {number}
*/
static msToMinutes(ms) {
return ms / (60 * 1000);
}
/**
* Converts milliseconds to hours
* @param ms {number}
* @returns {number}
*/
static msToHours(ms) {
return ms / (60 * 60 * 1000);
}
/**
* Converts seconds to milliseconds
* @param seconds {number}
* @returns {number}
*/
static secondsToMs(seconds) {
return seconds * 1000;
}
/**
* Converts minutes to milliseconds
* @param minutes {number}
* @returns {number}
*/
static minutesToMs(minutes) {
return minutes * 60 * 1000;
}
/**
* Converts hours to milliseconds
* @param hours {number}
* @returns {number}
*/
static hoursToMs(hours) {
return hours * 60 * 60 * 1000;
}
/**
* Turns a string date of the format yyyy-mm-dd into a Date object
* Does not take time into consideration
* @param {String} date to convert in the form yyyy-mm-dd
* @returns {Date}
*/
static dateFromString(date) {
let retVal = null;
try {
let yyyymmdd = date.split('-');
// Attempt to enforce date format (and skip time)
if (yyyymmdd.length !== 3) {
throw 'Invalid Date Format';
}
retVal = new Date(Number(yyyymmdd[0]), Number(yyyymmdd[1]) - 1, Number(yyyymmdd[2]));
}
catch (_a) {
console.error(`TimeUtils: Attempted to make date from malformed string: ${date}. Requires format yyyy-mm-dd. Returning null.`);
retVal = null;
}
return retVal;
}
/**
* Check whether a queried date is within the start and end date.
* If no end date is provided just check whether the query date is the same as the start date.
* Does not take time into consideration.
* @param {Date} startDate of interval
* @param {Date} queryDate to check
* @param {Date} endDate of interval
* @returns {boolean}
*/
static dateWithinInterval(startDate, queryDate, endDate) {
let withinInterval = false;
if (!endDate) {
endDate = startDate;
}
// Make sure that all 3 dates are time insensitive
const startDate_rounded = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const queryDate_rounded = new Date(queryDate.getFullYear(), queryDate.getMonth(), queryDate.getDate());
const endDate_rounded = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
if (startDate_rounded.getTime() <= queryDate_rounded.getTime() && queryDate_rounded.getTime() <= endDate_rounded.getTime()) {
withinInterval = true;
}
return withinInterval;
}
/**
* Check whether the two ms times are on the same day. A new day starts at EARLY-MORNING pod (4:45am)
* @param {number} ms1 of first time
* @param {number} ms2 of second time
*/
static sameDay(ms1, ms2) {
const date1 = new Date(ms1);
const date2 = new Date(ms2);
// Edge case where different days are within 24 hours. Could be in different months.
let same = true;
const diff = Math.abs(ms2 - ms1) / this.hoursToMs(1);
if (diff < 24) {
// Is the later of the two dates after EARLY MORNING?
const earlierDate = (date1 <= date2) ? date1 : date2;
const laterDate = (date1 > date2) ? date1 : date2;
const earlyMorning = new Date(laterDate.getFullYear(), laterDate.getMonth(), laterDate.getDate(), EARLY_MORNING_HOURS, EARLY_MORNING_MINUTES);
same = !((earlierDate < earlyMorning) && (earlyMorning < laterDate)); // The dates are not separated by an early morning
}
else {
same = false;
}
return same;
}
}
/**
* A class that can provide dates (defaults to `new Date()`)
* @type {DateProvider}
* @private
*/
TimeUtils._dateProvider = new DefaultDateProvider();
exports.TimeUtils = TimeUtils;
},{"lodash/lodash.min":undefined}],19:[function(require,module,exports){
"use strict";
/**
* Created by Andrew Stout on 6/28/16, based on prior work by Siggi Orn.
*/
Object.defineProperty(exports, "__esModule", { value: true });
class Updatable {
constructor() {
this.updatables = new Set();
// lint noop
}
/**
* Should get called from the main update loop.
* Iterates through all registered updatables and updates them
*/
update(elapsed) {
//for(let callback of this.updatables) {
this.updatables.forEach((callback) => {
callback(elapsed); // this should probably be made asynchronous at some point
});
}
/**
* Add a function to be called on every update of loop.
* If this function has already been added then this has no effect.
* @param {function} callback Function to be called at every update.
* The callback function takes a parameter of the form [seconds, milliseconds]
* @returns {boolean} false if callback existed in collection and
* was therefore not added.
*/
add(callback) {
let exists = this.updatables.has(callback);
this.updatables.add(callback);
return !exists;
}
/**
* Stop updating a function on every update of loop
* @param {function} callback Function to be removed from update loop
* @returns {boolean}
*/
remove(callback) {
return this.updatables.delete(callback);
}
}
exports.Updatable = Updatable;
},{}],20:[function(require,module,exports){
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./ActionTimeline"));
__export(require("./CacheUtils"));
__export(require("./CancelToken"));
__export(require("./CancelTokenSession"));
__export(require("./ExtPromiseWrapper"));
__export(require("./FileUtils"));
__export(require("./PathUtils"));
__export(require("./PromiseQueue"));
__export(require("./PromiseUtils"));
__export(require("./RandomUtils"));
__export(require("./SessionManager"));
__export(require("./CancelToken"));
__export(require("./CancelTokenSession"));
__export(require("./TerminalUtils"));
__export(require("./TestConfiguration"));
__export(require("./CacheManager"));
__export(require("./TestUtils"));
__export(require("./TimeUtils"));
__export(require("./Updatable"));
__export(require("./CircularBuffer"));
},{"./ActionTimeline":2,"./CacheManager":3,"./CacheUtils":4,"./CancelToken":5,"./CancelTokenSession":6,"./CircularBuffer":7,"./ExtPromiseWrapper":8,"./FileUtils":9,"./PathUtils":10,"./PromiseQueue":11,"./PromiseUtils":12,"./RandomUtils":13,"./SessionManager":14,"./TerminalUtils":15,"./TestConfiguration":16,"./TestUtils":17,"./TimeUtils":18,"./Updatable":19}],21:[function(require,module,exports){
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./main"));
},{"./main":20}]},{},[21])(21)
});
//# sourceMappingURL=jibo-cai-utils.js.map