UNPKG

pygment

Version:

Python — Node.js inter-process communication through stdio

229 lines (169 loc) 5.14 kB
const EventEmitter = require('events').EventEmitter, spawn = require('child_process').spawn, path = require('path'), util = require('util'), newline = require('os').EOL function toArray(source){ if (typeof source === 'undefined' || source === null) return [] else if (!Array.isArray(source)) return [source] return source } function extend(obj){ Array.prototype.slice .call(arguments, 1) .forEach((source) => { if (source) for (var key in source) obj[key] = source[key] }) return obj } const Pygment = function(script, options){ function resolve(type, val){ if (typeof val === 'string') return Pygment[type][val] else if (typeof val === 'function') return val } const self = this let errorData = '' EventEmitter.call(this) options = extend({}, Pygment.defaultOptions, options) const pythonPath = options.pythonPath || 'python', pythonOptions = toArray(options.pythonOptions), scriptArgs = toArray(options.args) this.script = path.join(options.scriptPath || './', script) this.command = pythonOptions.concat(this.script, scriptArgs) this.mode = options.mode || 'text' this.formatter = resolve('format', options.formatter || this.mode) this.parser = resolve('parse', options.parser || this.mode) this.terminated = false this.childProcess = spawn(pythonPath, this.command, options); ['stdout', 'stdin', 'stderr'].forEach((name) => { self[name] = self.childProcess[name] self.parser && self[name].setEncoding(options.encoding || 'utf8') }) if (this.parser) this.stdout.on('data', Pygment.prototype.receive.bind(this)) this.stderr.on('data', (data) => { errorData += '' + data }) this.stderr.on('end', () => { self.stderrHasEnded = true terminateIfNeeded() }) this.stdout.on('end', () => { self.stdoutHasEnded = true terminateIfNeeded() }) this.childProcess.on('exit', (code, signal) => { self.exitCode = code self.exitSignal = signal terminateIfNeeded() }) function terminateIfNeeded(){ if (!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return var err if (errorData || (self.exitCode && self.exitCode !== 0)){ if (errorData) err = self.parseError(errorData) else err = new Error('process exited with code ' + self.exitCode) err = extend(err, { executable: pythonPath, options: pythonOptions.length ? pythonOptions : null, script: self.script, args: scriptArgs.length ? scriptArgs : null, exitCode: self.exitCode }) if (self.listeners('error').length || !self._endCallback) self.emit('error', err) } self.emit('close') self.terminated = true self._endCallback && self._endCallback(err, self.exitCode, self.exitSignal) } } util.inherits(Pygment, EventEmitter) Pygment.defaultOptions = {} // Built-in formatters Pygment.format = { text: function toText(data){ if (!data) return '' else if (typeof data !== 'string') return data.toString() return data }, json: function toJson(data){ return JSON.stringify(data) } } // Built-in parsers Pygment.parse = { text: function asText(data){ return data }, json: function asJson(data){ return JSON.parse(data) } } Pygment.run = (script, options, callback) => { if (typeof options === 'function'){ callback = options options = null } const pyshell = new Pygment(script, options) let output = [] return pyshell.on('message', (message) => { output.push(message) }).end((err) => { if (err) return callback(err) return callback(null, output.length ? output : null) }) } Pygment.prototype.parseError = (data) => { let text = '' + data, error if (/^Traceback/.test(text)){ let lines = ('' + data).trim().split(new RegExp(newline, 'g')), exception = lines.pop() error = new Error(exception) error.traceback = data error.stack += newline + ' ----- Python Traceback -----' + newline + ' ' error.stack += lines.slice(1).join(newline + ' ') } else error = new Error(text) return error } Pygment.prototype.send = function(message){ let data = this.formatter ? this.formatter(message) : message if (this.mode !== 'binary') data += newline this.stdin.write(data) return this } Pygment.prototype.receive = function(data){ let self = this, parts = ('' + data) .split(new RegExp(newline, 'g')) if (parts.length === 1){ this._remaining = (this._remaining || '') + parts[0] return this } let lastLine = parts.pop() parts[0] = (this._remaining || '') + parts[0] this._remaining = lastLine parts.forEach((part) => { self.emit('message', self.parser(part)) }) return this } Pygment.prototype.end = function(callback){ this.childProcess.stdin.end() this._endCallback = callback return this } Pygment.prototype.terminate = function(signal){ this.childProcess.kill(signal) this.terminated = true return this } module.exports = Pygment