UNPKG

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