Comparing version 1.4.0 to 1.5.0
18
index.js
@@ -7,12 +7,2 @@ /* | ||
function requireScript(scriptId) { | ||
switch(scriptId) { | ||
case 'common': return require('./src/jsu_common.js'); | ||
case 'csv-parser': return require('./src/jsu_csv_parser.js'); | ||
case 'event': return require('./src/jsu_event.js'); | ||
case 'latex': return require('./src/jsu_latex.js'); | ||
} | ||
throw new Error('Failed to load script id: ' + scriptId); | ||
} | ||
// expose the same interface as when using <script> tags to embed each script; | ||
@@ -23,6 +13,6 @@ // however, a script is only loaded when requested by accessing the | ||
module.exports = { | ||
get Common() { return requireScript('common'); }, | ||
get CsvParser() { return requireScript('csv-parser'); }, | ||
get Event() { return requireScript('event'); }, | ||
get Latex() { return requireScript('latex'); }, | ||
get Common() { return require('./src/jsu_common.js'); }, | ||
get CsvParser() { return require('./src/jsu_csv_parser.js'); }, | ||
get Event() { return require('./src/jsu_event.js'); }, | ||
get Latex() { return require('./src/jsu_latex.js'); }, | ||
}; |
{ | ||
"name": "jsupack", | ||
"version": "1.4.0", | ||
"version": "1.5.0", | ||
"license": "MIT", | ||
@@ -12,3 +12,17 @@ "description": "JavaScript Utilities", | ||
"scripts": { | ||
"test": "echo Running tests... && mocha ./tests/_index.js ./tests/jsu*.js && echo Finished!" | ||
"test": "echo Testing APIs... && npm run test:_index -s && npm run test:common -s && npm run test:csv_parser -s && npm run test:event -s && npm run test:latex -s && echo APIs tested!", | ||
"test:_index": "mocha ./tests/_index.js", | ||
"test:common": "mocha ./tests/jsu_common.js", | ||
"test:csv_parser": "mocha ./tests/jsu_csv_parser.js", | ||
"test:csv_parser_quick": "mocha ./tests/jsu_csv_parser.js --quick-check", | ||
"test:event": "mocha ./tests/jsu_event.js", | ||
"test:latex": "mocha ./tests/jsu_latex.js", | ||
"browser:gen-tests": "bash ./scripts/build.sh browserify-tests", | ||
"browser:check-tests": "karma start ./.karma.conf.js", | ||
"browser:run-tests": "npm run browser:gen-tests && npm run browser:check-tests", | ||
"code:check-scripts": "echo Testing scripts... && node -e \"console.log('')\" && bash ./scripts/defs.test.sh && node -e \"console.log('')\" && echo Scripts tested!", | ||
"code:check-sources": "bash ./scripts/build.sh validate-sources", | ||
"code:gen-main-test-coverage": "nyc --reporter=lcov --reporter=text npm run test", | ||
"code:check-state": "npm run code:check-scripts && npm run code:check-sources && npm run code:gen-main-test-coverage && npm run browser:run-tests", | ||
"clean": "rm -rf .nyc_output coverage tests_browserified" | ||
}, | ||
@@ -20,2 +34,9 @@ "repository": { | ||
"keywords": [ | ||
"client-side", | ||
"server-side", | ||
"ecmascript", | ||
"es2009", | ||
"es2011", | ||
"es5", | ||
"es5.1", | ||
"javascript", | ||
@@ -36,6 +57,14 @@ "js", | ||
"devDependencies": { | ||
"browser-or-node": "^2.0.0", | ||
"browserify": "^17.0.0", | ||
"jsdom": "^20.0.0", | ||
"jshint": "^2.13.5", | ||
"karma": "^6.4.1", | ||
"karma-chrome-launcher": "^3.1.1", | ||
"karma-firefox-launcher": "^2.1.2", | ||
"karma-mocha": "^2.0.1", | ||
"mocha": "^10.0.0", | ||
"nyc": "^15.1.0", | ||
"sinon": "^14.0.0" | ||
} | ||
} |
# jsu | ||
*JavaScript utility library for ECMAScript 5.1 (or higher, due to backwards | ||
compatibility). See the list of browsers supporting ES5 [here](https://caniuse.com/es5).* | ||
jsu (read JSU) stands for and provides JavaScript Utilities. It targets older | ||
browser versions first unless there is a security risk, missing functionality | ||
or poor performance in newer browsers. Support for Internet Explorer is not an | ||
option as Microsoft Edge is its successor. | ||
or poor performance in newer browsers. Explicit support (dedicated code) is not | ||
planned for Internet Explorer as Microsoft Edge is its successor. | ||
@@ -24,19 +27,5 @@ The features provided by jsu were originally part of the [nvc](https://github.com/arlogy/nvc) | ||
Documentation and examples are available [here](doc). | ||
Documentation and examples are available [here](doc). Version changelog is | ||
[here](CHANGELOG.md). | ||
Version changelog is [here](CHANGELOG.md), with each version corresponding to a | ||
[tag](https://github.com/arlogy/jsu/tags). | ||
## Tests | ||
```bash | ||
git clone <project_git_uri> | ||
cd <project_dir>/ | ||
npm install | ||
npm run test # run all tests | ||
``` | ||
We use the following Node.js packages which were all installed using `npm install <package> --save-dev`. | ||
- mocha for unit testing. | ||
- sinon for spies, stubs and mocks. | ||
- jsdom to imitate in a Node.js environment the behavior of a browser. | ||
See [this file](DEVELOP.md) for development and maintenance notes. |
@@ -46,3 +46,3 @@ /* | ||
// --- UI --- | ||
// --- CSS Visibility --- | ||
@@ -67,7 +67,8 @@ API.setEltVisible = function(elt, vis, dsp) { | ||
API.isNumber = Number.isFinite || function(value) { // polyfill for Number.isFinite() | ||
return typeof value === 'number' && isFinite(value); | ||
// Number.isFinite() is used because we want to define a number as a | ||
// finite value (strings excluded) | ||
API._getIsNumberImpl = function() { // introduced so that all implementations can be easily tested | ||
return Number.isFinite || function(value) { // polyfill for Number.isFinite() | ||
return typeof value === 'number' && isFinite(value); | ||
}; // Number.isFinite() is used because we want to define a number as a finite value (strings excluded) | ||
}; | ||
API.isNumber = API._getIsNumberImpl(); | ||
@@ -81,5 +82,8 @@ API.isNumberAlike = function(value) { | ||
API.isArray = Array.isArray || function(arg) { // polyfill for Array.isArray() | ||
return Object.prototype.toString.call(arg) === '[object Array]'; | ||
API._getIsArrayImpl = function() { // introduced so that all implementations can be easily tested | ||
return Array.isArray || function(value) { // polyfill for Array.isArray() | ||
return Object.prototype.toString.call(value) === '[object Array]'; | ||
}; | ||
}; | ||
API.isArray = API._getIsArrayImpl(); | ||
@@ -229,11 +233,11 @@ API.isCssColor = function(value) { | ||
case 'symbol': { | ||
var copy = cache.get(value); | ||
return copy !== undefined ? copy : cache.add(value, Symbol(value.description)); | ||
var copy1 = cache.get(value); | ||
return copy1 !== undefined ? copy1 : cache.add(value, Symbol(value.description)); | ||
} | ||
case 'object': { | ||
default: { // case 'object' | ||
if(value === null) return value; | ||
var copy = cache.get(value); | ||
if(copy !== undefined) return copy; | ||
var copy2 = cache.get(value); | ||
if(copy2 !== undefined) return copy2; | ||
if(value instanceof Boolean) return cache.add(value, new Boolean(value.valueOf())); | ||
@@ -244,12 +248,12 @@ if(value instanceof Date) return cache.add(value, new Date(value.valueOf())); | ||
var i = undefined; | ||
var i = -1; | ||
if(_isArray(value)) { | ||
copy = []; | ||
cache.add(value, copy); // cache data before the recursive _cloneDeep() calls below | ||
copy2 = []; | ||
cache.add(value, copy2); // cache data before the recursive _cloneDeep() calls below | ||
var valueLen = value.length; | ||
for(i = 0; i < valueLen; i++) { | ||
copy.push(_cloneDeep(value[i], cache, cloneCustomImpl)); | ||
copy2.push(_cloneDeep(value[i], cache, cloneCustomImpl)); | ||
} | ||
} | ||
else if(cloneCustomImpl && ((copy = cloneCustomImpl(value, cache)) !== undefined)) { | ||
else if(cloneCustomImpl && ((copy2 = cloneCustomImpl(value, cache)) !== undefined)) { | ||
// nothing to do because value is already cloned | ||
@@ -264,4 +268,4 @@ } | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects | ||
copy = {}; | ||
cache.add(value, copy); // cache data before the recursive _cloneDeep() calls below | ||
copy2 = {}; | ||
cache.add(value, copy2); // cache data before the recursive _cloneDeep() calls below | ||
var valueKeys = Object.keys(value); | ||
@@ -271,10 +275,7 @@ var valueKeysLen = valueKeys.length; | ||
var prop = valueKeys[i]; | ||
copy[prop] = _cloneDeep(value[prop], cache, cloneCustomImpl); | ||
copy2[prop] = _cloneDeep(value[prop], cache, cloneCustomImpl); | ||
} | ||
} | ||
return copy; | ||
return copy2; | ||
} | ||
default: // will not be reached but retained anyway | ||
return value; | ||
} | ||
@@ -281,0 +282,0 @@ } |
@@ -20,6 +20,6 @@ /* | ||
function() { | ||
// internal utility functions; regarding strings, please note that they | ||
// should be transformed using toString() to allow consistent comparison of | ||
// string primitives and string objects using the === operator or the switch | ||
// statement for example | ||
// internal utility functions; useful-note: regarding strings, be aware that | ||
// they should be transformed using toString() to allow consistent | ||
// comparison of string primitives and string objects using the === operator | ||
// or the switch statement for example | ||
function isArray(val) { return Object.prototype.toString.call(val) === '[object Array]'; } | ||
@@ -52,52 +52,46 @@ function hasNoDuplicates(arr) { | ||
// reject invalid options | ||
// convert object strings to primitive strings (see useful-note) | ||
function validate() { | ||
[fieldDels, fieldSeps, lineSeps].forEach(function(arr) { | ||
if(!isArray(arr)) throw new RangeError( | ||
'The field or line separators are not an array, or there is an internal error on the field delimiter' | ||
); | ||
fieldDels = fieldDels.map(function(val) { return val instanceof String ? val.toString() : val; }); | ||
if(isArray(fieldSeps)) fieldSeps = fieldSeps.map(function(val) { return val instanceof String ? val.toString() : val; }); | ||
if(isArray(lineSeps)) lineSeps = lineSeps.map(function(val) { return val instanceof String ? val.toString() : val; }); | ||
if(!arr.every(isStringAndNonEmpty)) throw new RangeError( | ||
'Only non-empty strings are allowed for the field delimiter, field separators and line separators' | ||
); | ||
// validate options | ||
if(hasDuplicates(arr)) throw new RangeError( | ||
'The field or line separators contain duplicates, or there is an internal error on the field delimiter' | ||
); | ||
}); | ||
[fieldDels, fieldSeps, lineSeps].forEach(function(arr) { | ||
if(!isArray(arr)) throw new RangeError( | ||
'The field or line separators are not an array, or there is an internal error on the field delimiter' | ||
); | ||
[ | ||
[fieldDels, fieldSeps, lineSeps], | ||
[fieldSeps, fieldDels, lineSeps], | ||
[lineSeps, fieldDels, fieldSeps], | ||
].forEach(function(arr) { | ||
if(!arr[0].every(function(val) { return arr[1].indexOf(val) === -1 && arr[2].indexOf(val) === -1; })) | ||
throw new RangeError('Values cannot be shared between field delimiter, field separators and line separators'); | ||
}); | ||
} | ||
if(!arr.every(isStringAndNonEmpty)) throw new RangeError( | ||
'Only non-empty strings are allowed for the field delimiter, field separators and line separators' | ||
); | ||
validate(); | ||
if(hasDuplicates(arr)) throw new RangeError( | ||
'The field or line separators contain duplicates, or there is an internal error on the field delimiter' | ||
); | ||
}); | ||
// convert object strings to primitive strings | ||
[ | ||
[fieldDels, fieldSeps, lineSeps], | ||
[fieldSeps, fieldDels, lineSeps], | ||
[lineSeps, fieldDels, fieldSeps], | ||
].forEach(function(arr) { | ||
if(!arr[0].every(function(val) { return arr[1].indexOf(val) === -1 && arr[2].indexOf(val) === -1; })) | ||
throw new RangeError('Values cannot be shared between field delimiter, field separators and line separators'); | ||
}); | ||
fieldDels = fieldDels.map(function(val) { return val.toString(); }); | ||
fieldSeps = fieldSeps.map(function(val) { return val.toString(); }); | ||
lineSeps = lineSeps.map(function(val) { return val.toString(); }); | ||
// set other options accordingly | ||
// set the properties of this parser | ||
var stdLineSeps = ['\r', '\n', '\r\n']; // standard line separators (aka line breaks) | ||
smartRegex = smartRegex | ||
&& fieldDels.every(function(val) { return val.length === 1; }) | ||
&& fieldSeps.every(function(val) { return val.length === 1; }) | ||
&& lineSeps.every(function(val) { return stdLineSeps.indexOf(val) !== -1; }) | ||
; | ||
var regexOptimized = smartRegex && | ||
fieldDels.every(function(val) { return val.length === 1; }) && | ||
fieldSeps.every(function(val) { return val.length === 1; }) && | ||
lineSeps.every(function(val) { return stdLineSeps.indexOf(val) !== -1; }); // optimize regex? | ||
var regexPatterns = []; | ||
if(smartRegex) { | ||
if(regexOptimized) { | ||
var regexFieldDels = fieldDels.map(function(val) { return escapeRegExp(val); }).join(''); | ||
var regexFieldSeps = fieldSeps.map(function(val) { return escapeRegExp(val); }).join(''); | ||
regexPatterns = [ | ||
'[^' + regexFieldDels + regexFieldSeps + '\n\r]+', // line breaks are characters from stdLineSeps | ||
'[^' + regexFieldDels + regexFieldSeps + '\n\r]+', // the line breaks are characters from stdLineSeps | ||
'[' + regexFieldDels + regexFieldSeps + ']', | ||
@@ -114,5 +108,9 @@ stdLineSeps.slice(0).sort().reverse().join('|'), // see (1) below | ||
regexPatterns.sort().reverse(); // see (1) below | ||
regexPatterns.push('.'); | ||
regexPatterns.push('.', '\n', '\r'); // it is necessary to explicitly account for line break characters | ||
// - because the dot (.) metacharacter does not match them | ||
// note that ECMAScript 2018+ has an 's' (dotAll) regex flag to enable this | ||
} | ||
// set the properties of this parser | ||
this._fieldDel = fieldDels[0]; | ||
@@ -122,2 +120,3 @@ this._fieldSeps = fieldSeps; | ||
this._smartRegex = smartRegex; | ||
this._regexOptimized = regexOptimized; | ||
this._regexPattern = regexPatterns.join('|'); | ||
@@ -140,2 +139,3 @@ this._skipEmptyLinesWhen = skipEmptyLinesWhen; | ||
'smartRegex': this._smartRegex, | ||
'regexOptimized': this._regexOptimized, | ||
'regexPattern': this._regexPattern, | ||
@@ -155,5 +155,7 @@ 'skipEmptyLinesWhen': this._skipEmptyLinesWhen, | ||
var matchStr = null; | ||
var lineSepRead = null; | ||
while((match = regex.exec(str)) !== null) { | ||
matchStr = match[0]; | ||
this._currLineStr += matchStr; | ||
lineSepRead = false; | ||
this._currLineDataPending = true; | ||
switch(this._currState) { | ||
@@ -164,3 +166,3 @@ case 'q0': // initial state | ||
case fieldSeps.indexOf(matchStr) !== -1: this._saveNewField(); break; | ||
case lineSeps.indexOf(matchStr) !== -1: this._saveNewLine(); break; | ||
case lineSeps.indexOf(matchStr) !== -1: this._saveNewLine(); lineSepRead = true; break; | ||
default: this._currMatch += matchStr; this._currState = 'q1'; break; | ||
@@ -173,3 +175,3 @@ } | ||
case fieldSeps.indexOf(matchStr) !== -1: this._saveNewField(); this._currState = 'q0'; break; | ||
case lineSeps.indexOf(matchStr) !== -1: this._saveNewLine(); this._currState = 'q0'; break; | ||
case lineSeps.indexOf(matchStr) !== -1: this._saveNewLine(); this._currState = 'q0'; lineSepRead = true; break; | ||
default: this._currMatch += matchStr; break; | ||
@@ -188,5 +190,5 @@ } | ||
switch(true) { | ||
case fieldDel === matchStr: this._currMatch += matchStr; this._currState = 'q2'; break; | ||
case fieldDel === matchStr: this._currMatch += fieldDel; this._currState = 'q2'; break; | ||
case fieldSeps.indexOf(matchStr) !== -1: this._saveNewField(); this._currState = 'q0'; break; | ||
case lineSeps.indexOf(matchStr) !== -1: this._saveNewLine(); this._currState = 'q0'; break; | ||
case lineSeps.indexOf(matchStr) !== -1: this._saveNewLine(); this._currState = 'q0'; lineSepRead = true; break; | ||
default: | ||
@@ -201,2 +203,5 @@ this._saveNewWarning(CsvParser._getUnescapedDelimiterInfo(this._records.length + 1, fieldDel, matchStr)); | ||
} | ||
if(!lineSepRead) { | ||
this._currLineStr += matchStr; | ||
} | ||
} | ||
@@ -218,6 +223,3 @@ }; | ||
CsvParser.prototype.hasPendingData = function() { | ||
// we check states q2 and q3 for cases where the only or last line read | ||
// by readChunk() is '"' or '""'; so _currMatch and _currLineFields are | ||
// empty | ||
return this._currMatch !== '' || this._currLineFields.length !== 0 || this._currState === 'q2' || this._currState === 'q3'; | ||
return this._currLineDataPending; | ||
}; | ||
@@ -237,5 +239,6 @@ | ||
CsvParser.prototype.getWarningsRef = function() { | ||
// always include temporary warnings on the current line because data | ||
// have already been parsed and the temporary warnings help understand | ||
// why getRecordsRef() doesn't show the expected output for example | ||
// always include temporary warnings on the current line: this is useful | ||
// when data have been parsed but not reflected in the value returned by | ||
// getRecordsRef(); so the temporary warnings would help understand why | ||
// getRecordsRef() does not contain the expected output | ||
return this._warnings.concat(this._currLineWarnings); | ||
@@ -247,8 +250,7 @@ }; | ||
return warnings.map(function(obj) { | ||
return { | ||
'context': obj.context, | ||
'type': obj.type, | ||
'message': obj.message, | ||
'linePos': obj.linePos, | ||
}; | ||
var retVal = {}; | ||
for(var prop in obj) { | ||
retVal[prop] = obj[prop]; | ||
} | ||
return retVal; | ||
}); | ||
@@ -258,3 +260,3 @@ }; | ||
CsvParser.prototype.reset = function() { | ||
// temporary data (about the line being parsed) | ||
// data for the line being parsed | ||
this._currState = 'q0'; | ||
@@ -265,4 +267,5 @@ this._currMatch = ''; | ||
this._currLineWarnings = []; | ||
this._currLineDataPending = false; | ||
// final data (about lines already parsed) | ||
// data for lines already parsed | ||
this._records = []; | ||
@@ -297,3 +300,3 @@ this._warnings = []; | ||
this._records.push(this._currLineFields); | ||
this._warnings.push(...this._currLineWarnings); // push each warning | ||
Array.prototype.push.apply(this._warnings, this._currLineWarnings); // push each warning | ||
} | ||
@@ -303,2 +306,3 @@ this._currLineStr = ''; | ||
this._currLineWarnings = []; | ||
this._currLineDataPending = false; | ||
}; | ||
@@ -320,7 +324,7 @@ | ||
CsvParser._getUnescapedDelimiterInfo = function(linePos, delim, c) { | ||
CsvParser._getUnescapedDelimiterInfo = function(linePos, delim, str) { | ||
return CsvParser._getInfo( | ||
'DelimitedField', | ||
'DelimiterNotEscaped', | ||
'Expects field delimiter (' + delim + ') but got character ' + c[0], | ||
'Expects field delimiter (' + delim + ') but got character ' + str[0], | ||
linePos | ||
@@ -334,3 +338,3 @@ ); | ||
'DelimiterNotTerminated', | ||
'Expects field delimiter (' + delim + ') but reached end of line', | ||
'Expects field delimiter (' + delim + ') but no more data to read', | ||
linePos | ||
@@ -337,0 +341,0 @@ ); |
@@ -27,3 +27,3 @@ /* | ||
var JsuCmn = undefined; | ||
var JsuCmn; | ||
if(nodejs) { | ||
@@ -54,3 +54,3 @@ JsuCmn = require('./jsu_common.js'); | ||
}); | ||
greekPatterns = [].concat(...greekPatterns); // flatten array | ||
greekPatterns = greekPatterns.reduce(function(acc, val) { return acc.concat(val); }, []); // flatten array | ||
@@ -205,4 +205,4 @@ return { | ||
if(!patterns) patterns = []; | ||
patterns = patterns.slice(0); // shallow copy | ||
patterns.push(...latexShortcutData.greekLetter.pattern.list); | ||
patterns = patterns.slice(0); // shallow copy so as not to modify the original object | ||
Array.prototype.push.apply(patterns, latexShortcutData.greekLetter.pattern.list); // push each known pattern | ||
patterns.sort(function (a, b) { return b.localeCompare(a); }); // sort in descending order (see regex-alternation-note) | ||
@@ -339,3 +339,3 @@ return API.rewriteLatexCommands(str, patterns.join('|')); | ||
var obj = API.findLatexShortcutSpecialCharsAndIndex(nextStrBeforeCursor); // see (2) below | ||
var k = Math.max(...Object.keys(obj)); | ||
var k = Math.max.apply(null, Object.keys(obj)); | ||
nextCursorPos = cursorPos - nextStrBeforeCursor.substring(k).length; | ||
@@ -342,0 +342,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
52265
11
974
31