import.js (13284B)
1 "use strict"; 2 3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 4 5 Object.defineProperty(exports, "__esModule", { 6 value: true 7 }); 8 exports.importFactory = importFactory; 9 10 var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 11 12 var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); 13 14 var _is = require("../../utils/is.js"); 15 16 var _factory = require("../../utils/factory.js"); 17 18 var _object = require("../../utils/object.js"); 19 20 var _array = require("../../utils/array.js"); 21 22 var _ArgumentsError = require("../../error/ArgumentsError.js"); 23 24 function importFactory(typed, load, math, importedFactories) { 25 /** 26 * Import functions from an object or a module. 27 * 28 * This function is only available on a mathjs instance created using `create`. 29 * 30 * Syntax: 31 * 32 * math.import(functions) 33 * math.import(functions, options) 34 * 35 * Where: 36 * 37 * - `functions: Object` 38 * An object with functions or factories to be imported. 39 * - `options: Object` An object with import options. Available options: 40 * - `override: boolean` 41 * If true, existing functions will be overwritten. False by default. 42 * - `silent: boolean` 43 * If true, the function will not throw errors on duplicates or invalid 44 * types. False by default. 45 * - `wrap: boolean` 46 * If true, the functions will be wrapped in a wrapper function 47 * which converts data types like Matrix to primitive data types like Array. 48 * The wrapper is needed when extending math.js with libraries which do not 49 * support these data type. False by default. 50 * 51 * Examples: 52 * 53 * import { create, all } from 'mathjs' 54 * import * as numbers from 'numbers' 55 * 56 * // create a mathjs instance 57 * const math = create(all) 58 * 59 * // define new functions and variables 60 * math.import({ 61 * myvalue: 42, 62 * hello: function (name) { 63 * return 'hello, ' + name + '!' 64 * } 65 * }) 66 * 67 * // use the imported function and variable 68 * math.myvalue * 2 // 84 69 * math.hello('user') // 'hello, user!' 70 * 71 * // import the npm module 'numbers' 72 * // (must be installed first with `npm install numbers`) 73 * math.import(numbers, {wrap: true}) 74 * 75 * math.fibonacci(7) // returns 13 76 * 77 * @param {Object | Array} functions Object with functions to be imported. 78 * @param {Object} [options] Import options. 79 */ 80 function mathImport(functions, options) { 81 var num = arguments.length; 82 83 if (num !== 1 && num !== 2) { 84 throw new _ArgumentsError.ArgumentsError('import', num, 1, 2); 85 } 86 87 if (!options) { 88 options = {}; 89 } 90 91 function flattenImports(flatValues, value, name) { 92 if (Array.isArray(value)) { 93 value.forEach(function (item) { 94 return flattenImports(flatValues, item); 95 }); 96 } else if ((0, _typeof2.default)(value) === 'object') { 97 for (var _name in value) { 98 if ((0, _object.hasOwnProperty)(value, _name)) { 99 flattenImports(flatValues, value[_name], _name); 100 } 101 } 102 } else if ((0, _factory.isFactory)(value) || name !== undefined) { 103 var flatName = (0, _factory.isFactory)(value) ? isTransformFunctionFactory(value) ? value.fn + '.transform' // TODO: this is ugly 104 : value.fn : name; // we allow importing the same function twice if it points to the same implementation 105 106 if ((0, _object.hasOwnProperty)(flatValues, flatName) && flatValues[flatName] !== value && !options.silent) { 107 throw new Error('Cannot import "' + flatName + '" twice'); 108 } 109 110 flatValues[flatName] = value; 111 } else { 112 if (!options.silent) { 113 throw new TypeError('Factory, Object, or Array expected'); 114 } 115 } 116 } 117 118 var flatValues = {}; 119 flattenImports(flatValues, functions); 120 121 for (var name in flatValues) { 122 if ((0, _object.hasOwnProperty)(flatValues, name)) { 123 // console.log('import', name) 124 var value = flatValues[name]; 125 126 if ((0, _factory.isFactory)(value)) { 127 // we ignore name here and enforce the name of the factory 128 // maybe at some point we do want to allow overriding it 129 // in that case we can implement an option overrideFactoryNames: true 130 _importFactory(value, options); 131 } else if (isSupportedType(value)) { 132 _import(name, value, options); 133 } else { 134 if (!options.silent) { 135 throw new TypeError('Factory, Object, or Array expected'); 136 } 137 } 138 } 139 } 140 } 141 /** 142 * Add a property to the math namespace 143 * @param {string} name 144 * @param {*} value 145 * @param {Object} options See import for a description of the options 146 * @private 147 */ 148 149 150 function _import(name, value, options) { 151 // TODO: refactor this function, it's to complicated and contains duplicate code 152 if (options.wrap && typeof value === 'function') { 153 // create a wrapper around the function 154 value = _wrap(value); 155 } // turn a plain function with a typed-function signature into a typed-function 156 157 158 if (hasTypedFunctionSignature(value)) { 159 value = typed(name, (0, _defineProperty2.default)({}, value.signature, value)); 160 } 161 162 if (isTypedFunction(math[name]) && isTypedFunction(value)) { 163 if (options.override) { 164 // give the typed function the right name 165 value = typed(name, value.signatures); 166 } else { 167 // merge the existing and typed function 168 value = typed(math[name], value); 169 } 170 171 math[name] = value; 172 delete importedFactories[name]; 173 174 _importTransform(name, value); 175 176 math.emit('import', name, function resolver() { 177 return value; 178 }); 179 return; 180 } 181 182 if (math[name] === undefined || options.override) { 183 math[name] = value; 184 delete importedFactories[name]; 185 186 _importTransform(name, value); 187 188 math.emit('import', name, function resolver() { 189 return value; 190 }); 191 return; 192 } 193 194 if (!options.silent) { 195 throw new Error('Cannot import "' + name + '": already exists'); 196 } 197 } 198 199 function _importTransform(name, value) { 200 if (value && typeof value.transform === 'function') { 201 math.expression.transform[name] = value.transform; 202 203 if (allowedInExpressions(name)) { 204 math.expression.mathWithTransform[name] = value.transform; 205 } 206 } else { 207 // remove existing transform 208 delete math.expression.transform[name]; 209 210 if (allowedInExpressions(name)) { 211 math.expression.mathWithTransform[name] = value; 212 } 213 } 214 } 215 216 function _deleteTransform(name) { 217 delete math.expression.transform[name]; 218 219 if (allowedInExpressions(name)) { 220 math.expression.mathWithTransform[name] = math[name]; 221 } else { 222 delete math.expression.mathWithTransform[name]; 223 } 224 } 225 /** 226 * Create a wrapper a round an function which converts the arguments 227 * to their primitive values (like convert a Matrix to Array) 228 * @param {Function} fn 229 * @return {Function} Returns the wrapped function 230 * @private 231 */ 232 233 234 function _wrap(fn) { 235 var wrapper = function wrapper() { 236 var args = []; 237 238 for (var i = 0, len = arguments.length; i < len; i++) { 239 var arg = arguments[i]; 240 args[i] = arg && arg.valueOf(); 241 } 242 243 return fn.apply(math, args); 244 }; 245 246 if (fn.transform) { 247 wrapper.transform = fn.transform; 248 } 249 250 return wrapper; 251 } 252 /** 253 * Import an instance of a factory into math.js 254 * @param {function(scope: object)} factory 255 * @param {Object} options See import for a description of the options 256 * @param {string} [name=factory.name] Optional custom name 257 * @private 258 */ 259 260 261 function _importFactory(factory, options) { 262 var name = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : factory.fn; 263 264 if ((0, _array.contains)(name, '.')) { 265 throw new Error('Factory name should not contain a nested path. ' + 'Name: ' + JSON.stringify(name)); 266 } 267 268 var namespace = isTransformFunctionFactory(factory) ? math.expression.transform : math; 269 var existingTransform = (name in math.expression.transform); 270 var existing = (0, _object.hasOwnProperty)(namespace, name) ? namespace[name] : undefined; 271 272 var resolver = function resolver() { 273 // collect all dependencies, handle finding both functions and classes and other special cases 274 var dependencies = {}; 275 factory.dependencies.map(_factory.stripOptionalNotation).forEach(function (dependency) { 276 if ((0, _array.contains)(dependency, '.')) { 277 throw new Error('Factory dependency should not contain a nested path. ' + 'Name: ' + JSON.stringify(dependency)); 278 } 279 280 if (dependency === 'math') { 281 dependencies.math = math; 282 } else if (dependency === 'mathWithTransform') { 283 dependencies.mathWithTransform = math.expression.mathWithTransform; 284 } else if (dependency === 'classes') { 285 // special case for json reviver 286 dependencies.classes = math; 287 } else { 288 dependencies[dependency] = math[dependency]; 289 } 290 }); 291 var instance = /* #__PURE__ */factory(dependencies); 292 293 if (instance && typeof instance.transform === 'function') { 294 throw new Error('Transforms cannot be attached to factory functions. ' + 'Please create a separate function for it with exports.path="expression.transform"'); 295 } 296 297 if (existing === undefined || options.override) { 298 return instance; 299 } 300 301 if (isTypedFunction(existing) && isTypedFunction(instance)) { 302 // merge the existing and new typed function 303 return typed(existing, instance); 304 } 305 306 if (options.silent) { 307 // keep existing, ignore imported function 308 return existing; 309 } else { 310 throw new Error('Cannot import "' + name + '": already exists'); 311 } 312 }; // TODO: add unit test with non-lazy factory 313 314 315 if (!factory.meta || factory.meta.lazy !== false) { 316 (0, _object.lazy)(namespace, name, resolver); // FIXME: remove the `if (existing &&` condition again. Can we make sure subset is loaded before subset.transform? (Name collision, and no dependencies between the two) 317 318 if (existing && existingTransform) { 319 _deleteTransform(name); 320 } else { 321 if (isTransformFunctionFactory(factory) || factoryAllowedInExpressions(factory)) { 322 (0, _object.lazy)(math.expression.mathWithTransform, name, function () { 323 return namespace[name]; 324 }); 325 } 326 } 327 } else { 328 namespace[name] = resolver(); // FIXME: remove the `if (existing &&` condition again. Can we make sure subset is loaded before subset.transform? (Name collision, and no dependencies between the two) 329 330 if (existing && existingTransform) { 331 _deleteTransform(name); 332 } else { 333 if (isTransformFunctionFactory(factory) || factoryAllowedInExpressions(factory)) { 334 (0, _object.lazy)(math.expression.mathWithTransform, name, function () { 335 return namespace[name]; 336 }); 337 } 338 } 339 } // TODO: improve factories, store a list with imports instead which can be re-played 340 341 342 importedFactories[name] = factory; 343 math.emit('import', name, resolver); 344 } 345 /** 346 * Check whether given object is a type which can be imported 347 * @param {Function | number | string | boolean | null | Unit | Complex} object 348 * @return {boolean} 349 * @private 350 */ 351 352 353 function isSupportedType(object) { 354 return typeof object === 'function' || typeof object === 'number' || typeof object === 'string' || typeof object === 'boolean' || object === null || (0, _is.isUnit)(object) || (0, _is.isComplex)(object) || (0, _is.isBigNumber)(object) || (0, _is.isFraction)(object) || (0, _is.isMatrix)(object) || Array.isArray(object); 355 } 356 /** 357 * Test whether a given thing is a typed-function 358 * @param {*} fn 359 * @return {boolean} Returns true when `fn` is a typed-function 360 */ 361 362 363 function isTypedFunction(fn) { 364 return typeof fn === 'function' && (0, _typeof2.default)(fn.signatures) === 'object'; 365 } 366 367 function hasTypedFunctionSignature(fn) { 368 return typeof fn === 'function' && typeof fn.signature === 'string'; 369 } 370 371 function allowedInExpressions(name) { 372 return !(0, _object.hasOwnProperty)(unsafe, name); 373 } 374 375 function factoryAllowedInExpressions(factory) { 376 return factory.fn.indexOf('.') === -1 && // FIXME: make checking on path redundant, check on meta data instead 377 !(0, _object.hasOwnProperty)(unsafe, factory.fn) && (!factory.meta || !factory.meta.isClass); 378 } 379 380 function isTransformFunctionFactory(factory) { 381 return factory !== undefined && factory.meta !== undefined && factory.meta.isTransformFunction === true || false; 382 } // namespaces and functions not available in the parser for safety reasons 383 384 385 var unsafe = { 386 expression: true, 387 type: true, 388 docs: true, 389 error: true, 390 json: true, 391 chain: true // chain method not supported. Note that there is a unit chain too. 392 393 }; 394 return mathImport; 395 }