hexo/node_modules/warehouse/lib/jsonstream/index.js

203 lines
4.3 KiB
JavaScript
Raw Normal View History

2023-09-25 15:58:56 +08:00
'use strict';
const through2 = require('through2');
const Parser = require('jsonparse');
/**
* Check whether a x and y are equal, or x matches y, or x(y) is truthy.
* @param {boolean | string | RegExp | (args: any[]) => boolean} x
* @param {*} y
* @returns {boolean}
*/
const check = (x, y) => {
if (typeof x === 'string') {
return y === x;
}
if (x && typeof x.exec === 'function') {
return x.exec(y);
}
if (typeof x === 'boolean' || typeof x === 'object') {
return x;
}
if (typeof x === 'function') {
return x(y);
}
return false;
};
module.exports.parse = function(path, map) {
let header, footer;
const parser = new Parser();
const stream = through2.obj(
(chunk, enc, cb) => {
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk);
}
parser.write(chunk);
cb();
},
cb => {
if (header) {
stream.emit('header', header);
}
if (footer) {
stream.emit('footer', footer);
}
if (parser.tState !== Parser.C.START || parser.stack.length > 0) {
cb(new Error('Incomplete JSON'));
return;
}
cb();
}
);
if (typeof path === 'string') {
path = path.split('.').map(e => {
if (e === '$*') {
return { emitKey: true };
}
if (e === '*') {
return true;
}
if (e === '') {
// '..'.split('.') returns an empty string
return { recurse: true };
}
return e;
});
}
if (!path || !path.length) {
path = null;
}
parser.onValue = function(value) {
if (!this.root) { stream.root = value; }
if (!path) return;
let i = 0; // iterates on path
let j = 0; // iterates on stack
let emitKey = false;
let emitPath = false;
while (i < path.length) {
const key = path[i];
let c;
j++;
if (key && !key.recurse) {
c = j === this.stack.length ? this : this.stack[j];
if (!c) return;
if (!check(key, c.key)) {
setHeaderFooter(c.key, value);
return;
}
emitKey = !!key.emitKey;
emitPath = !!key.emitPath;
i++;
} else {
i++;
const nextKey = path[i];
if (!nextKey) return;
// eslint-disable-next-line no-constant-condition
while (true) {
c = j === this.stack.length ? this : this.stack[j];
if (!c) return;
if (check(nextKey, c.key)) {
i++;
if (!Object.isFrozen(this.stack[j])) {
this.stack[j].value = null;
}
break;
} else {
setHeaderFooter(c.key, value);
}
j++;
}
}
}
// emit header
if (header) {
stream.emit('header', header);
header = false;
}
if (j !== this.stack.length) return;
const actualPath = this.stack.slice(1).map(element => element.key);
actualPath.push(this.key);
let data = this.value[this.key];
if (data != null) {
if ((data = map ? map(data, actualPath) : data) != null) {
if (emitKey || emitPath) {
data = {
value: data
};
if (emitKey) {
data.key = this.key;
}
if (emitPath) {
data.path = actualPath;
}
}
stream.push(data);
}
}
delete this.value[this.key];
for (const k in this.stack) {
if (!Object.isFrozen(this.stack[k])) {
this.stack[k].value = null;
}
}
};
parser._onToken = parser.onToken;
parser.onToken = function(token, value) {
parser._onToken(token, value);
if (this.stack.length === 0) {
if (stream.root) {
if (!path) { stream.push(stream.root); }
stream.root = null;
}
}
};
parser.onError = function(err) {
if (err.message.includes('at position')) {
err.message = 'Invalid JSON (' + err.message + ')';
}
stream.destroy(err);
};
return stream;
function setHeaderFooter(key, value) {
// header has not been emitted yet
if (header !== false) {
header = header || {};
header[key] = value;
}
// footer has not been emitted yet but header has
if (footer !== false && header === false) {
footer = footer || {};
footer[key] = value;
}
}
};