Compare commits

..

No commits in common. "master" and "v0.9" have entirely different histories.
master ... v0.9

4 changed files with 130 additions and 208 deletions

View file

@ -1,13 +1,3 @@
# I'M NOT DEAD, I'M JUST RESTING
Beautiful plumage. But this project no longer functions and some of the functionality here has been implemented
in Node proper via .load and .save in the REPL. I will no longer maintain it (poorly and infrequently).
If you want to contribute a .edit command to Node that would be pretty snazzy. Now that the REPL sucks less
it's less pressing though. You can paste in some code the REPL doesn't understand, or broken syntax, and
then Ctrl-C your way out of it.
repl-edit repl-edit
========= =========
@ -15,51 +5,75 @@ Use your text editor to edit commands in Node's repl.
(tip o' the hat to Giles Bowkett for [inspiration](http://gilesbowkett.blogspot.com/2010/09/vim-in-irb-with-utility-belt.html)) (tip o' the hat to Giles Bowkett for [inspiration](http://gilesbowkett.blogspot.com/2010/09/vim-in-irb-with-utility-belt.html))
Installation Installation
============ ============
npm install repl-edit npm install repl-edit
Usage Usage
===== =====
Typically you just type `require('repl-edit')` in node's repl and it will extend it with new commands, just like `.break` and `.clear` that come with node. You can fire up a repl with editing capabilities by running `node-repl-edit`.
(It would be nice to extend an existing repl session but that's not possible with
Node's repl module right now.)
(You can also fire up a repl with editing capabilities by running `node-repl-edit` in your shell)
Commands Commands
======== ========
.edit edit
-----
The first time you run `.edit` your editor is opened containing the last statement you entered. Type away and then save and close the file when you're done. The code will be loaded and executed immediately. When you subsequently run `.edit` your editor is opened and contains whatever you left there.
Your editor is determined by the `VISUAL` and `EDITOR` environment variables, in that order. You can also change the editor for a single edit by doing something like `.edit vim`.
.run
---- ----
`.run` runs the most recent command you've edited. The first time you run `edit()` in a repl a temporary file is created, specific to that session,
and opened in your editor. Type away and then save and close the file when you're done. The file
is loaded and executed immediately.
.editor
run
---
To run whatever command you've been working on without editing it again type `run()`.
setEditor
---------
`setEditor('mate -w')` changes your editor to TextMate for this session. Note that this
command sets the environment variable EDITOR for the repl process.
stash
-----
`stash('/path/to/a/file')` saves your command to the named file.
unstash
------- -------
`.editor mate -w` changes your editor to TextMate for this session, by setting the environment variable `VISUAL`. `unstash('/path/to/a/file')` restores the contents of that file for you to run and/or edit.
.stash
------
`.stash /path/to/a/file` saves your command to the named file. Future
======
.unstash Instead of polluting the global namespace with functions I'd rather extend Node's repl
-------- to allow user-defined dot commands (just like `.break` and `.clear`), and then use that
capability to provide commands like `.edit` and `.stash <filename>`.
The first time edit() is run in a repl instead of an empty file the command should be seeded
with the last command that was executed.
If the native repl module exports the currently running repl object it will be possible to attach
to an existing repl instead of having to run a separate binary that loads a repl.
`.unstash /path/to/a/file` restores the contents of that file for you to run and/or edit.
License License
======= =======
Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net> Copyright 2010 Sami Samhuri sami@samhuri.net
MIT license, see the included [LICENSE](/samsonjs/repl-edit/blob/master/LICENSE) MIT (see the file named [LICENSE](/samsonjs/repl-edit/blob/master/LICENSE))

View file

@ -8,127 +8,100 @@
// 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')
, replModule = require('repl')
, spawn = require('child_process').spawn , spawn = require('child_process').spawn
, util = require('util') , util = require('util')
, vm = require('vm')
, Hint = 'Commands: .edit, .run, .stash <filename>, .unstash <filename>, .editor <editor>'
, theRepl
module.exports = { startRepl: startRepl, extendRepl: extendRepl } module.exports = { startRepl: startRepl, extendRepl: extendRepl }
var repl
// Start a repl // Start a repl
function startRepl() { function startRepl() {
if (theRepl) { if (repl) {
console.error('repl is already running, only one instance is allowed') console.error('repl is already running, only one instance is allowed')
return return
} }
return extendRepl(replModule.start('> ')) extendRepl(require('repl').start())
} }
function log(s) { function extendRepl(theRepl) {
if (theRepl.outputStream) { if (repl) {
theRepl.outputStream.write(s + '\n' + theRepl.prompt)
theRepl.displayPrompt()
}
}
function extendRepl(aRepl) {
if (theRepl) {
console.error('repl is already running, only one instance is allowed') console.error('repl is already running, only one instance is allowed')
return return
} }
if (!aRepl || typeof aRepl.defineCommand !== 'function') { if (!theRepl || typeof theRepl.defineCommand !== function) {
console.error('bad argument, repl is not compatible') console.error('bad argument, repl is not compatible')
return return
} }
theRepl = aRepl repl = theRepl
var tmpfile = makeTempfile() var tmpfile = makeTempfile()
process.on('exit', function() { process.on('exit', function() {
try { try {
fs.unlinkSync(tmpfile) fs.unlinkSync(tmpfile)
} } catch (e) {
catch (e) {
// might not exist // might not exist
} }
}) })
log(Hint) console.log('Commands: .edit, .run, .stash <filename>, .unstash <filename>, .editor <editor>')
theRepl.defineCommand('edit', { repl.defineCommand('edit', {
help: 'Edit the current command in your text editor' help: 'Edit the current command in your text editor',
, action: function(editor) { action: function(editor) {
edit(tmpfile, editor) edit(tmpfile, editor)
} }
}) })
theRepl.defineCommand('run', { repl.defineCommand('run', {
help: 'Run the previously edited command' help: 'Run the previously edited command',
, action: function() { action: function() {
pause() pause()
run(tmpfile, function() { unpause() }) run(tmpfile, function() { unpause() })
} }
}) })
theRepl.defineCommand('stash', { repl.defineCommand('stash', {
help: 'Write the current command to the named file' help: 'Write the current command to the named file',
, action: function(dest) { action: function(dest) {
stash(tmpfile, dest) stash(tmpfile, dest)
} }
}) })
theRepl.defineCommand('unstash', { repl.defineCommand('unstash', {
help: 'Replace the current command with the contents of the named file' help: 'Replace the current command with the contents of the named file',
, action: function(source) { action: function(source) {
unstash(tmpfile, source) unstash(tmpfile, source)
} }
}) })
theRepl.defineCommand('editor', function(editor) { repl.defineCommand('editor', function(editor) {
process.env['VISUAL'] = editor process.env['VISUAL'] = editor
}) })
return theRepl
} }
// Commands // Commands
function edit(cmdFile, editor) { function edit(cmdfile, editor) {
editor = editor || process.env['VISUAL'] || process.env['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] var fds = [process.openStdin(), process.stdout, process.stdout]
, args = [cmdFile] , args = [cmdfile]
// handle things like 'mate -w' and 'emacsclient --server-file <filename>' // handle things like 'mate -w' and 'emacsclient --server-file <filename>'
if (editor.match(/\s/)) { if (editor.match(/\s/)) {
var words = editor.split(/\s+/) // FIXME this should do proper word splitting ... var words = editor.split(/\s+/) // FIXME this should do proper word splitting ...
args = words.slice(1).concat(args) args = words.slice(1).concat(args)
editor = words[0] editor = words[0]
} }
// seed the file with repl.context._ if the file doesn't exist yet
try {
fs.statSync(cmdFile)
}
catch (e) {
// skip history[0], it's the .edit command
var lastCmd = theRepl.rli.history[1]
if (lastCmd && lastCmd[0] !== '.') {
fs.writeFileSync(cmdFile, lastCmd)
}
}
pause() pause()
spawn(editor, args, {customFds: fds}).on('exit', function(code) { spawn(editor, args, {customFds: fds}).on('exit', function(code) {
// some editors change the terminal resulting in skewed output, clean up // some editors change the terminal resulting in skewed output, clean up
spawn('reset').on('exit', function(_) { spawn('reset').on('exit', function(_) {
if (code === 0) { if (code === 0) {
run(cmdFile, function() { unpause() }) run(cmdfile, function() { unpause() })
} } else {
else {
unpause() unpause()
} }
}) })
@ -138,41 +111,30 @@ function edit(cmdFile, editor) {
function stash(cmdFile, dest) { function stash(cmdFile, dest) {
try { try {
fs.statSync(cmdFile) fs.statSync(cmdFile)
} } catch (e) {
catch (e) { console.log('nothing to stash')
log('nothing to stash')
return
}
dest = dest.trim()
if (!dest) {
log('try: .stash /path/to/file')
return return
} }
var read = fs.createReadStream(cmdFile) var read = fs.createReadStream(cmdFile)
read.on('end', function() { read.on('end', function() {
log('stashed') console.log('stashed')
unpause() unpause()
}) })
// TODO confirm before overwriting an existing file
pause() pause()
util.pump(read, fs.createWriteStream(dest)) util.pump(read, fs.createWriteStream(dest))
} }
function unstash(cmdFile, source) { function unstash(cmdFile, source) {
source = source.trim()
if (!source) {
log('try: .unstash /path/to/file')
return
}
try { try {
fs.statSync(source) fs.statSync(source)
} } catch (e) {
catch (e) { console.log('no stash at ' + source)
log('no stash at ' + source)
return return
} }
var read = fs.createReadStream(source) var read = fs.createReadStream(source)
read.on('end', function() { read.on('end', function() {
log('unstashed') console.log('unstashed')
unpause() unpause()
}) })
pause() pause()
@ -183,30 +145,31 @@ function run(filename, callback) {
// check if file exists. might not have been saved. // check if file exists. might not have been saved.
try { try {
fs.statSync(filename) fs.statSync(filename)
} } catch (e) {
catch (e) { repl.stream.write('nothing to run\n')
log('nothing to run\n')
callback() callback()
return return
} }
var read = fs.createReadStream(filename) var evalcx = require('vm').Script.runInContext
, cmd = '' , read = fs.createReadStream(filename)
read.on('data', function(d) { cmd += d }) , s = ''
read.on('data', function(d) { s += d })
read.on('end', function() { read.on('end', function() {
// The catchall for errors // The catchall for errors
try { try {
var ret = vm.runInContext(cmd, theRepl.context, 'repl'); // Use evalcx to supply the global context
theRepl.context._ = ret var ret = evalcx(s, repl.context, "repl");
theRepl.outputStream.write(replModule.writer(ret) + '\n') if (ret !== undefined) {
} repl.context._ = ret;
catch (e) { repl.writer(ret) + "\n"
}
} catch (e) {
// On error: Print the error and clear the buffer // On error: Print the error and clear the buffer
if (e.stack) { if (e.stack) {
log(e.stack + '\n') repl.stream.write(e.stack + "\n");
} } else {
else { repl.stream.write(e.toString() + "\n");
log(e.toString() + '\n')
} }
} }
if (callback) callback() if (callback) callback()
@ -217,19 +180,19 @@ function run(filename, callback) {
var origPrompt var origPrompt
function unpause() { function unpause() {
theRepl.prompt = origPrompt repl.prompt = origPrompt
theRepl.rli.enabled = true repl.rli.enabled = true
theRepl.outputStream.resume() repl.outputStream.resume()
theRepl.inputStream.resume() repl.inputStream.resume()
theRepl.displayPrompt() repl.displayPrompt()
} }
function pause() { function pause() {
theRepl.outputStream.pause() repl.outputStream.pause()
theRepl.inputStream.pause() repl.inputStream.pause()
theRepl.rli.enabled = false repl.rli.enabled = false
origPrompt = theRepl.prompt || '> ' origPrompt = repl.prompt || '> '
theRepl.prompt = '' repl.prompt = ''
} }
function makeTempfile() { function makeTempfile() {
@ -249,9 +212,5 @@ function makeTempfile() {
return path.join(tmpdir, 'node-repl-' + process.pid + '.js') return path.join(tmpdir, 'node-repl-' + process.pid + '.js')
} }
if (require.main === module) { if (require.main === module) startRepl()
startRepl() else if (module.parent && module.parent.id === 'repl') extendRepl(module.parent.exports.repl)
}
else if (module.parent && module.parent.id === 'repl') {
extendRepl(module.parent.exports.repl)
}

View file

@ -1,34 +1,23 @@
{ { "name" : "repl-edit"
"name": "repl-edit", , "description" : "Edit code in the repl using a real text editor"
"description": "Edit code in the repl using a real text editor", , "version" : "0.9.0"
"version": "0.9.5", , "homepage" : "http://samhuri.net/proj/repl-edit"
"homepage": "http://samhuri.net/proj/repl-edit", , "author" : "Sami Samhuri <sami@samhuri.net>"
"author": "Sami Samhuri <sami@samhuri.net>", , "repository" :
"repository": { { "type" : "git"
"type": "git", , "url" : "https://github.com/samsonjs/repl-edit.git"
"url": "git://github.com/samsonjs/repl-edit.git" }
}, , "bugs" :
"bugs": { { "mail" : "sami.samhuri@gmail.com"
"email": "sami.samhuri@gmail.com", , "web" : "https://github.com/samsonjs/repl-edit/issues"
"url": "https://github.com/samsonjs/repl-edit/issues" }
}, , "directories" : { "lib" : "./lib" }
"directories": { , "bin" : { "node-repl-edit" : "./repl.js" }
"lib": "./lib" , "main" : "./lib/index"
}, , "engines" : { "node" : ">=0.4.0" }
"bin": { , "licenses" :
"node-repl-edit": "./repl.js" [ { "type" : "MIT"
}, , "url" : "https://github.com/samsonjs/repl-edit/raw/master/LICENSE"
"main": "./lib/index",
"engines": {
"node": ">=0.4.0"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/samsonjs/repl-edit/raw/master/LICENSE"
} }
], ]
"dependencies": {},
"devDependencies": {},
"optionalDependencies": {}
} }

42
repl.js
View file

@ -1,43 +1,3 @@
#!/usr/bin/env node #!/usr/bin/env node
var fs = require('fs') require('./lib/index').startRepl()
, path = require('path')
, repl = require('./lib/index').startRepl()
, DefaultHistoryFile = path.join(process.env.HOME, '.node_history')
, historyFile
if ('NODE_HISTORY' in process.env)
historyFile = process.env.NODE_HISTORY
else
historyFile = DefaultHistoryFile
// restore history immediately
if (historyFile) {
try {
fs.statSync(historyFile)
var json = fs.readFileSync(historyFile)
repl.rli.history = JSON.parse(json)
}
catch (e) {
if (e.code !== 'ENOENT') {
console.error('!!! Error reading history from ' + historyFile)
if (e.message === 'Unexpected token ILLEGAL') {
console.error('is this a JSON array of strings? -> ' + json)
}
else {
console.error(e.message)
}
}
}
// save history on exit
process.on('exit', function() {
try {
fs.writeFileSync(historyFile, JSON.stringify(repl.rli.history))
}
catch (e) {
console.error('Error writing history file to ' + historyFile)
console.error(e)
}
})
}