Source: provider/scan/DefaultProviderByScanning.js

/**
 * @author Sloan Seaman 
 * @copyright 2016 and on
 * @version .1
 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */

/** @private */
var DefaultJSFilenameFormatter = require ('../defaultJSFilenameFormatter.js');
var DefaultJSONFilenameFormatter = require ('../defaultJSONFilenameFormatter.js');
var DefaultResponseBuilder = require ('../../response/defaultResponseBuilder.js');
var svUtil = require('../../util.js');
var providerUtil = require('../providerUtil.js');
var path = require('path');
var log = require('winston-simple').getLogger('ProviderByScanning');

var compressionList = ['filters', 'sessionHandlers'];
var alreadyLoaded = {};

/**
 * Loads filters, intentHandlers, sessionHandlers, and responses by scanning all of the passed in files
 * for the method signatures that define each of the types.  This model allows one object to
 * handle all types (except responses).  As responses are pure JSON, they cannot be detected by function
 * signatures and have to be detected by filename signature.
 *
 * The internal data structure that is made available via getItem() is:
 * @example
 * {
 *		sessionHandlers : {
 *			start : [],
 *			end : []
 *		},
 *		filters : {
 *			pre : [],
 *			post : []
 *		},
 *		intentHandlers : {},
 *		responses : {}
 * };
 * 
 * 
 * @constructor
 * @implements {Provider}
 * @see  {@link Filter}
 * @see  {@link IntentHandler}
 * @see  {@link SessionHandler}
 * @see  {@link Response}
 * @param {String} files The files to scan
 * @param {Object.<String, Object>} options Options for scanning 
 * @param {FilenameFormatter} [options.responseFilenameFormatter=DefaultFilenameFormatter] 
 *        	The filename formatter to use to detect responses names from the file name
 * @param {FilenameFormatter} [options.intentFilenameFormatter=DefaultFilenameFormatter] 
 *        	As intents are not required to have a getIntentsList, and instead can be derived from filename,
 *        	a @{link FilenameFormatter} is required to determine the intent name from the file name
 * @param {ResponseBuilder} [options.responseBuilder=DefaultResponseBuilder] The ResponseBuilder to use when building responses. Defaults to DefaultResponseBuilder
 * @param {String} [options.fileEncoding=utf8] The file encoding to use when reading files
 */
function DefaultProviderByScanning(files, options) {
	// map for the types
	this._items = {
		sessionHandlers : {
			start : [],
			end : []
		},
		filters : {
			pre : [],
			post : []
		},
		intentHandlers : {},
		responses : {}
	};

	this._responseBuilder = (options && options.responseBuilder) 
		? options.responseBuilder
		: new DefaultResponseBuilder();

	this._fileEncoding = (options && options.fileEncoding)
		? options.fileEncoding
		: 'utf8';

	this._intentFilenameFormatter = (options && options.intentFilenameFormatter) 
		?options.intentFilenameFormatter
		: new DefaultJSFilenameFormatter();

	this._responseFilenameFormatter = (options && options.responseFilenameFormatter) 
		? options.responseFilenameFormatter
		: new DefaultJSONFilenameFormatter();

	var loaded;
	var isResponse = true;
	for (var fileIdx=0;fileIdx<files.length;fileIdx++) {
		if (!alreadyLoaded[files[fileIdx]]) {
			log.info("Processing file "+files[fileIdx]);

			// load so we can see what it is
			loaded = svUtil.instantiate(path.resolve(process.cwd(),files[fileIdx])); 

			providerUtil.addFunctions(loaded, { 'name' : path.parse(files[fileIdx]).name });

			// reset for next pass
			isResponse = true;

			// intent
			if (svUtil.isFunction(loaded.handleIntent)) {
				isResponse = false;
				if (svUtil.isFunction(loaded.getIntentsList)) {
					var handledIntents = loaded.getIntentsList();
					for (var hIdx=0;hIdx<handledIntents.length;hIdx++) {
						this._items.intentHandlers[handledIntents[hIdx]] = loaded;
						log.info('Loaded intent handler '+loaded.getName()+ ' for intent '+handledIntents[hIdx]);
					}
				}
				else {
					var intentName = this._intentFilenameFormatter.parse(files[fileIdx])[0];
					this._items.intentHandlers[intentName] = loaded;
					log.info('Loaded intent handler '+loaded.getName()+ ' for intent '+intentName);
				}
			}
			// filter
			if (svUtil.isFunction(loaded.executePre)) {
				isResponse = false;
				this._populate('filter', 'pre', this._items.filters.pre, loaded);
			}
			if (svUtil.isFunction(loaded.executePost)) {
				isResponse = false;
				this._populate('filter', 'post', this._items.filters.post, loaded);
			}
			// session
			if (svUtil.isFunction(loaded.sessionStart)) {
				isResponse = false;
				this._populate('session', 'start', this._items.sessionHandlers.start, loaded);
			}
			if (svUtil.isFunction(loaded.sessionEnd)) {
				isResponse = false;
				this._populate('session', 'end', this._items.sessionHandlers.end, loaded);
			}
			// response
			if (isResponse) {
				var responseId = this._responseFilenameFormatter.parse(files[fileIdx])[0];
				var parsedFileName = path.parse(files[fileIdx]);
				log.debug('Loaded response '+parsedFileName.base+' as id '+responseId);
				this._items.responses[responseId] = this._responseBuilder.withResponseId(responseId).withJSON(loaded).build();
			}

			alreadyLoaded[files[fileIdx]] = true;
		}
	}

	// Compress arrays in case someone put one at 1 and the next at 99
	for (var i=0;i<compressionList.length;i++) {
		for (var key in this._items[compressionList[i]]) {
			this._items[compressionList[i]][key] = svUtil.compressArray(this._items[compressionList[i]][key]);
		}
	}
}

DefaultProviderByScanning.prototype._populate = function(type, stage, items, loaded) {
	var position = items.length; // default to no getOrder
	if (svUtil.isFunction(loaded.getOrder)) {
		position = loaded.getOrder();
		items[position] = loaded;
	}
	else {
		items.push(loaded);
	}
	log.info('Loaded '+type+' handler '+loaded.getName()+' for '+ stage + ', position '+ position);
};

/**
 * Returns the item stored under the itemId.  May be null
 * 
 * @function
 * @name Provider#getItem
 * @param {String} itemId The Id of the item to return
 * @return {Object} The item corresponding to the itemId
 */
DefaultProviderByScanning.prototype.getItem = function(itemId) {
	return this._items[itemId];
};

/**
 * Returns all of the items stored.  May be null
 * 
 * @function
 * @name Provider#getItems
 * @return {Object} All the items being managed by they provider
 */
DefaultProviderByScanning.prototype.getItems = function() {
	return this._items;
};

module.exports = DefaultProviderByScanning;