pygment
Version:
Python — Node.js inter-process communication through stdio
229 lines (169 loc) • 5.14 kB
JavaScript
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