util.coffee | |
|---|---|
| The | fs = require "fs"
path = require "path"
async = require "async"
{execFile} = require "child_process"
{Stream} = require "stream" |
| The | exports.LineBuffer = class LineBuffer extends Stream |
| Create a | constructor: (@stream) ->
@readable = true
@_buffer = "" |
| Install handlers for the underlying stream's | self = this
@stream.on 'data', (args...) -> self.write args...
@stream.on 'end', (args...) -> self.end args... |
| Write a chunk of data read from the stream to the internal buffer. | write: (chunk) ->
@_buffer += chunk |
| If there's a newline in the buffer, slice the line from the buffer and emit it. Repeat until there are no more newlines. | while (index = @_buffer.indexOf("\n")) != -1
line = @_buffer[0...index]
@_buffer = @_buffer[index+1...@_buffer.length]
@emit 'data', line |
| Process any final lines from the underlying stream's | end: (args...) ->
if args.length > 0
@write args...
@emit 'data', @_buffer if @_buffer.length
@emit 'end' |
| Read lines from | exports.bufferLines = (stream, callback) ->
buffer = new LineBuffer stream
buffer.on "data", callback
buffer |
| | |
| Asynchronously and recursively create a directory if it does not already exist. Then invoke the given callback. | exports.mkdirp = (dirname, callback) ->
fs.lstat (p = path.normalize dirname), (err, stats) ->
if err
paths = [p].concat(p = path.dirname p until p in ["/", "."])
async.forEachSeries paths.reverse(), (p, next) ->
path.exists p, (exists) ->
if exists then next()
else fs.mkdir p, 0o755, (err) ->
if err then callback err
else next()
, callback
else if stats.isDirectory()
callback()
else
callback "file exists" |
| A wrapper around | exports.chown = (path, owner, callback) ->
error = ""
exec ["chown", owner, path], (err, stdout, stderr) ->
if err then callback err, stderr
else callback null |
| Capture all | exports.pause = (stream) ->
queue = []
onData = (args...) -> queue.push ['data', args...]
onEnd = (args...) -> queue.push ['end', args...]
onClose = -> removeListeners()
removeListeners = ->
stream.removeListener 'data', onData
stream.removeListener 'end', onEnd
stream.removeListener 'close', onClose
stream.on 'data', onData
stream.on 'end', onEnd
stream.on 'close', onClose
->
removeListeners()
for args in queue
stream.emit args... |
| Spawn a Bash shell with the given | exports.sourceScriptEnv = (script, env, options, callback) ->
if options.call
callback = options
options = {}
else
options ?= {} |
| Build up the command to execute, starting with the | cwd = path.dirname script
filename = makeTemporaryFilename()
command = """
#{options.before ? "true"} &&
source #{quote script} > /dev/null &&
env > #{quote filename}
""" |
| Run our command through Bash in the directory of the script. If an error occurs, rewrite the error to a more descriptive message. Otherwise, read and parse the environment from the temporary file and pass it along to the callback. | exec ["bash", "-c", command], {cwd, env}, (err, stdout, stderr) ->
if err
err.message = "'#{script}' failed to load:\n#{command}"
err.stdout = stdout
err.stderr = stderr
callback err
else readAndUnlink filename, (err, result) ->
if err then callback err
else callback null, parseEnv result |
| Get the user's login environment by spawning a login shell and
collecting its environment variables via the The returned environment will include a default | exports.getUserEnv = (callback, defaultEncoding = "UTF-8") ->
filename = makeTemporaryFilename()
loginExec "env > #{quote filename}", (err) ->
if err then callback err
else readAndUnlink filename, (err, result) ->
if err then callback err
else getUserLocale (locale) ->
env = parseEnv result
env.LANG ?= "#{locale}.#{defaultEncoding}"
callback null, env |
| Execute a command without spawning a subshell. The command argument is an array of program name and arguments. | exec = (command, options, callback) ->
unless callback?
callback = options
options = {}
execFile "/usr/bin/env", command, options, callback |
| Single-quote a string for command line execution. | quote = (string) -> "'" + string.replace(/\'/g, "'\\''") + "'" |
| Generate and return a unique temporary filename based on the current process's PID, the number of milliseconds elapsed since the UNIX epoch, and a random integer. | makeTemporaryFilename = ->
tmpdir = process.env.TMPDIR ? "/tmp"
timestamp = new Date().getTime()
random = parseInt Math.random() * Math.pow(2, 16)
filename = "pow.#{process.pid}.#{timestamp}.#{random}"
path.join tmpdir, filename |
| Read the contents of a file, unlink the file, then invoke the callback with the contents of the file. | readAndUnlink = (filename, callback) ->
fs.readFile filename, "utf8", (err, contents) ->
if err then callback err
else fs.unlink filename, (err) ->
if err then callback err
else callback null, contents |
| Execute the given command through a login shell and pass the
contents of its stdout and stderr streams to the callback. In order
to spawn a login shell, first spawn the user's shell with the | loginExec = (command, callback) ->
getUserShell (shell) ->
login = ["login", "-qf", process.env.LOGNAME, shell]
exec [login..., "-l", "-c", command], (err, stdout, stderr) ->
if err
exec [login..., "-c", command], callback
else
callback null, stdout, stderr |
| Invoke | getUserShell = (callback) ->
command = ["dscl", ".", "-read", "/Users/#{process.env.LOGNAME}", "UserShell"]
exec command, (err, stdout, stderr) ->
if err
callback process.env.SHELL
else
if matches = stdout.trim().match /^UserShell: (.+)$/
[match, shell] = matches
callback shell
else
callback process.env.SHELL |
| Read the user's current locale preference from the OS X defaults
database. Fall back to | getUserLocale = (callback) ->
exec ["defaults", "read", "-g", "AppleLocale"], (err, stdout, stderr) ->
locale = stdout?.trim() ? ""
locale = "en_US" unless locale.match /^\w+$/
callback locale |
| Parse the output of the | parseEnv = (stdout) ->
env = {}
for line in stdout.split "\n"
if matches = line.match /([^=]+)=(.+)/
[match, name, value] = matches
env[name] = value
env
|