ChildProcess = require 'child_process' # Public: A wrapper which provides standard error/output line buffering for # Node's ChildProcess. # # ## Requiring in packages # # ```coffee # {BufferedProcess} = require 'atom' # # command = 'ps' # args = ['-ef'] # stdout = (output) -> console.log(output) # exit = (code) -> console.log("ps -ef exited with #{code}") # process = new BufferredProcess({command, args, stdout, exit}) # ``` module.exports = class BufferedProcess # Public: Runs the given command by spawning a new child process. # # options - An {Object} with the following keys: # :command - The {String} command to execute. # :args - The {Array} of arguments to pass to the command (optional). # :options - The options {Object} to pass to Node's `ChildProcess.spawn` # method (optional). # :stdout - The callback {Function} that receives a single argument which # contains the standard output from the command. The callback is # called as data is received but it's buffered to ensure only # complete lines are passed until the source stream closes. After # the source stream has closed all remaining data is sent in a # final call (optional). # :stderr - The callback {Function} that receives a single argument which # contains the standard error output from the command. The # callback is called as data is received but it's buffered to # ensure only complete lines are passed until the source stream # closes. After the source stream has closed all remaining data # is sent in a final call (optional). # :exit - The callback {Function} which receives a single argument # containing the exit status (optional). constructor: ({command, args, options, stdout, stderr, exit}={}) -> options ?= {} @process = ChildProcess.spawn(command, args, options) @killed = false stdoutClosed = true stderrClosed = true processExited = true exitCode = 0 triggerExitCallback = -> return if @killed if stdoutClosed and stderrClosed and processExited exit?(exitCode) if stdout stdoutClosed = false @bufferStream @process.stdout, stdout, -> stdoutClosed = true triggerExitCallback() if stderr stderrClosed = false @bufferStream @process.stderr, stderr, -> stderrClosed = true triggerExitCallback() if exit processExited = false @process.on 'exit', (code) -> exitCode = code processExited = true triggerExitCallback() # Helper method to pass data line by line. # # stream - The Stream to read from. # onLines - The callback to call with each line of data. # onDone - The callback to call when the stream has closed. bufferStream: (stream, onLines, onDone) -> stream.setEncoding('utf8') buffered = '' stream.on 'data', (data) => return if @killed buffered += data lastNewlineIndex = buffered.lastIndexOf('\n') if lastNewlineIndex isnt -1 onLines(buffered.substring(0, lastNewlineIndex + 1)) buffered = buffered.substring(lastNewlineIndex + 1) stream.on 'close', => return if @killed onLines(buffered) if buffered.length > 0 onDone() # Public: Terminate the process. kill: -> @killed = true @process.kill() @process = null