source: frontend/node_modules/bfj/src/match.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 6.7 KB
Line 
1'use strict'
2
3const check = require('check-types')
4const DataStream = require('./datastream')
5const events = require('./events')
6const Hoopy = require('hoopy')
7const jsonpath = require('jsonpath')
8const walk = require('./walk')
9
10const DEFAULT_BUFFER_LENGTH = 1024
11
12module.exports = match
13
14/**
15 * Public function `match`.
16 *
17 * Asynchronously parses a stream of JSON data, returning a stream of items
18 * that match the argument. Note that if a value is `null`, it won't be matched
19 * because `null` is used to signify end-of-stream in node.
20 *
21 * @param stream: Readable instance representing the incoming JSON.
22 *
23 * @param selector: Regular expression, string or predicate function used to
24 * identify matches. If a regular expression or string is
25 * passed, only property keys are tested. If a predicate is
26 * passed, both the key and the value are passed to it as
27 * arguments.
28 *
29 * @option minDepth: Number indicating the minimum depth to apply the selector
30 * to. The default is `0`, but setting it to a higher value
31 * can improve performance and reduce memory usage by
32 * eliminating the need to actualise top-level items.
33 *
34 * @option numbers: Boolean, indicating whether numerical keys (e.g. array
35 * indices) should be coerced to strings before testing the
36 * match. Only applies if the `selector` argument is a string
37 * or regular expression.
38 *
39 * @option ndjson: Set this to true to parse newline-delimited JSON,
40 * default is `false`.
41 *
42 * @option yieldRate: The number of data items to process per timeslice,
43 * default is 16384.
44 *
45 * @option bufferLength: The length of the match buffer, default is 1024.
46 *
47 * @option highWaterMark: If set, will be passed to the readable stream constructor
48 * as the value for the highWaterMark option.
49 *
50 * @option Promise: The promise constructor to use, defaults to bluebird.
51 **/
52function match (stream, selector, options = {}) {
53 const keys = []
54 const scopes = []
55 const properties = []
56 const emitter = walk(stream, options)
57 const matches = new Hoopy(options.bufferLength || DEFAULT_BUFFER_LENGTH)
58 let streamOptions
59 const { highWaterMark } = options
60 if (highWaterMark) {
61 streamOptions = { highWaterMark }
62 }
63 const results = new DataStream(read, streamOptions)
64
65 let selectorFunction, selectorPath, selectorString, resume
66 let coerceNumbers = false
67 let awaitPush = true
68 let isEnded = false
69 let length = 0
70 let index = 0
71
72 const minDepth = options.minDepth || 0
73 check.assert.greaterOrEqual(minDepth, 0)
74
75 if (check.function(selector)) {
76 selectorFunction = selector
77 selector = null
78 } else if (check.string(selector)) {
79 check.assert.nonEmptyString(selector)
80
81 if (selector.startsWith('$.')) {
82 selectorPath = jsonpath.parse(selector)
83 check.assert.identical(selectorPath.shift(), {
84 expression: {
85 type: 'root',
86 value: '$',
87 },
88 })
89 selectorPath.forEach((part) => {
90 check.assert.equal(part.scope, 'child')
91 })
92 } else {
93 selectorString = selector
94 coerceNumbers = !! options.numbers
95 }
96
97 selector = null
98 } else {
99 check.assert.instanceStrict(selector, RegExp)
100 coerceNumbers = !! options.numbers
101 }
102
103 emitter.on(events.array, array)
104 emitter.on(events.object, object)
105 emitter.on(events.property, property)
106 emitter.on(events.endArray, endScope)
107 emitter.on(events.endObject, endScope)
108 emitter.on(events.string, value)
109 emitter.on(events.number, value)
110 emitter.on(events.literal, value)
111 emitter.on(events.end, end)
112 emitter.on(events.error, error)
113 emitter.on(events.dataError, dataError)
114
115 return results
116
117 function read () {
118 if (awaitPush) {
119 awaitPush = false
120
121 if (isEnded) {
122 if (length > 0) {
123 after()
124 }
125
126 return endResults()
127 }
128 }
129
130 if (resume) {
131 const resumeCopy = resume
132 resume = null
133 resumeCopy()
134 after()
135 }
136 }
137
138 function after () {
139 if (awaitPush || resume) {
140 return
141 }
142
143 let i
144
145 for (i = 0; i < length && ! resume; ++i) {
146 if (! results.push(matches[i + index])) {
147 pause()
148 }
149 }
150
151 if (i === length) {
152 index = length = 0
153 } else {
154 length -= i
155 index += i
156 }
157 }
158
159 function pause () {
160 resume = emitter.pause()
161 }
162
163 function endResults () {
164 if (! awaitPush) {
165 results.push(null)
166 }
167 }
168
169 function array () {
170 scopes.push([])
171 }
172
173 function object () {
174 scopes.push({})
175 }
176
177 function property (name) {
178 keys.push(name)
179
180 if (scopes.length < minDepth) {
181 return
182 }
183
184 properties.push(name)
185 }
186
187 function endScope () {
188 if (selectorPath) {
189 keys.pop()
190 }
191 value(scopes.pop())
192 }
193
194 function value (v) {
195 let key
196
197 if (scopes.length < minDepth) {
198 return
199 }
200
201 if (scopes.length > 0) {
202 const scope = scopes[scopes.length - 1]
203
204 if (Array.isArray(scope)) {
205 key = scope.length
206 } else {
207 key = properties.pop()
208 }
209
210 scope[key] = v
211 }
212
213 if (v === null) {
214 return
215 }
216
217 if (selectorFunction) {
218 if (selectorFunction(key, v, scopes.length)) {
219 push(v)
220 }
221 } else if (selectorPath) {
222 if (isSelectorPathSatisfied([ ...keys, key ])) {
223 push(v)
224 }
225 } else {
226 if (coerceNumbers && typeof key === 'number') {
227 key = key.toString()
228 }
229
230 if ((selectorString && selectorString === key) || (selector && selector.test(key))) {
231 push(v)
232 }
233 }
234 }
235
236 function isSelectorPathSatisfied (path) {
237 if (selectorPath.length !== path.length) {
238 return false
239 }
240
241 return selectorPath.every(({ expression, operation }, i) => {
242 if (
243 (operation === 'member' && expression.type === 'identifier') ||
244 (operation === 'subscript' && (
245 expression.type === 'string_literal' ||
246 expression.type === 'numeric_literal'
247 ))
248 ) {
249 return path[i] === expression.value
250 }
251
252 if (
253 operation === 'subscript' &&
254 expression.type === 'wildcard' &&
255 expression.value === '*'
256 ) {
257 return true
258 }
259
260 return false
261 })
262 }
263
264 function push (v) {
265 if (length + 1 === matches.length) {
266 pause()
267 }
268
269 matches[index + length++] = v
270
271 after()
272 }
273
274 function end () {
275 isEnded = true
276 endResults()
277 }
278
279 function error (e) {
280 results.emit('error', e)
281 }
282
283 function dataError (e) {
284 results.emit('dataError', e)
285 }
286}
Note: See TracBrowser for help on using the repository browser.