node 0.4 compat, extend a running repl

This commit is contained in:
Sami Samhuri 2011-05-17 23:59:31 -07:00
parent acb188db44
commit 6ba1449bbd

View file

@ -1,5 +1,5 @@
// //
// Copyright 2010 Sami Samhuri @_sjs // Copyright 2010 - 2011 Sami Samhuri @_sjs
// MIT license // MIT license
// //
// github.com/samsonjs/repl-edit // github.com/samsonjs/repl-edit
@ -8,164 +8,209 @@
// TODO proper error handling, better intregration with node/lib/repl.js // TODO proper error handling, better intregration with node/lib/repl.js
var fs = require('fs') var fs = require('fs')
, repl = require('repl') , spawn = require('child_process').spawn
, _repl , util = require('util')
, _tmpdir
// Find a suitable directory to store the REPL session module.exports = { startRepl: startRepl, extendRepl: extendRepl }
if (process.env['TMPDIR']) _tmpdir = process.env['TMPDIR']
else { var repl
// Start a repl
function startRepl() {
if (repl) {
console.error('repl is already running, only one instance is allowed')
return
}
extendRepl(require('repl').start())
}
function extendRepl(theRepl) {
if (repl) {
console.error('repl is already running, only one instance is allowed')
return
}
if (!theRepl || typeof theRepl.defineCommand !== function) {
console.error('bad argument, repl is not compatible')
return
}
repl = theRepl
var tmpfile = makeTempfile()
process.on('exit', function() {
try { try {
fs.statSync('/tmp') fs.unlinkSync(tmpfile)
_tmpdir = '/tmp'
} catch (e) { } catch (e) {
_tmpdir = process.cwd() // might not exist
} }
})
console.log('Commands: .edit, .run, .stash <filename>, .unstash <filename>, .editor <editor>')
repl.defineCommand('edit', {
help: 'Edit the current command in your text editor',
action: function(editor) {
edit(tmpfile, editor)
}
})
repl.defineCommand('run', {
help: 'Run the previously edited command',
action: function() {
pause()
run(tmpfile, function() { unpause() })
}
})
repl.defineCommand('stash', {
help: 'Write the current command to the named file',
action: function(dest) {
stash(tmpfile, dest)
}
})
repl.defineCommand('unstash', {
help: 'Replace the current command with the contents of the named file',
action: function(source) {
unstash(tmpfile, source)
}
})
repl.defineCommand('editor', function(editor) {
process.env['VISUAL'] = editor
})
} }
exports.startRepl = function() { // Commands
console.log('Commands: edit(), run(), stash(filename), unstash(filename), setEditor(editor)')
// TODO extend the repl context instead, problem is that repl.js:resetContext() isn't exported
// so simple assignments to _repl.context can't survive .clear yet
exports.extend(global)
_repl = repl.start()
return exports
}
exports.extend = function(obj) { function edit(cmdfile, editor) {
var path = require('path') editor = editor || process.env['VISUAL'] || process.env['EDITOR']
, spawn = require('child_process').spawn // TODO seed the file with repl.context._ if the file doesn't exist yet
, sys = require('sys') var fds = [process.openStdin(), process.stdout, process.stdout]
, _tmpfile = path.join(_tmpdir, 'node-repl-' + process.pid + '.js') , args = [cmdfile]
// handle things like 'mate -w' and 'emacsclient --server-file <filename>'
obj.edit = function(editor) { if (editor.match(/\s/)) {
editor = editor || process.ENV['VISUAL'] || process.ENV['EDITOR'] var words = editor.split(/\s+/) // FIXME this should do proper word splitting ...
// TODO seed the file with _repl.context._ if the file doesn't exist yet args = words.slice(1).concat(args)
pausingRepl(function(unpause) { editor = words[0]
var fds = [process.openStdin(), process.stdout, process.stdout] }
, args = [_tmpfile] pause()
// handle things like 'mate -w' and 'emacsclient --server-file <filename>' spawn(editor, args, {customFds: fds}).on('exit', function(code) {
if (editor.match(/\s/)) { // some editors change the terminal resulting in skewed output, clean up
var words = editor.split(/\s+/) // FIXME this should do proper word splitting ... spawn('reset').on('exit', function(_) {
args = words.slice(1).concat(args) if (code === 0) {
editor = words[0] run(cmdfile, function() { unpause() })
} } else {
spawn(editor, args, {customFds: fds}).on('exit', function(code) { unpause()
// some editors change the terminal resulting in skewed output, clean up }
spawn('reset').on('exit', function(_) {
if (code === 0) {
runFile(_tmpfile, function() { unpause() })
} else {
unpause()
}
})
})
})
}
obj.run = function() {
pausingRepl(function(unpause) {
runFile(_tmpfile, function() { unpause() })
})
}
obj.setEditor = function(editor) {
process.ENV['VISUAL'] = editor
}
obj.stash = function(dest) {
try {
fs.statSync(_tmpfile)
} catch (e) {
console.log('nothing to stash')
return
}
pausingRepl(function(unpause) {
var read = fs.createReadStream(_tmpfile)
read.on('end', function() {
console.log('stashed')
unpause()
})
// TODO confirm before overwriting an existing file
sys.pump(read, fs.createWriteStream(dest))
})
}
obj.unstash = function(source) {
try {
fs.statSync(source)
} catch (e) {
console.log('no stash at ' + source)
return
}
pausingRepl(function(unpause) {
var read = fs.createReadStream(source)
read.on('end', function() {
console.log('unstashed')
unpause()
})
sys.pump(read, fs.createWriteStream(_tmpfile))
})
}
process.on('exit', function() {
try {
fs.unlinkSync(_tmpfile)
} catch (e) {
// might not exist
}
}) })
})
return exports
} }
function pausingRepl(fn) { function stash(cmdFile, dest) {
var origPrompt = _repl.prompt || '> ' try {
_repl.stream.pause() fs.statSync(cmdFile)
_repl.rli.enabled = false } catch (e) {
_repl.prompt = '' console.log('nothing to stash')
fn(function() { return
_repl.prompt = origPrompt }
_repl.rli.enabled = true var read = fs.createReadStream(cmdFile)
_repl.stream.resume() read.on('end', function() {
_repl.displayPrompt() console.log('stashed')
}) unpause()
})
// TODO confirm before overwriting an existing file
pause()
util.pump(read, fs.createWriteStream(dest))
} }
function runFile(filename, callback) { function unstash(cmdFile, source) {
// check if file exists. might not have been saved. try {
fs.statSync(source)
} catch (e) {
console.log('no stash at ' + source)
return
}
var read = fs.createReadStream(source)
read.on('end', function() {
console.log('unstashed')
unpause()
})
pause()
util.pump(read, fs.createWriteStream(cmdFile))
}
function run(filename, callback) {
// check if file exists. might not have been saved.
try {
fs.statSync(filename)
} catch (e) {
repl.stream.write('nothing to run\n')
callback()
return
}
var evalcx = require('vm').Script.runInContext
, read = fs.createReadStream(filename)
, s = ''
read.on('data', function(d) { s += d })
read.on('end', function() {
// The catchall for errors
try { try {
fs.statSync(filename) // Use evalcx to supply the global context
var ret = evalcx(s, repl.context, "repl");
if (ret !== undefined) {
repl.context._ = ret;
repl.writer(ret) + "\n"
}
} catch (e) { } catch (e) {
_repl.stream.write('nothing to run\n') // On error: Print the error and clear the buffer
callback() if (e.stack) {
return repl.stream.write(e.stack + "\n");
} else {
repl.stream.write(e.toString() + "\n");
}
} }
if (callback) callback()
var Script = process.binding('evals').Script })
, evalcx = Script.runInContext
, read = fs.createReadStream(filename)
, s = ''
read.on('data', function(d) { s += d })
read.on('end', function() {
// The catchall for errors
try {
// Use evalcx to supply the global context
var ret = evalcx(s, _repl.context, "repl");
if (ret !== undefined) {
_repl.context._ = ret;
repl.writer(ret) + "\n"
}
} catch (e) {
// On error: Print the error and clear the buffer
if (e.stack) {
_repl.stream.write(e.stack + "\n");
} else {
_repl.stream.write(e.toString() + "\n");
}
}
if (callback) callback()
})
} }
if (require.main === module) exports.startRepl() // Support
var origPrompt
function unpause() {
repl.prompt = origPrompt
repl.rli.enabled = true
repl.outputStream.resume()
repl.inputStream.resume()
repl.displayPrompt()
}
function pause() {
repl.outputStream.pause()
repl.inputStream.pause()
repl.rli.enabled = false
origPrompt = repl.prompt || '> '
repl.prompt = ''
}
function makeTempfile() {
var path = require('path')
, tmpdir
// Find a suitable directory to store the REPL session
if (process.env['TMPDIR']) tmpdir = process.env['TMPDIR']
else {
try {
fs.statSync('/tmp')
tmpdir = '/tmp'
} catch (e) {
tmpdir = process.cwd()
}
}
return path.join(tmpdir, 'node-repl-' + process.pid + '.js')
}
if (require.main === module) startRepl()
else if (module.parent && module.parent.id === 'repl') extendRepl(module.parent.exports.repl)