UNPKG

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