Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. return new (P || (P = Promise))(function (resolve, reject) {
  4. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  5. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  6. function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
  7. step((generator = generator.apply(thisArg, _arguments || [])).next());
  8. });
  9. };
  10. Object.defineProperty(exports, "__esModule", { value: true });
  11. const events_1 = require("events");
  12. const child_process_1 = require("child_process");
  13. const os_1 = require("os");
  14. const path_1 = require("path");
  15. const fs_1 = require("fs");
  16. const util_1 = require("util");
  17. function toArray(source) {
  18. if (typeof source === 'undefined' || source === null) {
  19. return [];
  20. }
  21. else if (!Array.isArray(source)) {
  22. return [source];
  23. }
  24. return source;
  25. }
  26. /**
  27. * adds arguments as properties to obj
  28. */
  29. function extend(obj, ...args) {
  30. Array.prototype.slice.call(arguments, 1).forEach(function (source) {
  31. if (source) {
  32. for (let key in source) {
  33. obj[key] = source[key];
  34. }
  35. }
  36. });
  37. return obj;
  38. }
  39. /**
  40. * gets a random int from 0-10000000000
  41. */
  42. function getRandomInt() {
  43. return Math.floor(Math.random() * 10000000000);
  44. }
  45. class PythonShellError extends Error {
  46. }
  47. exports.PythonShellError = PythonShellError;
  48. /**
  49. * An interactive Python shell exchanging data through stdio
  50. * @param {string} script The python script to execute
  51. * @param {object} [options] The launch options (also passed to child_process.spawn)
  52. * @constructor
  53. */
  54. class PythonShell extends events_1.EventEmitter {
  55. /**
  56. * spawns a python process
  57. * @param scriptPath path to script. Relative to current directory or options.scriptFolder if specified
  58. * @param options
  59. */
  60. constructor(scriptPath, options) {
  61. super();
  62. /**
  63. * returns either pythonshell func (if val string) or custom func (if val Function)
  64. */
  65. function resolve(type, val) {
  66. if (typeof val === 'string') {
  67. // use a built-in function using its name
  68. return PythonShell[type][val];
  69. }
  70. else if (typeof val === 'function') {
  71. // use a custom function
  72. return val;
  73. }
  74. }
  75. if (scriptPath.trim().length == 0)
  76. throw Error("scriptPath cannot be empty! You must give a script for python to run");
  77. let self = this;
  78. let errorData = '';
  79. events_1.EventEmitter.call(this);
  80. options = extend({}, PythonShell.defaultOptions, options);
  81. let pythonPath;
  82. if (!options.pythonPath) {
  83. pythonPath = PythonShell.defaultPythonPath;
  84. }
  85. else
  86. pythonPath = options.pythonPath;
  87. let pythonOptions = toArray(options.pythonOptions);
  88. let scriptArgs = toArray(options.args);
  89. this.scriptPath = path_1.join(options.scriptPath || '', scriptPath);
  90. this.command = pythonOptions.concat(this.scriptPath, scriptArgs);
  91. this.mode = options.mode || 'text';
  92. this.formatter = resolve('format', options.formatter || this.mode);
  93. this.parser = resolve('parse', options.parser || this.mode);
  94. this.stderrParser = resolve('parse', options.stderrParser || this.mode);
  95. this.terminated = false;
  96. this.childProcess = child_process_1.spawn(pythonPath, this.command, options);
  97. ['stdout', 'stdin', 'stderr'].forEach(function (name) {
  98. self[name] = self.childProcess[name];
  99. self.parser && self[name] && self[name].setEncoding(options.encoding || 'utf8');
  100. });
  101. // parse incoming data on stdout
  102. if (this.parser && this.stdout) {
  103. this.stdout.on('data', this.receive.bind(this));
  104. }
  105. // listen to stderr and emit errors for incoming data
  106. if (this.stderr) {
  107. this.stderr.on('data', function (data) {
  108. errorData += '' + data;
  109. self.receiveStderr(data);
  110. });
  111. this.stderr.on('end', function () {
  112. self.stderrHasEnded = true;
  113. terminateIfNeeded();
  114. });
  115. }
  116. else {
  117. self.stderrHasEnded = true;
  118. }
  119. if (this.stdout) {
  120. this.stdout.on('end', function () {
  121. self.stdoutHasEnded = true;
  122. terminateIfNeeded();
  123. });
  124. }
  125. else {
  126. self.stdoutHasEnded = true;
  127. }
  128. this.childProcess.on('exit', function (code, signal) {
  129. self.exitCode = code;
  130. self.exitSignal = signal;
  131. terminateIfNeeded();
  132. });
  133. function terminateIfNeeded() {
  134. if (!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null))
  135. return;
  136. let err;
  137. if (self.exitCode && self.exitCode !== 0) {
  138. if (errorData) {
  139. err = self.parseError(errorData);
  140. }
  141. else {
  142. err = new PythonShellError('process exited with code ' + self.exitCode);
  143. }
  144. err = extend(err, {
  145. executable: pythonPath,
  146. options: pythonOptions.length ? pythonOptions : null,
  147. script: self.scriptPath,
  148. args: scriptArgs.length ? scriptArgs : null,
  149. exitCode: self.exitCode
  150. });
  151. // do not emit error if only a callback is used
  152. if (self.listeners('error').length || !self._endCallback) {
  153. self.emit('error', err);
  154. }
  155. }
  156. self.terminated = true;
  157. self.emit('close');
  158. self._endCallback && self._endCallback(err, self.exitCode, self.exitSignal);
  159. }
  160. ;
  161. }
  162. /**
  163. * checks syntax without executing code
  164. * @param {string} code
  165. * @returns {Promise} rejects w/ stderr if syntax failure
  166. */
  167. static checkSyntax(code) {
  168. return __awaiter(this, void 0, void 0, function* () {
  169. const randomInt = getRandomInt();
  170. const filePath = os_1.tmpdir() + path_1.sep + `pythonShellSyntaxCheck${randomInt}.py`;
  171. // todo: replace this with util.promisify (once we no longer support node v7)
  172. return new Promise((resolve, reject) => {
  173. fs_1.writeFile(filePath, code, (err) => {
  174. if (err)
  175. reject(err);
  176. resolve(this.checkSyntaxFile(filePath));
  177. });
  178. });
  179. });
  180. }
  181. static getPythonPath() {
  182. return this.defaultOptions.pythonPath ? this.defaultOptions.pythonPath : this.defaultPythonPath;
  183. }
  184. /**
  185. * checks syntax without executing code
  186. * @param {string} filePath
  187. * @returns {Promise} rejects w/ stderr if syntax failure
  188. */
  189. static checkSyntaxFile(filePath) {
  190. return __awaiter(this, void 0, void 0, function* () {
  191. const pythonPath = this.getPythonPath();
  192. const compileCommand = `${pythonPath} -m py_compile ${filePath}`;
  193. return new Promise((resolve, reject) => {
  194. child_process_1.exec(compileCommand, (error, stdout, stderr) => {
  195. if (error == null)
  196. resolve();
  197. else
  198. reject(stderr);
  199. });
  200. });
  201. });
  202. }
  203. /**
  204. * Runs a Python script and returns collected messages
  205. * @param {string} scriptPath The path to the script to execute
  206. * @param {Options} options The execution options
  207. * @param {Function} callback The callback function to invoke with the script results
  208. * @return {PythonShell} The PythonShell instance
  209. */
  210. static run(scriptPath, options, callback) {
  211. let pyshell = new PythonShell(scriptPath, options);
  212. let output = [];
  213. return pyshell.on('message', function (message) {
  214. output.push(message);
  215. }).end(function (err) {
  216. if (err)
  217. return callback(err);
  218. return callback(null, output.length ? output : null);
  219. });
  220. }
  221. ;
  222. /**
  223. * Runs the inputted string of python code and returns collected messages. DO NOT ALLOW UNTRUSTED USER INPUT HERE!
  224. * @param {string} code The python code to execute
  225. * @param {Options} options The execution options
  226. * @param {Function} callback The callback function to invoke with the script results
  227. * @return {PythonShell} The PythonShell instance
  228. */
  229. static runString(code, options, callback) {
  230. // put code in temp file
  231. const randomInt = getRandomInt();
  232. const filePath = os_1.tmpdir + path_1.sep + `pythonShellFile${randomInt}.py`;
  233. fs_1.writeFileSync(filePath, code);
  234. return PythonShell.run(filePath, options, callback);
  235. }
  236. ;
  237. static getVersion(pythonPath) {
  238. if (!pythonPath)
  239. pythonPath = this.getPythonPath();
  240. const execPromise = util_1.promisify(child_process_1.exec);
  241. return execPromise(pythonPath + " --version");
  242. }
  243. static getVersionSync(pythonPath) {
  244. if (!pythonPath)
  245. pythonPath = this.getPythonPath();
  246. return child_process_1.execSync(pythonPath + " --version").toString();
  247. }
  248. /**
  249. * Parses an error thrown from the Python process through stderr
  250. * @param {string|Buffer} data The stderr contents to parse
  251. * @return {Error} The parsed error with extended stack trace when traceback is available
  252. */
  253. parseError(data) {
  254. let text = '' + data;
  255. let error;
  256. if (/^Traceback/.test(text)) {
  257. // traceback data is available
  258. let lines = ('' + data).trim().split(new RegExp(os_1.EOL, 'g'));
  259. let exception = lines.pop();
  260. error = new PythonShellError(exception);
  261. error.traceback = data;
  262. // extend stack trace
  263. error.stack += os_1.EOL + ' ----- Python Traceback -----' + os_1.EOL + ' ';
  264. error.stack += lines.slice(1).join(os_1.EOL + ' ');
  265. }
  266. else {
  267. // otherwise, create a simpler error with stderr contents
  268. error = new PythonShellError(text);
  269. }
  270. return error;
  271. }
  272. ;
  273. /**
  274. * Sends a message to the Python shell through stdin
  275. * Override this method to format data to be sent to the Python process
  276. * @param {string|Object} data The message to send
  277. * @returns {PythonShell} The same instance for chaining calls
  278. */
  279. send(message) {
  280. if (!this.stdin)
  281. throw new Error("stdin not open for writting");
  282. let data = this.formatter ? this.formatter(message) : message;
  283. if (this.mode !== 'binary')
  284. data += os_1.EOL;
  285. this.stdin.write(data);
  286. return this;
  287. }
  288. ;
  289. /**
  290. * Parses data received from the Python shell stdout stream and emits "message" events
  291. * This method is not used in binary mode
  292. * Override this method to parse incoming data from the Python process into messages
  293. * @param {string|Buffer} data The data to parse into messages
  294. */
  295. receive(data) {
  296. return this.receiveInternal(data, 'message');
  297. }
  298. ;
  299. /**
  300. * Parses data received from the Python shell stderr stream and emits "stderr" events
  301. * This method is not used in binary mode
  302. * Override this method to parse incoming logs from the Python process into messages
  303. * @param {string|Buffer} data The data to parse into messages
  304. */
  305. receiveStderr(data) {
  306. return this.receiveInternal(data, 'stderr');
  307. }
  308. ;
  309. receiveInternal(data, emitType) {
  310. let self = this;
  311. let parts = ('' + data).split(new RegExp(os_1.EOL, 'g'));
  312. if (parts.length === 1) {
  313. // an incomplete record, keep buffering
  314. this._remaining = (this._remaining || '') + parts[0];
  315. return this;
  316. }
  317. let lastLine = parts.pop();
  318. // fix the first line with the remaining from the previous iteration of 'receive'
  319. parts[0] = (this._remaining || '') + parts[0];
  320. // keep the remaining for the next iteration of 'receive'
  321. this._remaining = lastLine;
  322. parts.forEach(function (part) {
  323. if (emitType == 'message')
  324. self.emit(emitType, self.parser(part));
  325. else if (emitType == 'stderr')
  326. self.emit(emitType, self.stderrParser(part));
  327. });
  328. return this;
  329. }
  330. /**
  331. * Closes the stdin stream, which should cause the process to finish its work and close
  332. * @returns {PythonShell} The same instance for chaining calls
  333. */
  334. end(callback) {
  335. if (this.childProcess.stdin) {
  336. this.childProcess.stdin.end();
  337. }
  338. this._endCallback = callback;
  339. return this;
  340. }
  341. ;
  342. /**
  343. * Closes the stdin stream, which should cause the process to finish its work and close
  344. * @returns {PythonShell} The same instance for chaining calls
  345. */
  346. terminate(signal) {
  347. this.childProcess.kill(signal);
  348. this.terminated = true;
  349. return this;
  350. }
  351. ;
  352. }
  353. // starting 2020 python2 is deprecated so we choose 3 as default
  354. PythonShell.defaultPythonPath = process.platform != "win32" ? "python3" : "py";
  355. PythonShell.defaultOptions = {}; //allow global overrides for options
  356. // built-in formatters
  357. PythonShell.format = {
  358. text: function toText(data) {
  359. if (!data)
  360. return '';
  361. else if (typeof data !== 'string')
  362. return data.toString();
  363. return data;
  364. },
  365. json: function toJson(data) {
  366. return JSON.stringify(data);
  367. }
  368. };
  369. //built-in parsers
  370. PythonShell.parse = {
  371. text: function asText(data) {
  372. return data;
  373. },
  374. json: function asJson(data) {
  375. return JSON.parse(data);
  376. }
  377. };
  378. exports.PythonShell = PythonShell;
  379. ;
  380. //# sourceMappingURL=index.js.map