UNPKG

3.95 kBJavaScriptView Raw
1'use strict'
2
3const Transform = require('stream').Transform
4const Writable = require('stream').Writable
5
6const BEGIN_READY_RE = /{begin(\d+)}([\s\S]*){ready\1}/g
7
8/**
9 * A transform stream which will maintain a buffer with data
10 * received from incoming stream and push data when the buffer
11 * can be matched againts the regex. Will push match object
12 * returned by regex.exec.
13 * @return {Transform} A transform stream, could be piped into
14 * BeginReady transform stream do construct a proper object.
15 */
16function createRegexTransformStream(regex) {
17 let buffer = ''
18
19 const ts = new Transform({ objectMode: true })
20
21 // cannot be arrow function to call this.push
22 ts._transform = function (chunk, enc, next) {
23 let lastMatch
24 let match
25 buffer += chunk.toString()
26
27 // thx stream-snitch
28 // https://github.com/dmotz/stream-snitch/blob/master/index.js#L52
29 // eslint-disable-next-line no-cond-assign
30 while (match = regex.exec(buffer)) {
31 this.push(match)
32 lastMatch = match
33 if (!regex.global) break
34 }
35 if (lastMatch) {
36 buffer = buffer.slice(lastMatch.index + lastMatch[0].length)
37 }
38 next()
39 }
40
41 return ts
42}
43
44/**
45 * A transform stream which will mutate data from regex stream into an object
46 * with commandNumber and data.
47 * @return {Transfrom} A transform stream into which exiftool process stdout and
48 * stderr can be piped. It will push objects in form of { cn: commandNumber, d: data }
49 */
50function createBeginReadyMatchTransformStream() {
51 const ts = new Transform({ objectMode: true })
52 // expecting data from RegexTransformStream with BEGIN_READY_RE
53 ts._transform = (match, enc, next) => {
54 const data = {
55 cn: match[1],
56 d: match[2].trim(),
57 }
58 next(null, data)
59 }
60 return ts
61}
62
63/**
64 * A write stream which will maintain a map of commands which are waiting
65 * to be resolved, where keys are the corresponding resolve promise. The
66 * stream will expect input from BeginReady Transform Stream.
67 * @return {Writable} A write stream extended with `addToResolveMap` method.
68 * @see createBeginReadyTransformStream
69 */
70function createResolverWriteStream() {
71 const ws = new Writable({
72 objectMode: true,
73 })
74 ws._resolveMap = {}
75 ws.addToResolveMap = function(commandNumber, resolve) {
76 if (typeof commandNumber !== 'string') {
77 throw new Error('commandNumber argument must be a string')
78 }
79 if (typeof resolve !== 'function') {
80 throw new Error('resolve argument must be a function')
81 }
82 if (this._resolveMap[commandNumber]) {
83 throw new Error('Command with the same number is already expected')
84 }
85 this._resolveMap[commandNumber] = resolve
86 }
87 ws._write = function (obj, enc, next) {
88 const commandNumber = obj.cn
89 const data = obj.d
90 const resolve = this._resolveMap[commandNumber]
91 if (resolve) {
92 resolve(data)
93 delete this._resolveMap[commandNumber]
94 next()
95 } else {
96 next(new Error(`Command with index ${commandNumber} not found`))
97 }
98 }
99 return ws
100}
101
102/**
103 * Setup a pipe from process std stream into resolve write stream
104 * through regex transform and begin-ready transform streams.
105 * @param {Readable} rs Readable stream (from exiftool process)
106 * @return {Writable} A Resolve transform stream.
107 */
108function setupResolveWriteStreamPipe(rs) {
109 const rts = createRegexTransformStream(BEGIN_READY_RE)
110 const brmts = createBeginReadyMatchTransformStream()
111 const rws = createResolverWriteStream()
112
113 return rs.pipe(rts).pipe(brmts).pipe(rws)
114}
115
116module.exports = {
117 createRegexTransformStream,
118 createBeginReadyMatchTransformStream,
119 createResolverWriteStream,
120 BEGIN_READY_RE,
121 setupResolveWriteStreamPipe,
122}