1 | "use strict";
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4 | return new (P || (P = Promise))(function (resolve, reject) {
|
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9 | });
|
10 | };
|
11 | Object.defineProperty(exports, "__esModule", { value: true });
|
12 | exports.PythonShell = exports.NewlineTransformer = exports.PythonShellErrorWithLogs = exports.PythonShellError = void 0;
|
13 | const events_1 = require("events");
|
14 | const child_process_1 = require("child_process");
|
15 | const os_1 = require("os");
|
16 | const path_1 = require("path");
|
17 | const stream_1 = require("stream");
|
18 | const fs_1 = require("fs");
|
19 | const util_1 = require("util");
|
20 | function toArray(source) {
|
21 | if (typeof source === 'undefined' || source === null) {
|
22 | return [];
|
23 | }
|
24 | else if (!Array.isArray(source)) {
|
25 | return [source];
|
26 | }
|
27 | return source;
|
28 | }
|
29 |
|
30 |
|
31 |
|
32 | function extend(obj, ...args) {
|
33 | Array.prototype.slice.call(arguments, 1).forEach(function (source) {
|
34 | if (source) {
|
35 | for (let key in source) {
|
36 | obj[key] = source[key];
|
37 | }
|
38 | }
|
39 | });
|
40 | return obj;
|
41 | }
|
42 |
|
43 |
|
44 |
|
45 | function getRandomInt() {
|
46 | return Math.floor(Math.random() * 10000000000);
|
47 | }
|
48 | const execPromise = (0, util_1.promisify)(child_process_1.exec);
|
49 | class PythonShellError extends Error {
|
50 | }
|
51 | exports.PythonShellError = PythonShellError;
|
52 | class PythonShellErrorWithLogs extends PythonShellError {
|
53 | }
|
54 | exports.PythonShellErrorWithLogs = PythonShellErrorWithLogs;
|
55 |
|
56 |
|
57 |
|
58 | class NewlineTransformer extends stream_1.Transform {
|
59 | _transform(chunk, encoding, callback) {
|
60 | let data = chunk.toString();
|
61 | if (this._lastLineData)
|
62 | data = this._lastLineData + data;
|
63 | const lines = data.split(os_1.EOL);
|
64 | this._lastLineData = lines.pop();
|
65 |
|
66 | lines.forEach(this.push.bind(this));
|
67 | callback();
|
68 | }
|
69 | _flush(done) {
|
70 | if (this._lastLineData)
|
71 | this.push(this._lastLineData);
|
72 | this._lastLineData = null;
|
73 | done();
|
74 | }
|
75 | }
|
76 | exports.NewlineTransformer = NewlineTransformer;
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | class PythonShell extends events_1.EventEmitter {
|
86 | |
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 | constructor(scriptPath, options, stdoutSplitter = null, stderrSplitter = null) {
|
94 | super();
|
95 | |
96 |
|
97 |
|
98 | function resolve(type, val) {
|
99 | if (typeof val === 'string') {
|
100 |
|
101 | return PythonShell[type][val];
|
102 | }
|
103 | else if (typeof val === 'function') {
|
104 |
|
105 | return val;
|
106 | }
|
107 | }
|
108 | if (scriptPath.trim().length == 0)
|
109 | throw Error("scriptPath cannot be empty! You must give a script for python to run");
|
110 | let self = this;
|
111 | let errorData = '';
|
112 | events_1.EventEmitter.call(this);
|
113 | options = extend({}, PythonShell.defaultOptions, options);
|
114 | let pythonPath;
|
115 | if (!options.pythonPath) {
|
116 | pythonPath = PythonShell.defaultPythonPath;
|
117 | }
|
118 | else
|
119 | pythonPath = options.pythonPath;
|
120 | let pythonOptions = toArray(options.pythonOptions);
|
121 | let scriptArgs = toArray(options.args);
|
122 | this.scriptPath = (0, path_1.join)(options.scriptPath || '', scriptPath);
|
123 | this.command = pythonOptions.concat(this.scriptPath, scriptArgs);
|
124 | this.mode = options.mode || 'text';
|
125 | this.formatter = resolve('format', options.formatter || this.mode);
|
126 | this.parser = resolve('parse', options.parser || this.mode);
|
127 |
|
128 | this.stderrParser = resolve('parse', options.stderrParser || 'text');
|
129 | this.terminated = false;
|
130 | this.childProcess = (0, child_process_1.spawn)(pythonPath, this.command, options);
|
131 | ['stdout', 'stdin', 'stderr'].forEach(function (name) {
|
132 | self[name] = self.childProcess[name];
|
133 | self.parser && self[name] && self[name].setEncoding(options.encoding || 'utf8');
|
134 | });
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | if (this.parser && this.stdout) {
|
140 | if (!stdoutSplitter)
|
141 | stdoutSplitter = new NewlineTransformer();
|
142 |
|
143 | stdoutSplitter.setEncoding(options.encoding || 'utf8');
|
144 | this.stdout.pipe(stdoutSplitter).on('data', (chunk) => {
|
145 | this.emit('message', self.parser(chunk));
|
146 | });
|
147 | }
|
148 |
|
149 | if (this.stderrParser && this.stderr) {
|
150 | if (!stderrSplitter)
|
151 | stderrSplitter = new NewlineTransformer();
|
152 |
|
153 | stderrSplitter.setEncoding(options.encoding || 'utf8');
|
154 | this.stderr.pipe(stderrSplitter).on('data', (chunk) => {
|
155 | this.emit('stderr', self.stderrParser(chunk));
|
156 | });
|
157 | }
|
158 | if (this.stderr) {
|
159 | this.stderr.on('data', function (data) {
|
160 | errorData += '' + data;
|
161 | });
|
162 | this.stderr.on('end', function () {
|
163 | self.stderrHasEnded = true;
|
164 | terminateIfNeeded();
|
165 | });
|
166 | }
|
167 | else {
|
168 | self.stderrHasEnded = true;
|
169 | }
|
170 | if (this.stdout) {
|
171 | this.stdout.on('end', function () {
|
172 | self.stdoutHasEnded = true;
|
173 | terminateIfNeeded();
|
174 | });
|
175 | }
|
176 | else {
|
177 | self.stdoutHasEnded = true;
|
178 | }
|
179 | this.childProcess.on('error', function (err) {
|
180 | self.emit('error', err);
|
181 | });
|
182 | this.childProcess.on('exit', function (code, signal) {
|
183 | self.exitCode = code;
|
184 | self.exitSignal = signal;
|
185 | terminateIfNeeded();
|
186 | });
|
187 | function terminateIfNeeded() {
|
188 | if (!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null))
|
189 | return;
|
190 | let err;
|
191 | if (self.exitCode && self.exitCode !== 0) {
|
192 | if (errorData) {
|
193 | err = self.parseError(errorData);
|
194 | }
|
195 | else {
|
196 | err = new PythonShellError('process exited with code ' + self.exitCode);
|
197 | }
|
198 | err = extend(err, {
|
199 | executable: pythonPath,
|
200 | options: pythonOptions.length ? pythonOptions : null,
|
201 | script: self.scriptPath,
|
202 | args: scriptArgs.length ? scriptArgs : null,
|
203 | exitCode: self.exitCode
|
204 | });
|
205 |
|
206 | if (self.listeners('pythonError').length || !self._endCallback) {
|
207 | self.emit('pythonError', err);
|
208 | }
|
209 | }
|
210 | self.terminated = true;
|
211 | self.emit('close');
|
212 | self._endCallback && self._endCallback(err, self.exitCode, self.exitSignal);
|
213 | }
|
214 | ;
|
215 | }
|
216 | |
217 |
|
218 |
|
219 |
|
220 | static checkSyntax(code) {
|
221 | return __awaiter(this, void 0, void 0, function* () {
|
222 | const randomInt = getRandomInt();
|
223 | const filePath = (0, os_1.tmpdir)() + path_1.sep + `pythonShellSyntaxCheck${randomInt}.py`;
|
224 | const writeFilePromise = (0, util_1.promisify)(fs_1.writeFile);
|
225 | return writeFilePromise(filePath, code).then(() => {
|
226 | return this.checkSyntaxFile(filePath);
|
227 | });
|
228 | });
|
229 | }
|
230 | static getPythonPath() {
|
231 | return this.defaultOptions.pythonPath ? this.defaultOptions.pythonPath : this.defaultPythonPath;
|
232 | }
|
233 | |
234 |
|
235 |
|
236 |
|
237 | static checkSyntaxFile(filePath) {
|
238 | return __awaiter(this, void 0, void 0, function* () {
|
239 | const pythonPath = this.getPythonPath();
|
240 | let compileCommand = `${pythonPath} -m py_compile ${filePath}`;
|
241 | return execPromise(compileCommand);
|
242 | });
|
243 | }
|
244 | |
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | static run(scriptPath, options) {
|
251 | return new Promise((resolve, reject) => {
|
252 | let pyshell = new PythonShell(scriptPath, options);
|
253 | let output = [];
|
254 | pyshell.on('message', function (message) {
|
255 | output.push(message);
|
256 | }).end(function (err) {
|
257 | if (err) {
|
258 | err.logs = output;
|
259 | reject(err);
|
260 | }
|
261 | else
|
262 | resolve(output);
|
263 | });
|
264 | });
|
265 | }
|
266 | ;
|
267 | |
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 | static runString(code, options) {
|
274 |
|
275 | const randomInt = getRandomInt();
|
276 | const filePath = os_1.tmpdir + path_1.sep + `pythonShellFile${randomInt}.py`;
|
277 | (0, fs_1.writeFileSync)(filePath, code);
|
278 | return PythonShell.run(filePath, options);
|
279 | }
|
280 | ;
|
281 | static getVersion(pythonPath) {
|
282 | if (!pythonPath)
|
283 | pythonPath = this.getPythonPath();
|
284 | return execPromise(pythonPath + " --version");
|
285 | }
|
286 | static getVersionSync(pythonPath) {
|
287 | if (!pythonPath)
|
288 | pythonPath = this.getPythonPath();
|
289 | return (0, child_process_1.execSync)(pythonPath + " --version").toString();
|
290 | }
|
291 | |
292 |
|
293 |
|
294 |
|
295 |
|
296 | parseError(data) {
|
297 | let text = '' + data;
|
298 | let error;
|
299 | if (/^Traceback/.test(text)) {
|
300 |
|
301 | let lines = text.trim().split(os_1.EOL);
|
302 | let exception = lines.pop();
|
303 | error = new PythonShellError(exception);
|
304 | error.traceback = data;
|
305 |
|
306 | error.stack += os_1.EOL + ' ----- Python Traceback -----' + os_1.EOL + ' ';
|
307 | error.stack += lines.slice(1).join(os_1.EOL + ' ');
|
308 | }
|
309 | else {
|
310 |
|
311 | error = new PythonShellError(text);
|
312 | }
|
313 | return error;
|
314 | }
|
315 | ;
|
316 | |
317 |
|
318 |
|
319 |
|
320 |
|
321 | send(message) {
|
322 | if (!this.stdin)
|
323 | throw new Error("stdin not open for writing");
|
324 | let data = this.formatter ? this.formatter(message) : message;
|
325 | if (this.mode !== 'binary')
|
326 | data += os_1.EOL;
|
327 | this.stdin.write(data);
|
328 | return this;
|
329 | }
|
330 | ;
|
331 | |
332 |
|
333 |
|
334 |
|
335 |
|
336 | end(callback) {
|
337 | if (this.childProcess.stdin) {
|
338 | this.childProcess.stdin.end();
|
339 | }
|
340 | this._endCallback = callback;
|
341 | return this;
|
342 | }
|
343 | ;
|
344 | |
345 |
|
346 |
|
347 |
|
348 | kill(signal) {
|
349 | this.terminated = this.childProcess.kill(signal);
|
350 | return this;
|
351 | }
|
352 | ;
|
353 | |
354 |
|
355 |
|
356 |
|
357 | terminate(signal) {
|
358 |
|
359 | return this.kill(signal);
|
360 | }
|
361 | }
|
362 | exports.PythonShell = PythonShell;
|
363 |
|
364 | PythonShell.defaultPythonPath = process.platform != "win32" ? "python3" : "python";
|
365 | PythonShell.defaultOptions = {};
|
366 |
|
367 | PythonShell.format = {
|
368 | text: function toText(data) {
|
369 | if (!data)
|
370 | return '';
|
371 | else if (typeof data !== 'string')
|
372 | return data.toString();
|
373 | return data;
|
374 | },
|
375 | json: function toJson(data) {
|
376 | return JSON.stringify(data);
|
377 | }
|
378 | };
|
379 |
|
380 | PythonShell.parse = {
|
381 | text: function asText(data) {
|
382 | return data;
|
383 | },
|
384 | json: function asJson(data) {
|
385 | return JSON.parse(data);
|
386 | }
|
387 | };
|
388 | ;
|
389 |
|
\ | No newline at end of file |