* spawns a python process * @param scriptPath path to script. Relative to current directory or options.scriptFolder if specified * @param options * @param stdoutSplitter Optional. Splits stdout into chunks, defaulting to splitting into newline-seperated lines * @param stderrSpli
(scriptPath: string, options?: Options, stdoutSplitter: Transform = null, stderrSplitter: Transform = null)
| 132 | * @param stderrSplitter Optional. splits stderr into chunks, defaulting to splitting into newline-seperated lines |
| 133 | */ |
| 134 | constructor(scriptPath: string, options?: Options, stdoutSplitter: Transform = null, stderrSplitter: Transform = null) { |
| 135 | super(); |
| 136 | |
| 137 | /** |
| 138 | * returns either pythonshell func (if val string) or custom func (if val Function) |
| 139 | */ |
| 140 | function resolve(type, val: string | Function) { |
| 141 | if (typeof val === 'string') { |
| 142 | // use a built-in function using its name |
| 143 | return PythonShell[type][val]; |
| 144 | } else if (typeof val === 'function') { |
| 145 | // use a custom function |
| 146 | return val; |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | if (scriptPath.trim().length == 0) throw Error("scriptPath cannot be empty! You must give a script for python to run") |
| 151 | |
| 152 | let self = this; |
| 153 | let errorData = ''; |
| 154 | EventEmitter.call(this); |
| 155 | |
| 156 | options = <Options>extend({}, PythonShell.defaultOptions, options); |
| 157 | let pythonPath: string; |
| 158 | if (!options.pythonPath) { |
| 159 | pythonPath = PythonShell.defaultPythonPath; |
| 160 | } else pythonPath = options.pythonPath; |
| 161 | let pythonOptions = toArray(options.pythonOptions); |
| 162 | let scriptArgs = toArray(options.args); |
| 163 | |
| 164 | this.scriptPath = join(options.scriptPath || '', scriptPath); |
| 165 | this.command = pythonOptions.concat(this.scriptPath, scriptArgs); |
| 166 | this.mode = options.mode || 'text'; |
| 167 | this.formatter = resolve('format', options.formatter || this.mode); |
| 168 | this.parser = resolve('parse', options.parser || this.mode); |
| 169 | // We don't expect users to ever format stderr as JSON so we default to text mode |
| 170 | this.stderrParser = resolve('parse', options.stderrParser || 'text'); |
| 171 | this.terminated = false; |
| 172 | this.childProcess = spawn(pythonPath, this.command, options); |
| 173 | |
| 174 | ['stdout', 'stdin', 'stderr'].forEach(function (name) { |
| 175 | self[name] = self.childProcess[name]; |
| 176 | self.parser && self[name] && self[name].setEncoding(options.encoding || 'utf8'); |
| 177 | }); |
| 178 | |
| 179 | // Node buffers stdout&stderr in batches regardless of newline placement |
| 180 | // This is troublesome if you want to recieve distinct individual messages |
| 181 | // for example JSON parsing breaks if it recieves partial JSON |
| 182 | // so we use newlineTransformer to emit each batch seperated by newline |
| 183 | if (this.parser && this.stdout) { |
| 184 | if(!stdoutSplitter) stdoutSplitter = new NewlineTransformer() |
| 185 | // note that setting the encoding turns the chunk into a string |
| 186 | stdoutSplitter.setEncoding(options.encoding || 'utf8') |
| 187 | this.stdout.pipe(stdoutSplitter).on('data', (chunk: string) => { |
| 188 | this.emit('message', self.parser(chunk)); |
| 189 | }); |
| 190 | } |
| 191 |