mirror of
https://github.com/samsonjs/repl-edit.git
synced 2026-03-25 09:25:49 +00:00
node 0.4 compat, extend a running repl
This commit is contained in:
parent
acb188db44
commit
6ba1449bbd
1 changed files with 190 additions and 145 deletions
335
lib/index.js
335
lib/index.js
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright 2010 Sami Samhuri @_sjs
|
||||
// Copyright 2010 - 2011 Sami Samhuri @_sjs
|
||||
// MIT license
|
||||
//
|
||||
// github.com/samsonjs/repl-edit
|
||||
|
|
@ -8,164 +8,209 @@
|
|||
// TODO proper error handling, better intregration with node/lib/repl.js
|
||||
|
||||
var fs = require('fs')
|
||||
, repl = require('repl')
|
||||
, _repl
|
||||
, _tmpdir
|
||||
, spawn = require('child_process').spawn
|
||||
, util = require('util')
|
||||
|
||||
// Find a suitable directory to store the REPL session
|
||||
if (process.env['TMPDIR']) _tmpdir = process.env['TMPDIR']
|
||||
else {
|
||||
module.exports = { startRepl: startRepl, extendRepl: extendRepl }
|
||||
|
||||
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 {
|
||||
fs.statSync('/tmp')
|
||||
_tmpdir = '/tmp'
|
||||
fs.unlinkSync(tmpfile)
|
||||
} 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() {
|
||||
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
|
||||
}
|
||||
// Commands
|
||||
|
||||
exports.extend = function(obj) {
|
||||
var path = require('path')
|
||||
, spawn = require('child_process').spawn
|
||||
, sys = require('sys')
|
||||
, _tmpfile = path.join(_tmpdir, 'node-repl-' + process.pid + '.js')
|
||||
|
||||
obj.edit = function(editor) {
|
||||
editor = editor || process.ENV['VISUAL'] || process.ENV['EDITOR']
|
||||
// TODO seed the file with _repl.context._ if the file doesn't exist yet
|
||||
pausingRepl(function(unpause) {
|
||||
var fds = [process.openStdin(), process.stdout, process.stdout]
|
||||
, args = [_tmpfile]
|
||||
// handle things like 'mate -w' and 'emacsclient --server-file <filename>'
|
||||
if (editor.match(/\s/)) {
|
||||
var words = editor.split(/\s+/) // FIXME this should do proper word splitting ...
|
||||
args = words.slice(1).concat(args)
|
||||
editor = words[0]
|
||||
}
|
||||
spawn(editor, args, {customFds: fds}).on('exit', function(code) {
|
||||
// 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
|
||||
}
|
||||
function edit(cmdfile, editor) {
|
||||
editor = editor || process.env['VISUAL'] || process.env['EDITOR']
|
||||
// TODO seed the file with repl.context._ if the file doesn't exist yet
|
||||
var fds = [process.openStdin(), process.stdout, process.stdout]
|
||||
, args = [cmdfile]
|
||||
// handle things like 'mate -w' and 'emacsclient --server-file <filename>'
|
||||
if (editor.match(/\s/)) {
|
||||
var words = editor.split(/\s+/) // FIXME this should do proper word splitting ...
|
||||
args = words.slice(1).concat(args)
|
||||
editor = words[0]
|
||||
}
|
||||
pause()
|
||||
spawn(editor, args, {customFds: fds}).on('exit', function(code) {
|
||||
// some editors change the terminal resulting in skewed output, clean up
|
||||
spawn('reset').on('exit', function(_) {
|
||||
if (code === 0) {
|
||||
run(cmdfile, function() { unpause() })
|
||||
} else {
|
||||
unpause()
|
||||
}
|
||||
})
|
||||
|
||||
return exports
|
||||
})
|
||||
}
|
||||
|
||||
function pausingRepl(fn) {
|
||||
var origPrompt = _repl.prompt || '> '
|
||||
_repl.stream.pause()
|
||||
_repl.rli.enabled = false
|
||||
_repl.prompt = ''
|
||||
fn(function() {
|
||||
_repl.prompt = origPrompt
|
||||
_repl.rli.enabled = true
|
||||
_repl.stream.resume()
|
||||
_repl.displayPrompt()
|
||||
})
|
||||
function stash(cmdFile, dest) {
|
||||
try {
|
||||
fs.statSync(cmdFile)
|
||||
} catch (e) {
|
||||
console.log('nothing to stash')
|
||||
return
|
||||
}
|
||||
var read = fs.createReadStream(cmdFile)
|
||||
read.on('end', function() {
|
||||
console.log('stashed')
|
||||
unpause()
|
||||
})
|
||||
// TODO confirm before overwriting an existing file
|
||||
pause()
|
||||
util.pump(read, fs.createWriteStream(dest))
|
||||
}
|
||||
|
||||
function runFile(filename, callback) {
|
||||
// check if file exists. might not have been saved.
|
||||
function unstash(cmdFile, source) {
|
||||
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 {
|
||||
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) {
|
||||
_repl.stream.write('nothing to run\n')
|
||||
callback()
|
||||
return
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
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 (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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue