'use strict'; var lazy = require('../../utils/object').lazy; var isFactory = require('../../utils/object').isFactory; var traverse = require('../../utils/object').traverse; var extend = require('../../utils/object').extend; var ArgumentsError = require('../../error/ArgumentsError'); function factory (type, config, load, typed, math) { /** * Import functions from an object or a module * * Syntax: * * math.import(object) * math.import(object, options) * * Where: * * - `object: Object` * An object with functions to be imported. * - `options: Object` An object with import options. Available options: * - `override: boolean` * If true, existing functions will be overwritten. False by default. * - `silent: boolean` * If true, the function will not throw errors on duplicates or invalid * types. False by default. * - `wrap: boolean` * If true, the functions will be wrapped in a wrapper function * which converts data types like Matrix to primitive data types like Array. * The wrapper is needed when extending math.js with libraries which do not * support these data type. False by default. * * Examples: * * // define new functions and variables * math.import({ * myvalue: 42, * hello: function (name) { * return 'hello, ' + name + '!'; * } * }); * * // use the imported function and variable * math.myvalue * 2; // 84 * math.hello('user'); // 'hello, user!' * * // import the npm module 'numbers' * // (must be installed first with `npm install numbers`) * math.import(require('numbers'), {wrap: true}); * * math.fibonacci(7); // returns 13 * * @param {Object | Array} object Object with functions to be imported. * @param {Object} [options] Import options. */ function math_import(object, options) { var num = arguments.length; if (num != 1 && num != 2) { throw new ArgumentsError('import', num, 1, 2); } if (!options) { options = {}; } if (isFactory(object)) { _importFactory(object, options); } // TODO: allow a typed-function with name too else if (Array.isArray(object)) { object.forEach(function (entry) { math_import(entry, options); }); } else if (typeof object === 'object') { // a map with functions for (var name in object) { if (object.hasOwnProperty(name)) { var value = object[name]; if (isSupportedType(value)) { _import(name, value, options); } else if (isFactory(object)) { _importFactory(object, options); } else { math_import(value, options); } } } } else { if (!options.silent) { throw new TypeError('Factory, Object, or Array expected'); } } } /** * Add a property to the math namespace and create a chain proxy for it. * @param {string} name * @param {*} value * @param {Object} options See import for a description of the options * @private */ function _import(name, value, options) { if (options.wrap && typeof value === 'function') { // create a wrapper around the function value = _wrap(value); } if (isTypedFunction(math[name]) && isTypedFunction(value)) { if (options.override) { // give the typed function the right name value = typed(name, value.signatures); } else { // merge the existing and typed function value = typed(math[name], value); } math[name] = value; _importTransform(name, value); math.emit('import', name, function resolver() { return value; }); return; } if (math[name] === undefined || options.override) { math[name] = value; _importTransform(name, value); math.emit('import', name, function resolver() { return value; }); return; } if (!options.silent) { throw new Error('Cannot import "' + name + '": already exists'); } } function _importTransform (name, value) { if (value && typeof value.transform === 'function') { math.expression.transform[name] = value.transform; } } /** * Create a wrapper a round an function which converts the arguments * to their primitive values (like convert a Matrix to Array) * @param {Function} fn * @return {Function} Returns the wrapped function * @private */ function _wrap (fn) { var wrapper = function wrapper () { var args = []; for (var i = 0, len = arguments.length; i < len; i++) { var arg = arguments[i]; args[i] = arg && arg.valueOf(); } return fn.apply(math, args); }; if (fn.transform) { wrapper.transform = fn.transform; } return wrapper; } /** * Import an instance of a factory into math.js * @param {{factory: Function, name: string, path: string, math: boolean}} factory * @param {Object} options See import for a description of the options * @private */ function _importFactory(factory, options) { if (typeof factory.name === 'string') { var name = factory.name; var namespace = factory.path ? traverse(math, factory.path) : math; var existing = namespace.hasOwnProperty(name) ? namespace[name] : undefined; var resolver = function () { var instance = load(factory); if (isTypedFunction(existing) && isTypedFunction(instance)) { if (options.override) { // replace the existing typed function (nothing to do) } else { // merge the existing and new typed function instance = typed(existing, instance); } return instance; } if (existing === undefined || options.override) { return instance; } if (!options.silent) { throw new Error('Cannot import "' + name + '": already exists'); } }; if (factory.lazy !== false) { lazy(namespace, name, resolver); } else { namespace[name] = resolver(); } math.emit('import', name, resolver, factory.path); } else { // unnamed factory. // no lazy loading load(factory); } } /** * Check whether given object is a type which can be imported * @param {Function | number | string | boolean | null | Unit | Complex} object * @return {boolean} * @private */ function isSupportedType(object) { return typeof object == 'function' || typeof object === 'number' || typeof object === 'string' || typeof object === 'boolean' || object === null || (object && object.isUnit === true) || (object && object.isComplex === true) || (object && object.isBigNumber === true) || (object && object.isFraction === true) || (object && object.isMatrix === true) || (object && Array.isArray(object) === true) } /** * Test whether a given thing is a typed-function * @param {*} fn * @return {boolean} Returns true when `fn` is a typed-function */ function isTypedFunction (fn) { return typeof fn === 'function' && typeof fn.signatures === 'object'; } return math_import; } exports.math = true; // request access to the math namespace as 5th argument of the factory function exports.name = 'import'; exports.factory = factory; exports.lazy = true;