bfj 5.3.0 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +1 -1
- package/.gitlab-ci.yml +25 -0
- package/AUTHORS +1 -1
- package/CONTRIBUTING.md +4 -4
- package/HISTORY.md +46 -0
- package/README.md +172 -66
- package/package.json +9 -9
- package/src/datastream.js +17 -0
- package/src/eventify.js +13 -6
- package/src/events.js +1 -0
- package/src/index.js +1 -0
- package/src/jsonstream.js +6 -12
- package/src/match.js +218 -0
- package/src/parse.js +1 -0
- package/src/stream.js +23 -0
- package/src/streamify.js +26 -13
- package/src/stringify.js +16 -12
- package/src/unpipe.js +1 -1
- package/src/walk.js +2 -2
- package/src/write.js +16 -13
- package/test/integration.js +147 -56
- package/test/unit/datastream.js +95 -0
- package/test/unit/eventify.js +370 -13
- package/test/unit/jsonstream.js +2 -2
- package/test/unit/match.js +1058 -0
- package/test/unit/parse.js +38 -29
- package/test/unit/streamify.js +32 -8
- package/test/unit/stringify.js +21 -4
- package/test/unit/unpipe.js +3 -3
- package/test/unit/walk.js +309 -149
- package/.travis.yml +0 -12
package/src/match.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const check = require('check-types')
|
|
4
|
+
const DataStream = require('./datastream')
|
|
5
|
+
const events = require('./events')
|
|
6
|
+
const Hoopy = require('hoopy')
|
|
7
|
+
const walk = require('./walk')
|
|
8
|
+
|
|
9
|
+
const DEFAULT_BUFFER_LENGTH = 1024
|
|
10
|
+
|
|
11
|
+
module.exports = match
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Public function `match`.
|
|
15
|
+
*
|
|
16
|
+
* Asynchronously parses a stream of JSON data, returning a stream of items
|
|
17
|
+
* that match the argument. Note that if a value is `null`, it won't be matched
|
|
18
|
+
* because `null` is used to signify end-of-stream in node.
|
|
19
|
+
*
|
|
20
|
+
* @param stream: Readable instance representing the incoming JSON.
|
|
21
|
+
*
|
|
22
|
+
* @param selector: Regular expression, string or predicate function used to
|
|
23
|
+
* identify matches. If a regular expression or string is
|
|
24
|
+
* passed, only property keys are tested. If a predicate is
|
|
25
|
+
* passed, both the key and the value are passed to it as
|
|
26
|
+
* arguments.
|
|
27
|
+
*
|
|
28
|
+
* @option numbers: Boolean, indicating whether numerical keys (e.g. array
|
|
29
|
+
* indices) should be coerced to strings before testing the
|
|
30
|
+
* match. Only applies if the `selector` argument is a string
|
|
31
|
+
* or regular expression.
|
|
32
|
+
*
|
|
33
|
+
* @option ndjson: Set this to true to parse newline-delimited JSON,
|
|
34
|
+
* default is `false`.
|
|
35
|
+
*
|
|
36
|
+
* @option yieldRate: The number of data items to process per timeslice,
|
|
37
|
+
* default is 16384.
|
|
38
|
+
*
|
|
39
|
+
* @option bufferLength: The length of the match buffer, default is 1024.
|
|
40
|
+
*
|
|
41
|
+
* @option highWaterMark: If set, will be passed to the readable stream constructor
|
|
42
|
+
* as the value for the highWaterMark option.
|
|
43
|
+
*
|
|
44
|
+
* @option Promise: The promise constructor to use, defaults to bluebird.
|
|
45
|
+
**/
|
|
46
|
+
function match (stream, selector, options = {}) {
|
|
47
|
+
const scopes = []
|
|
48
|
+
const properties = []
|
|
49
|
+
const emitter = walk(stream, options)
|
|
50
|
+
const matches = new Hoopy(options.bufferLength || DEFAULT_BUFFER_LENGTH)
|
|
51
|
+
let streamOptions
|
|
52
|
+
const { highWaterMark } = options
|
|
53
|
+
if (highWaterMark) {
|
|
54
|
+
streamOptions = { highWaterMark }
|
|
55
|
+
}
|
|
56
|
+
const results = new DataStream(read, streamOptions)
|
|
57
|
+
|
|
58
|
+
let selectorFunction, selectorString, resume
|
|
59
|
+
let coerceNumbers = false
|
|
60
|
+
let awaitPush = true
|
|
61
|
+
let isEnded = false
|
|
62
|
+
let length = 0
|
|
63
|
+
let index = 0
|
|
64
|
+
|
|
65
|
+
if (check.function(selector)) {
|
|
66
|
+
selectorFunction = selector
|
|
67
|
+
selector = null
|
|
68
|
+
} else {
|
|
69
|
+
coerceNumbers = !! options.numbers
|
|
70
|
+
|
|
71
|
+
if (check.string(selector)) {
|
|
72
|
+
check.assert.nonEmptyString(selector)
|
|
73
|
+
selectorString = selector
|
|
74
|
+
selector = null
|
|
75
|
+
} else {
|
|
76
|
+
check.assert.instanceStrict(selector, RegExp)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
emitter.on(events.array, array)
|
|
81
|
+
emitter.on(events.object, object)
|
|
82
|
+
emitter.on(events.property, property)
|
|
83
|
+
emitter.on(events.endArray, endScope)
|
|
84
|
+
emitter.on(events.endObject, endScope)
|
|
85
|
+
emitter.on(events.string, value)
|
|
86
|
+
emitter.on(events.number, value)
|
|
87
|
+
emitter.on(events.literal, value)
|
|
88
|
+
emitter.on(events.end, end)
|
|
89
|
+
emitter.on(events.error, error)
|
|
90
|
+
emitter.on(events.dataError, dataError)
|
|
91
|
+
|
|
92
|
+
return results
|
|
93
|
+
|
|
94
|
+
function read () {
|
|
95
|
+
if (awaitPush) {
|
|
96
|
+
awaitPush = false
|
|
97
|
+
|
|
98
|
+
if (isEnded) {
|
|
99
|
+
if (length > 0) {
|
|
100
|
+
after()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return endResults()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (resume) {
|
|
108
|
+
const resumeCopy = resume
|
|
109
|
+
resume = null
|
|
110
|
+
resumeCopy()
|
|
111
|
+
after()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function after () {
|
|
116
|
+
if (awaitPush || resume) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let i
|
|
121
|
+
|
|
122
|
+
for (i = 0; i < length && ! resume; ++i) {
|
|
123
|
+
if (! results.push(matches[i + index])) {
|
|
124
|
+
pause()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (i === length) {
|
|
129
|
+
index = length = 0
|
|
130
|
+
} else {
|
|
131
|
+
length -= i
|
|
132
|
+
index += i
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pause () {
|
|
137
|
+
resume = emitter.pause()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function endResults () {
|
|
141
|
+
if (! awaitPush) {
|
|
142
|
+
results.push(null)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function array () {
|
|
147
|
+
scopes.push([])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function object () {
|
|
151
|
+
scopes.push({})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function property (name) {
|
|
155
|
+
properties.push(name)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function endScope () {
|
|
159
|
+
value(scopes.pop())
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function value (v) {
|
|
163
|
+
let key
|
|
164
|
+
|
|
165
|
+
if (scopes.length > 0) {
|
|
166
|
+
const scope = scopes[scopes.length - 1]
|
|
167
|
+
|
|
168
|
+
if (Array.isArray(scope)) {
|
|
169
|
+
key = scope.length
|
|
170
|
+
} else {
|
|
171
|
+
key = properties.pop()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
scope[key] = v
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (v === null) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (selectorFunction) {
|
|
182
|
+
if (selectorFunction(key, v, scopes.length)) {
|
|
183
|
+
push(v)
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
if (coerceNumbers && typeof key === 'number') {
|
|
187
|
+
key = key.toString()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if ((selectorString && selectorString === key) || (selector && selector.test(key))) {
|
|
191
|
+
push(v)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function push (v) {
|
|
197
|
+
if (length + 1 === matches.length) {
|
|
198
|
+
pause()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
matches[index + length++] = v
|
|
202
|
+
|
|
203
|
+
after()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function end () {
|
|
207
|
+
isEnded = true
|
|
208
|
+
endResults()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function error (e) {
|
|
212
|
+
results.emit('error', e)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function dataError (e) {
|
|
216
|
+
results.emit('dataError', e)
|
|
217
|
+
}
|
|
218
|
+
}
|
package/src/parse.js
CHANGED
package/src/stream.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const util = require('util')
|
|
4
|
+
const Readable = require('stream').Readable
|
|
5
|
+
const check = require('check-types')
|
|
6
|
+
|
|
7
|
+
util.inherits(BfjStream, Readable)
|
|
8
|
+
|
|
9
|
+
module.exports = BfjStream
|
|
10
|
+
|
|
11
|
+
function BfjStream (read, options) {
|
|
12
|
+
if (check.not.instanceStrict(this, BfjStream)) {
|
|
13
|
+
return new BfjStream(read)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
check.assert.function(read, 'Invalid read implementation')
|
|
17
|
+
|
|
18
|
+
this._read = function () { // eslint-disable-line no-underscore-dangle
|
|
19
|
+
read()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return Readable.call(this, options)
|
|
23
|
+
}
|
package/src/streamify.js
CHANGED
|
@@ -18,34 +18,42 @@ module.exports = streamify
|
|
|
18
18
|
* Asynchronously serialises a data structure to a stream of JSON
|
|
19
19
|
* data. Sanely handles promises, buffers, maps and other iterables.
|
|
20
20
|
*
|
|
21
|
-
* @param data:
|
|
21
|
+
* @param data: The data to transform.
|
|
22
22
|
*
|
|
23
|
-
* @option space:
|
|
24
|
-
*
|
|
23
|
+
* @option space: Indentation string, or the number of spaces
|
|
24
|
+
* to indent each nested level by.
|
|
25
25
|
*
|
|
26
|
-
* @option promises:
|
|
26
|
+
* @option promises: 'resolve' or 'ignore', default is 'resolve'.
|
|
27
27
|
*
|
|
28
|
-
* @option buffers:
|
|
28
|
+
* @option buffers: 'toString' or 'ignore', default is 'toString'.
|
|
29
29
|
*
|
|
30
|
-
* @option maps:
|
|
30
|
+
* @option maps: 'object' or 'ignore', default is 'object'.
|
|
31
31
|
*
|
|
32
|
-
* @option iterables:
|
|
32
|
+
* @option iterables: 'array' or 'ignore', default is 'array'.
|
|
33
33
|
*
|
|
34
|
-
* @option circular:
|
|
34
|
+
* @option circular: 'error' or 'ignore', default is 'error'.
|
|
35
35
|
*
|
|
36
|
-
* @option yieldRate:
|
|
37
|
-
*
|
|
36
|
+
* @option yieldRate: The number of data items to process per timeslice,
|
|
37
|
+
* default is 16384.
|
|
38
38
|
*
|
|
39
|
-
* @option bufferLength:
|
|
39
|
+
* @option bufferLength: The length of the buffer, default is 1024.
|
|
40
40
|
*
|
|
41
|
-
* @option
|
|
41
|
+
* @option highWaterMark: If set, will be passed to the readable stream constructor
|
|
42
|
+
* as the value for the highWaterMark option.
|
|
43
|
+
*
|
|
44
|
+
* @option Promise: The promise constructor to use, defaults to bluebird.
|
|
42
45
|
**/
|
|
43
46
|
function streamify (data, options = {}) {
|
|
44
47
|
const emitter = eventify(data, options)
|
|
45
48
|
const json = new Hoopy(options.bufferLength || DEFAULT_BUFFER_LENGTH)
|
|
46
49
|
const Promise = promise(options)
|
|
47
50
|
const space = normaliseSpace(options)
|
|
48
|
-
|
|
51
|
+
let streamOptions
|
|
52
|
+
const { highWaterMark } = options
|
|
53
|
+
if (highWaterMark) {
|
|
54
|
+
streamOptions = { highWaterMark }
|
|
55
|
+
}
|
|
56
|
+
const stream = new JsonStream(read, streamOptions)
|
|
49
57
|
|
|
50
58
|
let awaitPush = true
|
|
51
59
|
let index = 0
|
|
@@ -67,6 +75,7 @@ function streamify (data, options = {}) {
|
|
|
67
75
|
emitter.on(events.endObject, noRacing(endObject))
|
|
68
76
|
emitter.on(events.end, noRacing(end))
|
|
69
77
|
emitter.on(events.error, noRacing(error))
|
|
78
|
+
emitter.on(events.dataError, noRacing(dataError))
|
|
70
79
|
|
|
71
80
|
return stream
|
|
72
81
|
|
|
@@ -255,6 +264,10 @@ function streamify (data, options = {}) {
|
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
function error (err) {
|
|
267
|
+
stream.emit('error', err)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function dataError (err) {
|
|
258
271
|
stream.emit('dataError', err)
|
|
259
272
|
}
|
|
260
273
|
}
|
package/src/stringify.js
CHANGED
|
@@ -11,27 +11,30 @@ module.exports = stringify
|
|
|
11
11
|
* Returns a promise and asynchronously serialises a data structure to a
|
|
12
12
|
* JSON string. Sanely handles promises, buffers, maps and other iterables.
|
|
13
13
|
*
|
|
14
|
-
* @param data:
|
|
14
|
+
* @param data: The data to transform
|
|
15
15
|
*
|
|
16
|
-
* @option space:
|
|
17
|
-
*
|
|
16
|
+
* @option space: Indentation string, or the number of spaces
|
|
17
|
+
* to indent each nested level by.
|
|
18
18
|
*
|
|
19
|
-
* @option promises:
|
|
19
|
+
* @option promises: 'resolve' or 'ignore', default is 'resolve'.
|
|
20
20
|
*
|
|
21
|
-
* @option buffers:
|
|
21
|
+
* @option buffers: 'toString' or 'ignore', default is 'toString'.
|
|
22
22
|
*
|
|
23
|
-
* @option maps:
|
|
23
|
+
* @option maps: 'object' or 'ignore', default is 'object'.
|
|
24
24
|
*
|
|
25
|
-
* @option iterables:
|
|
25
|
+
* @option iterables: 'array' or 'ignore', default is 'array'.
|
|
26
26
|
*
|
|
27
|
-
* @option circular:
|
|
27
|
+
* @option circular: 'error' or 'ignore', default is 'error'.
|
|
28
28
|
*
|
|
29
|
-
* @option yieldRate:
|
|
30
|
-
*
|
|
29
|
+
* @option yieldRate: The number of data items to process per timeslice,
|
|
30
|
+
* default is 16384.
|
|
31
31
|
*
|
|
32
|
-
* @option bufferLength:
|
|
32
|
+
* @option bufferLength: The length of the buffer, default is 1024.
|
|
33
33
|
*
|
|
34
|
-
* @option
|
|
34
|
+
* @option highWaterMark: If set, will be passed to the readable stream constructor
|
|
35
|
+
* as the value for the highWaterMark option.
|
|
36
|
+
*
|
|
37
|
+
* @option Promise: The promise constructor to use, defaults to bluebird.
|
|
35
38
|
**/
|
|
36
39
|
function stringify (data, options) {
|
|
37
40
|
const json = []
|
|
@@ -42,6 +45,7 @@ function stringify (data, options) {
|
|
|
42
45
|
|
|
43
46
|
stream.on('data', read)
|
|
44
47
|
stream.on('end', end)
|
|
48
|
+
stream.on('error', error)
|
|
45
49
|
stream.on('dataError', error)
|
|
46
50
|
|
|
47
51
|
return new Promise((res, rej) => {
|
package/src/unpipe.js
CHANGED
|
@@ -29,7 +29,7 @@ function unpipe (callback, options) {
|
|
|
29
29
|
|
|
30
30
|
const jsonstream = new stream.PassThrough()
|
|
31
31
|
|
|
32
|
-
parse(jsonstream, options)
|
|
32
|
+
parse(jsonstream, Object.assign({}, options, { ndjson: false }))
|
|
33
33
|
.then(data => callback(null, data))
|
|
34
34
|
.catch(error => callback(error))
|
|
35
35
|
|
package/src/walk.js
CHANGED
|
@@ -374,7 +374,7 @@ function initialise (stream, options = {}) {
|
|
|
374
374
|
|
|
375
375
|
return endScope(scp)
|
|
376
376
|
.then(() => {
|
|
377
|
-
if (
|
|
377
|
+
if (scopes.length > 0) {
|
|
378
378
|
return checkCharacter(character(), ',', currentPosition)
|
|
379
379
|
}
|
|
380
380
|
})
|
|
@@ -389,7 +389,7 @@ function initialise (stream, options = {}) {
|
|
|
389
389
|
|
|
390
390
|
function fail (actual, expected, position) {
|
|
391
391
|
return emit(
|
|
392
|
-
events.
|
|
392
|
+
events.dataError,
|
|
393
393
|
error.create(
|
|
394
394
|
actual,
|
|
395
395
|
expected,
|
package/src/write.js
CHANGED
|
@@ -13,29 +13,32 @@ module.exports = write
|
|
|
13
13
|
* JSON file on disk. Sanely handles promises, buffers, maps and other
|
|
14
14
|
* iterables.
|
|
15
15
|
*
|
|
16
|
-
* @param path:
|
|
16
|
+
* @param path: Path to the JSON file.
|
|
17
17
|
*
|
|
18
|
-
* @param data:
|
|
18
|
+
* @param data: The data to transform.
|
|
19
19
|
*
|
|
20
|
-
* @option space:
|
|
21
|
-
*
|
|
20
|
+
* @option space: Indentation string, or the number of spaces
|
|
21
|
+
* to indent each nested level by.
|
|
22
22
|
*
|
|
23
|
-
* @option promises:
|
|
23
|
+
* @option promises: 'resolve' or 'ignore', default is 'resolve'.
|
|
24
24
|
*
|
|
25
|
-
* @option buffers:
|
|
25
|
+
* @option buffers: 'toString' or 'ignore', default is 'toString'.
|
|
26
26
|
*
|
|
27
|
-
* @option maps:
|
|
27
|
+
* @option maps: 'object' or 'ignore', default is 'object'.
|
|
28
28
|
*
|
|
29
|
-
* @option iterables:
|
|
29
|
+
* @option iterables: 'array' or 'ignore', default is 'array'.
|
|
30
30
|
*
|
|
31
|
-
* @option circular:
|
|
31
|
+
* @option circular: 'error' or 'ignore', default is 'error'.
|
|
32
32
|
*
|
|
33
|
-
* @option yieldRate:
|
|
34
|
-
*
|
|
33
|
+
* @option yieldRate: The number of data items to process per timeslice,
|
|
34
|
+
* default is 16384.
|
|
35
35
|
*
|
|
36
|
-
* @option bufferLength:
|
|
36
|
+
* @option bufferLength: The length of the buffer, default is 1024.
|
|
37
37
|
*
|
|
38
|
-
* @option
|
|
38
|
+
* @option highWaterMark: If set, will be passed to the readable stream constructor
|
|
39
|
+
* as the value for the highWaterMark option.
|
|
40
|
+
*
|
|
41
|
+
* @option Promise: The promise constructor to use, defaults to bluebird.
|
|
39
42
|
**/
|
|
40
43
|
function write (path, data, options) {
|
|
41
44
|
const Promise = promise(options)
|
package/test/integration.js
CHANGED
|
@@ -41,6 +41,14 @@ suite('integration:', () => {
|
|
|
41
41
|
assert.lengthOf(bfj.walk, 1)
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
+
test('match function is exported', () => {
|
|
45
|
+
assert.isFunction(bfj.match)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('match expects two arguments', () => {
|
|
49
|
+
assert.lengthOf(bfj.match, 2)
|
|
50
|
+
})
|
|
51
|
+
|
|
44
52
|
test('parse function is exported', () => {
|
|
45
53
|
assert.isFunction(bfj.parse)
|
|
46
54
|
})
|
|
@@ -161,61 +169,6 @@ suite('integration:', () => {
|
|
|
161
169
|
})
|
|
162
170
|
})
|
|
163
171
|
|
|
164
|
-
suite('parse NDJSON:', () => {
|
|
165
|
-
let failed, file, results
|
|
166
|
-
|
|
167
|
-
setup(() => {
|
|
168
|
-
failed = false
|
|
169
|
-
file = path.join(__dirname, 'data.ndjson')
|
|
170
|
-
results = []
|
|
171
|
-
fs.writeFileSync(file, [
|
|
172
|
-
JSON.stringify([ 'b', 'a', 'r' ]),
|
|
173
|
-
JSON.stringify(null),
|
|
174
|
-
'',
|
|
175
|
-
'',
|
|
176
|
-
JSON.stringify('wibble')
|
|
177
|
-
].join('\n'))
|
|
178
|
-
const stream = fs.createReadStream(file)
|
|
179
|
-
return bfj.parse(stream, { ndjson: true })
|
|
180
|
-
.then(result => {
|
|
181
|
-
results.push(result)
|
|
182
|
-
return bfj.parse(stream, { ndjson: true })
|
|
183
|
-
})
|
|
184
|
-
.then(result => {
|
|
185
|
-
results.push(result)
|
|
186
|
-
return bfj.parse(stream, { ndjson: true })
|
|
187
|
-
})
|
|
188
|
-
.then(result => {
|
|
189
|
-
results.push(result)
|
|
190
|
-
return bfj.parse(stream, { ndjson: true })
|
|
191
|
-
})
|
|
192
|
-
.then(result => {
|
|
193
|
-
results.push(result)
|
|
194
|
-
return bfj.parse(stream, { ndjson: true })
|
|
195
|
-
})
|
|
196
|
-
.then(result => results.push(result))
|
|
197
|
-
.catch(e => {
|
|
198
|
-
failed = true
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
teardown(() => {
|
|
203
|
-
fs.unlinkSync(file)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
test('results were correct', () => {
|
|
207
|
-
assert.isFalse(failed)
|
|
208
|
-
assert.lengthOf(results, 5)
|
|
209
|
-
assert.deepEqual(results, [
|
|
210
|
-
[ 'b', 'a', 'r' ],
|
|
211
|
-
null,
|
|
212
|
-
'wibble',
|
|
213
|
-
undefined,
|
|
214
|
-
undefined
|
|
215
|
-
])
|
|
216
|
-
})
|
|
217
|
-
})
|
|
218
|
-
|
|
219
172
|
suite('read error:', () => {
|
|
220
173
|
let failed, file, result, error
|
|
221
174
|
|
|
@@ -264,12 +217,95 @@ suite('integration:', () => {
|
|
|
264
217
|
})
|
|
265
218
|
})
|
|
266
219
|
|
|
220
|
+
suite('match predicate:', () => {
|
|
221
|
+
let file, results, errors
|
|
222
|
+
|
|
223
|
+
setup(done => {
|
|
224
|
+
file = path.join(__dirname, 'data.json')
|
|
225
|
+
fs.writeFileSync(file, JSON.stringify({
|
|
226
|
+
foo: 'bar',
|
|
227
|
+
baz: 'qux',
|
|
228
|
+
wibble: 'blee'
|
|
229
|
+
}))
|
|
230
|
+
results = []
|
|
231
|
+
errors = []
|
|
232
|
+
const datastream = bfj.match(fs.createReadStream(file), (k, v) => k === 'baz' || v === 'blee')
|
|
233
|
+
datastream.on('data', item => results.push(item))
|
|
234
|
+
datastream.on('error', error => errors.push(error))
|
|
235
|
+
datastream.on('end', done)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('the correct properties were matched', () => {
|
|
239
|
+
assert.deepEqual([ 'qux', 'blee' ], results)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test('no errors occurred', () => {
|
|
243
|
+
assert.deepEqual(errors, [])
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
suite('match nested:', () => {
|
|
248
|
+
let file, results, errors
|
|
249
|
+
|
|
250
|
+
setup(done => {
|
|
251
|
+
file = path.join(__dirname, 'data.json')
|
|
252
|
+
fs.writeFileSync(file, JSON.stringify({
|
|
253
|
+
foo: {
|
|
254
|
+
bar: 'baz'
|
|
255
|
+
}
|
|
256
|
+
}))
|
|
257
|
+
results = []
|
|
258
|
+
errors = []
|
|
259
|
+
const datastream = bfj.match(fs.createReadStream(file), () => true)
|
|
260
|
+
datastream.on('data', item => results.push(item))
|
|
261
|
+
datastream.on('error', error => errors.push(error))
|
|
262
|
+
datastream.on('end', done)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test('the correct properties were matched', () => {
|
|
266
|
+
assert.deepEqual([ 'baz', { bar: 'baz' }, { foo: { bar: 'baz' } } ], results)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('no errors occurred', () => {
|
|
270
|
+
assert.deepEqual(errors, [])
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
suite('match ndjson:', () => {
|
|
275
|
+
let file, results, errors
|
|
276
|
+
|
|
277
|
+
setup(done => {
|
|
278
|
+
file = path.join(__dirname, 'data.ndjson')
|
|
279
|
+
fs.writeFileSync(file, [
|
|
280
|
+
JSON.stringify([ 'a', 'b' ]),
|
|
281
|
+
JSON.stringify(null),
|
|
282
|
+
'',
|
|
283
|
+
'',
|
|
284
|
+
JSON.stringify('wibble')
|
|
285
|
+
].join('\n'))
|
|
286
|
+
results = []
|
|
287
|
+
errors = []
|
|
288
|
+
const datastream = bfj.match(fs.createReadStream(file), () => true, { ndjson: true })
|
|
289
|
+
datastream.on('data', item => results.push(item))
|
|
290
|
+
datastream.on('error', error => errors.push(error))
|
|
291
|
+
datastream.on('end', done)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
test('the correct properties were matched', () => {
|
|
295
|
+
assert.deepEqual([ 'a', 'b', [ 'a', 'b' ], 'wibble' ], results)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('no errors occurred', () => {
|
|
299
|
+
assert.deepEqual(errors, [])
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
267
303
|
suite('parse request:', () => {
|
|
268
304
|
let error, result
|
|
269
305
|
|
|
270
306
|
setup(done => {
|
|
271
307
|
const jsonstream = new stream.PassThrough()
|
|
272
|
-
request({ url: 'https://
|
|
308
|
+
request({ url: 'https://gitlab.com/philbooth/bfj/raw/master/package.json' })
|
|
273
309
|
.pipe(bfj.unpipe((err, res) => {
|
|
274
310
|
error = err
|
|
275
311
|
result = res
|
|
@@ -283,6 +319,61 @@ suite('integration:', () => {
|
|
|
283
319
|
})
|
|
284
320
|
})
|
|
285
321
|
|
|
322
|
+
suite('parse NDJSON:', () => {
|
|
323
|
+
let failed, file, results
|
|
324
|
+
|
|
325
|
+
setup(() => {
|
|
326
|
+
failed = false
|
|
327
|
+
file = path.join(__dirname, 'data.ndjson')
|
|
328
|
+
results = []
|
|
329
|
+
fs.writeFileSync(file, [
|
|
330
|
+
JSON.stringify([ 'b', 'a', 'r' ]),
|
|
331
|
+
JSON.stringify(null),
|
|
332
|
+
'',
|
|
333
|
+
'',
|
|
334
|
+
JSON.stringify('wibble')
|
|
335
|
+
].join('\n'))
|
|
336
|
+
const stream = fs.createReadStream(file)
|
|
337
|
+
return bfj.parse(stream, { ndjson: true })
|
|
338
|
+
.then(result => {
|
|
339
|
+
results.push(result)
|
|
340
|
+
return bfj.parse(stream, { ndjson: true })
|
|
341
|
+
})
|
|
342
|
+
.then(result => {
|
|
343
|
+
results.push(result)
|
|
344
|
+
return bfj.parse(stream, { ndjson: true })
|
|
345
|
+
})
|
|
346
|
+
.then(result => {
|
|
347
|
+
results.push(result)
|
|
348
|
+
return bfj.parse(stream, { ndjson: true })
|
|
349
|
+
})
|
|
350
|
+
.then(result => {
|
|
351
|
+
results.push(result)
|
|
352
|
+
return bfj.parse(stream, { ndjson: true })
|
|
353
|
+
})
|
|
354
|
+
.then(result => results.push(result))
|
|
355
|
+
.catch(e => {
|
|
356
|
+
failed = true
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
teardown(() => {
|
|
361
|
+
fs.unlinkSync(file)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('results were correct', () => {
|
|
365
|
+
assert.isFalse(failed)
|
|
366
|
+
assert.lengthOf(results, 5)
|
|
367
|
+
assert.deepEqual(results, [
|
|
368
|
+
[ 'b', 'a', 'r' ],
|
|
369
|
+
null,
|
|
370
|
+
'wibble',
|
|
371
|
+
undefined,
|
|
372
|
+
undefined
|
|
373
|
+
])
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
286
377
|
suite('stringify value:', () => {
|
|
287
378
|
let result
|
|
288
379
|
|