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