mathjs/bin/cli.js
Jos de Jong 6f00715754
Specify import require paths (continuation of #1941) (#1962)
* Add `.js` extension to source file imports

* Specify package `exports` in `package.json`

Specify package type as `commonjs` (It's good to be specific)

* Move all compiled scripts into `lib` directory

Remove ./number.js (You can use the compiled ones in `./lib/*`)

Tell node that the `esm` directory is type `module` and enable tree shaking.

Remove unused files from packages `files` property

* Allow importing of package.json

* Make library ESM first

* - Fix merge conflicts
- Refactor `bundleAny` into `defaultInstance.js` and `browserBundle.cjs`
- Refactor unit tests to be able to run with plain nodejs (no transpiling)
- Fix browser examples

* Fix browser and browserstack tests

* Fix running unit tests on Node 10 (which has no support for modules)

* Fix node.js examples (those are still commonjs)

* Remove the need for `browserBundle.cjs`

* Generate minified bundle only

* [Security] Bump node-fetch from 2.6.0 to 2.6.1 (#1963)

Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. **This update includes a security fix.**
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* Cleanup console.log

* Add integration tests to test the entry points (commonjs/esm, full/number only)

* Create backward compatibility error messages in the files moved/removed since v8

* Describe breaking changes in HISTORY.md

* Bump karma from 5.2.1 to 5.2.2 (#1965)

Bumps [karma](https://github.com/karma-runner/karma) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/karma-runner/karma/releases)
- [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md)
- [Commits](https://github.com/karma-runner/karma/compare/v5.2.1...v5.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

Co-authored-by: Lee Langley-Rees <lee@greenimp.co.uk>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-09-20 18:01:29 +02:00

433 lines
12 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* math.js
* https://github.com/josdejong/mathjs
*
* Math.js is an extensive math library for JavaScript and Node.js,
* It features real and complex numbers, units, matrices, a large set of
* mathematical functions, and a flexible expression parser.
*
* Usage:
*
* mathjs [scriptfile(s)] {OPTIONS}
*
* Options:
*
* --version, -v Show application version
* --help, -h Show this message
* --tex Generate LaTeX instead of evaluating
* --string Generate string instead of evaluating
* --parenthesis= Set the parenthesis option to
* either of "keep", "auto" and "all"
*
* Example usage:
* mathjs Open a command prompt
* mathjs 1+2 Evaluate expression
* mathjs script.txt Run a script file
* mathjs script1.txt script2.txt Run two script files
* mathjs script.txt > results.txt Run a script file, output to file
* cat script.txt | mathjs Run input stream
* cat script.txt | mathjs > results.txt Run input stream, output to file
*
* @license
* Copyright (C) 2013-2020 Jos de Jong <wjosdejong@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
let scope = {}
const fs = require('fs')
const path = require('path')
const PRECISION = 14 // decimals
/**
* "Lazy" load math.js: only require when we actually start using it.
* This ensures the cli application looks like it loads instantly.
* When requesting help or version number, math.js isn't even loaded.
* @return {*}
*/
function getMath () {
return require('../lib/cjs/defaultInstance.js').default
}
/**
* Helper function to format a value. Regular numbers will be rounded
* to 14 digits to prevent round-off errors from showing up.
* @param {*} value
*/
function format (value) {
const math = getMath()
return math.format(value, {
fn: function (value) {
if (typeof value === 'number') {
// round numbers
return math.format(value, PRECISION)
} else {
return math.format(value)
}
}
})
}
/**
* auto complete a text
* @param {String} text
* @return {[Array, String]} completions
*/
function completer (text) {
const math = getMath()
let matches = []
let keyword
const m = /[a-zA-Z_0-9]+$/.exec(text)
if (m) {
keyword = m[0]
// scope variables
for (const def in scope) {
if (hasOwnProperty(scope, def)) {
if (def.indexOf(keyword) === 0) {
matches.push(def)
}
}
}
// commandline keywords
['exit', 'quit', 'clear'].forEach(function (cmd) {
if (cmd.indexOf(keyword) === 0) {
matches.push(cmd)
}
})
// math functions and constants
const ignore = ['expr', 'type']
for (const func in math.expression.mathWithTransform) {
if (hasOwnProperty(math.expression.mathWithTransform, func)) {
if (func.indexOf(keyword) === 0 && ignore.indexOf(func) === -1) {
matches.push(func)
}
}
}
// units
const Unit = math.Unit
for (const name in Unit.UNITS) {
if (hasOwnProperty(Unit.UNITS, name)) {
if (name.indexOf(keyword) === 0) {
matches.push(name)
}
}
}
for (const name in Unit.PREFIXES) {
if (hasOwnProperty(Unit.PREFIXES, name)) {
const prefixes = Unit.PREFIXES[name]
for (const prefix in prefixes) {
if (hasOwnProperty(prefixes, prefix)) {
if (prefix.indexOf(keyword) === 0) {
matches.push(prefix)
} else if (keyword.indexOf(prefix) === 0) {
const unitKeyword = keyword.substring(prefix.length)
for (const n in Unit.UNITS) {
if (hasOwnProperty(Unit.UNITS, n)) {
if (n.indexOf(unitKeyword) === 0 &&
Unit.isValuelessUnit(prefix + n)) {
matches.push(prefix + n)
}
}
}
}
}
}
}
}
// remove duplicates
matches = matches.filter(function (elem, pos, arr) {
return arr.indexOf(elem) === pos
})
}
return [matches, keyword]
}
/**
* Run stream, read and evaluate input and stream that to output.
* Text lines read from the input are evaluated, and the results are send to
* the output.
* @param input Input stream
* @param output Output stream
* @param mode Output mode
* @param parenthesis Parenthesis option
*/
function runStream (input, output, mode, parenthesis) {
const readline = require('readline')
const rl = readline.createInterface({
input: input || process.stdin,
output: output || process.stdout,
completer: completer
})
if (rl.output.isTTY) {
rl.setPrompt('> ')
rl.prompt()
}
// load math.js now, right *after* loading the prompt.
const math = getMath()
// TODO: automatic insertion of 'ans' before operators like +, -, *, /
rl.on('line', function (line) {
const expr = line.trim()
switch (expr.toLowerCase()) {
case 'quit':
case 'exit':
// exit application
rl.close()
break
case 'clear':
// clear memory
scope = {}
console.log('memory cleared')
// get next input
if (rl.output.isTTY) {
rl.prompt()
}
break
default:
if (!expr) {
break
}
switch (mode) {
case 'evaluate':
// evaluate expression
try {
let node = math.parse(expr)
let res = node.evaluate(scope)
if (math.isResultSet(res)) {
// we can have 0 or 1 results in the ResultSet, as the CLI
// does not allow multiple expressions separated by a return
res = res.entries[0]
node = node.blocks
.filter(function (entry) { return entry.visible })
.map(function (entry) { return entry.node })[0]
}
if (node) {
if (math.isAssignmentNode(node)) {
const name = findSymbolName(node)
if (name !== null) {
scope.ans = scope[name]
console.log(name + ' = ' + format(scope[name]))
} else {
scope.ans = res
console.log(format(res))
}
} else if (math.isHelp(res)) {
console.log(res.toString())
} else {
scope.ans = res
console.log(format(res))
}
}
} catch (err) {
console.log(err.toString())
}
break
case 'string':
try {
const string = math.parse(expr).toString({ parenthesis: parenthesis })
console.log(string)
} catch (err) {
console.log(err.toString())
}
break
case 'tex':
try {
const tex = math.parse(expr).toTex({ parenthesis: parenthesis })
console.log(tex)
} catch (err) {
console.log(err.toString())
}
break
}
}
// get next input
if (rl.output.isTTY) {
rl.prompt()
}
})
rl.on('close', function () {
console.log()
process.exit(0)
})
}
/**
* Find the symbol name of an AssignmentNode. Recurses into the chain of
* objects to the root object.
* @param {AssignmentNode} node
* @return {string | null} Returns the name when found, else returns null.
*/
function findSymbolName (node) {
const math = getMath()
let n = node
while (n) {
if (math.isSymbolNode(n)) {
return n.name
}
n = n.object
}
return null
}
/**
* Output application version number.
* Version number is read version from package.json.
*/
function outputVersion () {
fs.readFile(path.join(__dirname, '/../package.json'), function (err, data) {
if (err) {
console.log(err.toString())
} else {
const pkg = JSON.parse(data)
const version = pkg && pkg.version ? pkg.version : 'unknown'
console.log(version)
}
process.exit(0)
})
}
/**
* Output a help message
*/
function outputHelp () {
console.log('math.js')
console.log('https://mathjs.org')
console.log()
console.log('Math.js is an extensive math library for JavaScript and Node.js. It features ')
console.log('real and complex numbers, units, matrices, a large set of mathematical')
console.log('functions, and a flexible expression parser.')
console.log()
console.log('Usage:')
console.log(' mathjs [scriptfile(s)|expression] {OPTIONS}')
console.log()
console.log('Options:')
console.log(' --version, -v Show application version')
console.log(' --help, -h Show this message')
console.log(' --tex Generate LaTeX instead of evaluating')
console.log(' --string Generate string instead of evaluating')
console.log(' --parenthesis= Set the parenthesis option to')
console.log(' either of "keep", "auto" and "all"')
console.log()
console.log('Example usage:')
console.log(' mathjs Open a command prompt')
console.log(' mathjs 1+2 Evaluate expression')
console.log(' mathjs script.txt Run a script file')
console.log(' mathjs script.txt script2.txt Run two script files')
console.log(' mathjs script.txt > results.txt Run a script file, output to file')
console.log(' cat script.txt | mathjs Run input stream')
console.log(' cat script.txt | mathjs > results.txt Run input stream, output to file')
console.log()
process.exit(0)
}
/**
* Process input and output, based on the command line arguments
*/
const scripts = [] // queue of scripts that need to be processed
let mode = 'evaluate' // one of 'evaluate', 'tex' or 'string'
let parenthesis = 'keep'
let version = false
let help = false
process.argv.forEach(function (arg, index) {
if (index < 2) {
return
}
switch (arg) {
case '-v':
case '--version':
version = true
break
case '-h':
case '--help':
help = true
break
case '--tex':
mode = 'tex'
break
case '--string':
mode = 'string'
break
case '--parenthesis=keep':
parenthesis = 'keep'
break
case '--parenthesis=auto':
parenthesis = 'auto'
break
case '--parenthesis=all':
parenthesis = 'all'
break
// TODO: implement configuration via command line arguments
default:
scripts.push(arg)
}
})
if (version) {
outputVersion()
} else if (help) {
outputHelp()
} else if (scripts.length === 0) {
// run a stream, can be user input or pipe input
runStream(process.stdin, process.stdout, mode, parenthesis)
} else {
fs.stat(scripts[0], function (e, f) {
if (e) {
console.log(getMath().evaluate(scripts.join(' ')).toString())
} else {
// work through the queue of scripts
scripts.forEach(function (arg) {
// run a script file
runStream(fs.createReadStream(arg), process.stdout, mode, parenthesis)
})
}
})
}
// helper function to safely check whether an object as a property
// copy from the function in object.js which is ES6
function hasOwnProperty (object, property) {
return object && Object.hasOwnProperty.call(object, property)
}