mathjs/tools/entryGenerator.js

566 lines
16 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const Handlebars = require('handlebars')
const ENTRY_FOLDER = path.join(__dirname, '../src/entry')
const IGNORED_DEPENDENCIES = {
'on': true,
'config': true,
'math': true,
'mathWithTransform': true,
'classes': true
}
const DEPRECATED_FACTORIES = {
'typeof': 'typeOf',
'var': 'variance',
'eval': 'evaluate',
'E': 'e',
'PI': 'pi'
}
const FACTORY_NAMES_ES6_MAPPING = {
'true': '_true',
'false': '_false',
'NaN': '_NaN',
'null': '_null',
'Infinity': '_Infinity'
}
const IGNORED_DEPENDENCIES_ES6 = {
'on': true
}
const dependenciesIndexTemplate = Handlebars.compile(`/**
* THIS FILE IS AUTO-GENERATED
* DON'T MAKE CHANGES HERE
*/
{{#factories}}
export { {{name}} } from '{{fileName}}'{{eslintComment}}
{{/factories}}
export { all } from './allFactories{{suffix}}.js'
`)
const dependenciesFileTemplate = Handlebars.compile(`/**
* THIS FILE IS AUTO-GENERATED
* DON'T MAKE CHANGES HERE
*/
{{#dependencies}}
import { {{name}} } from '{{fileName}}'
{{/dependencies}}
import { {{factoryName}} } from '../../factories{{suffix}}.js'{{eslintComment}}
export const {{name}} = {{braceOpen}}{{eslintComment}}
{{#dependencies}}
{{name}},
{{/dependencies}}
{{factoryName}}
}
`)
const pureFunctionsTemplate = Handlebars.compile(`/**
* THIS FILE IS AUTO-GENERATED
* DON'T MAKE CHANGES HERE
*/
import { config } from './configReadonly'
import {
{{#pureFactories}}
{{factoryName}}{{#unless @last}},{{/unless}}{{eslintComment}}
{{/pureFactories}}
} from '../factories{{suffix}}'
{{#pureFactories}}
export const {{name}} = /* #__PURE__ */ {{factoryName ~}}
({{braceOpen}}{{#if dependencies}} {{/if ~}}
{{#dependencies ~}}
{{name}}{{#unless @last}}, {{/unless ~}}
{{/dependencies ~}}
{{#if dependencies}} {{/if ~}}})
{{/pureFactories}}
`)
const impureFunctionsTemplate = Handlebars.compile(`/**
* THIS FILE IS AUTO-GENERATED
* DON'T MAKE CHANGES HERE
*/
import { config } from './configReadonly'
import {
{{#impureFactories}}
{{factoryName}},{{eslintComment}}
{{/impureFactories}}
{{#transformFactories}}
{{factoryName}}{{#unless @last}},{{/unless}}{{eslintComment}}
{{/transformFactories}}
} from '../factories{{suffix}}'
import {
{{#pureFactories}}
{{name}}{{#unless @last}},{{/unless}}{{eslintComment}}
{{/pureFactories}}
} from './pureFunctions{{suffix}}.generated'
const math = {} // NOT pure!
const mathWithTransform = {} // NOT pure!
const classes = {} // NOT pure!
{{#impureFactories}}
export const {{name}} = {{factoryName ~}}
({{braceOpen}}{{#if dependencies}} {{/if ~}}
{{#dependencies ~}}
{{name}}{{#unless @last}}, {{/unless ~}}
{{/dependencies ~}}
{{#if dependencies}} {{/if ~}}})
{{/impureFactories}}
Object.assign(math, {
{{#math}}
{{#if renamed}}
'{{name}}': {{renamed}},
{{else if mappedName}}
{{name}}: {{mappedName}},
{{else}}
{{name}},
{{/if}}
{{/math}}
config
})
Object.assign(mathWithTransform, math, {
{{#transformFactories}}
{{name}}: {{factoryName ~}}
({{braceOpen}}{{#if dependencies}} {{/if ~}}
{{#dependencies ~}}
{{name}}{{#unless @last}}, {{/unless ~}}
{{/dependencies ~}}
{{#if dependencies}} {{/if ~}}}){{#unless @last}},{{/unless}}
{{/transformFactories}}
})
Object.assign(classes, {
{{#classes}}
{{name}}{{#unless @last}},{{/unless}}
{{/classes}}
})
Chain.createProxy(math)
export { embeddedDocs as docs } from '../expression/embeddedDocs/embeddedDocs'
`)
exports.generateEntryFiles = function () {
const factoriesAny = require('../lib/factoriesAny')
const factoriesNumber = require('../lib/factoriesNumber')
generateDependenciesFiles({
suffix: 'Any',
factories: factoriesAny,
entryFolder: ENTRY_FOLDER
})
generateDependenciesFiles({
suffix: 'Number',
factories: factoriesNumber,
entryFolder: ENTRY_FOLDER
})
generateFunctionsFiles({
suffix: 'Any',
factories: factoriesAny,
entryFolder: ENTRY_FOLDER
})
generateFunctionsFiles({
suffix: 'Number',
factories: factoriesNumber,
entryFolder: ENTRY_FOLDER
})
}
/**
* Generate index files like
* dependenciesAny.generated.js
* dependenciesNumber.generated.js
* And the individual files for every dependencies collection.
*/
function generateDependenciesFiles ({ suffix, factories, entryFolder }) {
const braceOpen = '{' // a hack to be able to create a single brace open character in handlebars
// a map containing:
// {
// 'sqrt': true,
// 'subset': true,
// ...
// }
const exists = {}
Object.keys(factories).forEach(factoryName => {
const factory = factories[factoryName]
exists[factory.fn] = true
})
mkdirSyncIfNotExists(path.join(entryFolder, 'dependencies' + suffix))
const data = {
suffix,
factories: Object.keys(factories).map((factoryName) => {
const factory = factories[factoryName]
return {
suffix,
factoryName,
braceOpen,
name: getDependenciesName(factoryName, factories), // FIXME: rename name with dependenciesName, and functionName with name
fileName: './dependencies' + suffix + '/' + getDependenciesFileName(factoryName) + '.generated',
eslintComment: factoryName === 'createSQRT1_2'
? ' // eslint-disable-line camelcase'
: undefined,
dependencies: factory.dependencies
.map(stripOptionalNotation)
.filter(dependency => !IGNORED_DEPENDENCIES[dependency])
.filter(dependency => {
if (!exists[dependency]) {
if (factory.dependencies.indexOf(dependency) !== -1) {
throw new Error(`Required dependency "${dependency}" missing for factory "${factory.fn}" (suffix: ${suffix})`)
}
return false
}
return true
})
.map(dependency => {
const factoryName = findFactoryName(factories, dependency)
const name = getDependenciesName(factoryName, factories)
const fileName = './' + getDependenciesFileName(factoryName) + '.generated'
return {
suffix,
name,
fileName
}
})
}
})
}
// generate a file for every dependency
data.factories.forEach(factoryData => {
const generatedFactory = dependenciesFileTemplate(factoryData)
const p = path.join(entryFolder, factoryData.fileName + '.js')
fs.writeFileSync(p, generatedFactory)
})
// generate a file with links to all dependencies
const generated = dependenciesIndexTemplate(data)
fs.writeFileSync(path.join(entryFolder, 'dependencies' + suffix + '.generated.js'), generated)
}
/**
* Generate index files like
* pureFunctionsAny.generated.js
* impureFunctionsAny.generated.js
*/
function generateFunctionsFiles ({ suffix, factories, entryFolder }) {
const braceOpen = '{' // a hack to be able to create a single brace open character in handlebars
const sortedFactories = sortFactories(values(factories))
// sort the factories, and split them in three groups:
// - transform: the transform functions
// - impure: the functions that depend on `math` or `mathWithTransform` (directly or indirectly),
// - pure: the rest
const pureFactories = []
const impureFactories = []
const transformFactories = []
sortedFactories
.filter(factory => !DEPRECATED_FACTORIES[factory.fn])
.forEach(factory => {
if (isTransform(factory)) {
transformFactories.push(factory)
} else if (
contains(factory.dependencies, 'math') ||
contains(factory.dependencies, 'mathWithTransform') ||
contains(factory.dependencies, 'classes') ||
isTransform(factory) ||
factory.dependencies.some(dependency => {
return impureFactories.find(f => f.fn === stripOptionalNotation(dependency))
})
) {
impureFactories.push(factory)
} else {
pureFactories.push(factory)
}
})
// a map containing:
// {
// 'sqrt': true,
// 'subset': true,
// ...
// }
const pureExists = {
config: true
}
pureFactories.forEach(factory => {
pureExists[factory.fn] = true
})
const impureExists = {
...pureExists
}
impureFactories.forEach(factory => {
impureExists[factory.fn] = true
})
const math = sortedFactories
.filter(factory => !isClass(factory))
.filter(factory => !isTransform(factory))
.map(factory => {
return {
name: factory.fn,
renamed: DEPRECATED_FACTORIES[factory.fn],
mappedName: FACTORY_NAMES_ES6_MAPPING[factory.fn]
}
})
const classes = sortedFactories
.filter(factory => isClass(factory))
.map(factory => ({ name: factory.fn }))
const data = {
suffix,
pureFactories: pureFactories.map(factory => {
const name = FACTORY_NAMES_ES6_MAPPING[factory.fn] || factory.fn
return {
braceOpen,
factoryName: findKey(factories, factory), // TODO: find a better way to match the factory names
eslintComment: name === 'SQRT1_2'
? ' // eslint-disable-line camelcase'
: undefined,
name,
dependencies: factory.dependencies
.map(stripOptionalNotation)
.filter(dependency => !IGNORED_DEPENDENCIES_ES6[dependency])
.filter(dependency => {
// TODO: this code is duplicated. extract it in a separate function
if (!pureExists[dependency]) {
if (factory.dependencies.indexOf(dependency) !== -1) {
throw new Error(`Required dependency "${dependency}" missing for factory "${factory.fn}" (suffix: ${suffix})`)
}
return false
}
return true
})
.map(dependency => ({ name: dependency }))
}
}),
impureFactories: impureFactories.map(factory => {
const name = FACTORY_NAMES_ES6_MAPPING[factory.fn] || factory.fn
return {
braceOpen,
factoryName: findKey(factories, factory), // TODO: find a better way to match the factory names
eslintComment: name === 'SQRT1_2'
? ' // eslint-disable-line camelcase'
: undefined,
name,
dependencies: factory.dependencies
.map(stripOptionalNotation)
.filter(dependency => !IGNORED_DEPENDENCIES_ES6[dependency])
.filter(dependency => {
if (dependency === 'math' || dependency === 'mathWithTransform' || dependency === 'classes') {
return true
}
// TODO: this code is duplicated. extract it in a separate function
if (!impureExists[dependency]) {
// if (factory.dependencies.indexOf(dependency) !== -1) {
// throw new Error(`Required dependency "${dependency}" missing for factory "${factory.fn}"`)
// }
return false
}
return true
})
.map(dependency => ({ name: dependency }))
}
}),
transformFactories: transformFactories.map(factory => {
const name = FACTORY_NAMES_ES6_MAPPING[factory.fn] || factory.fn
return {
braceOpen,
factoryName: findKey(factories, factory), // TODO: find a better way to match the factory names
eslintComment: name === 'SQRT1_2'
? ' // eslint-disable-line camelcase'
: undefined,
name,
dependencies: factory.dependencies
.map(stripOptionalNotation)
.filter(dependency => !IGNORED_DEPENDENCIES_ES6[dependency])
.filter(dependency => {
if (dependency === 'math' || dependency === 'mathWithTransform' || dependency === 'classes') {
return true
}
// TODO: this code is duplicated. extract it in a separate function
if (!impureExists[dependency]) {
// if (factory.dependencies.indexOf(dependency) !== -1) {
// throw new Error(`Required dependency "${dependency}" missing for factory "${factory.fn}"`)
// }
return false
}
return true
})
.map(dependency => ({ name: dependency }))
}
}),
math,
classes
}
// create file with all functions: functionsAny.generated.js, functionsNumber.generated.js
fs.writeFileSync(path.join(entryFolder, 'pureFunctions' + suffix + '.generated.js'), pureFunctionsTemplate(data))
// create file with all functions: impureFunctions.generated.js, impureFunctions.generated.js
fs.writeFileSync(path.join(entryFolder, 'impureFunctions' + suffix + '.generated.js'), impureFunctionsTemplate(data))
}
function getDependenciesName (factoryName, factories) {
if (!factories) {
throw new Error(`Cannot create dependencies name: factories is undefined`)
}
const factory = factories[factoryName]
const transform = isTransform(factory) ? 'Transform' : ''
return factory.fn + transform + 'Dependencies'
}
function getDependenciesFileName (factoryName) {
if (factoryName.indexOf('create') !== 0) {
throw new Error(`Cannot create dependencies name from factoryName "${factoryName}". Should start with "create..."`)
}
return 'dependencies' + factoryName.slice(6)
}
function findFactoryName (factories, name) {
for (const factoryName in factories) {
if (factories.hasOwnProperty(factoryName)) {
if (factories[factoryName].fn === name) {
return factoryName
}
}
}
return undefined
}
function findKey (object, value) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
if (object[key] === value) {
return key
}
}
}
return undefined
}
/**
* Sort the factories in such a way that their dependencies are resolved.
* @param {Array} factories
* @return {Array} Returns sorted factories
*/
exports.sortFactories = sortFactories
function sortFactories (factories) {
const loaded = {}
const leftOverFactories = factories.slice()
const sortedFactories = []
const exists = {}
factories.forEach(factory => {
exists[factory.fn] = true
})
function allDependenciesResolved (dependencies) {
return dependencies.every(dependency => {
const d = stripOptionalNotation(dependency)
return loaded[d] === true ||
IGNORED_DEPENDENCIES[d] === true ||
(dependency[0] === '?' && !exists[d])
})
}
let changed = true
while (leftOverFactories.length > 0 && changed) {
changed = false
for (let i = 0; i < leftOverFactories.length; i++) {
const factory = leftOverFactories[i]
if (allDependenciesResolved(factory.dependencies)) {
if (!isTransform(factory)) {
loaded[factory.fn] = true
}
sortedFactories.push(factory)
leftOverFactories.splice(i, 1)
changed = true
}
}
}
if (leftOverFactories.length > 0) {
const first = leftOverFactories[0]
throw new Error('Cannot resolve dependencies of factory "' + first.fn + '". ' +
'Dependencies: ' + first.dependencies.map(d => '"' + d + '"').join(', '))
}
return sortedFactories
}
function values (object) {
return Object.keys(object).map(key => object[key])
}
function contains (array, item) {
return array.indexOf(item) !== -1
}
function isTransform (factory) {
return (factory && factory.meta && factory.meta.isTransformFunction === true) || false
}
function isClass (factory) {
return (factory && factory.meta && factory.meta.isClass === true) || false
}
function stripOptionalNotation (dependency) {
return dependency && dependency[0] === '?'
? dependency.slice(1)
: dependency
}
// function will check if a directory exists, and create it if it doesn't
// https://blog.raananweber.com/2015/12/15/check-if-a-directory-exists-in-node-js/
function mkdirSyncIfNotExists (directory) {
try {
fs.statSync(directory)
} catch (error) {
fs.mkdirSync(directory)
}
}