Skip to content

Commit

Permalink
write - nearly working
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonah Fox authored and Jonah Fox committed Oct 7, 2010
1 parent 698502e commit 831151b
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 52 deletions.
22 changes: 12 additions & 10 deletions README.md
@@ -1,14 +1,14 @@
Bounce
====

Reloads node upon changes to source files.
Reloads process upon changes to source files and exceptions.
Great for development.

How?
----

Runs node as child process whilst watching JS file in the current directory and below for changes.
If a change is detected, the node child process is restarted. It also looks for new files JS every 10s.
Runs node as child process whilst watching specified files changes.
If a change is detected, the child process is restarted.

All STDOUT AND STDERR should be piped back to the console, so it shouldn't look any different.

Expand All @@ -20,24 +20,26 @@ sudo bin/install
Usage
----

simply replace 'node' for 'bounce'. E.g.

bounce lib/server.js
bounce -w [-r] [-g] 'command_to_run -a -b arg1 arg2'

-w: watch paths, comma separated. If using globs (*) then must be wrapped in quotes, e.g. 'lib/*.*'
-r: respawn on child process exit
-g: growl notify on child process exit (needs growlnotify)
-h: show this usage

Running Tests
----

* bin/bounce test/test.js
* bin/bounce -r -g -w "node test/test.js"

Changes to test.js and watch/file.js should cause the counter to restart.

* bin/bounce test/error.js
* bin/bounce -w "node test/error.js"
Should throw an error and exit immediately

Tested on Ubuntu/OSX on Node 0.2.0 and 0.1.104
Tested on Ubuntu/OSX on Node 0.2.3

Todo
----

* how to handle watching different directories than CWD ?
* npm
111 changes: 71 additions & 40 deletions bin/bounce
@@ -1,45 +1,63 @@
#!/usr/bin/env node

var args = process.ARGV.slice(2),
wdir = "./", //args.shift()
prog = "node", //args.shift()
// RUN LIKE
var watchPaths,
child,
fs = require("fs"),
path = require("path"),
watching = {},
steppedInto = {}
steppedInto = {},
respawnOnExit = false,
glob = require("glob"),
growlNotify = false

var growl = require("growl")
command = process.ARGV[process.ARGV.length-1]
var args = process.ARGV
for(var i=0; i <args.length; i++) {
var arg = args[i]
if(arg == "-w") watchPaths = args[i+1].split(",")
if(arg == "-r") respawnOnExit = true;
if(arg == "-g") growlNotify = true;
if(arg == "-h") usage()
}

function usage() {

log("bounce -w [-r] [-g] 'command_to_run -a -b arg1 arg2' ")
log("")
log("-w: watch paths, comma separated. If using globs (*) then must be wrapped in quotes, e.g. 'lib/*.*' ")
log("-r: respawn on child process exit")
log("-g: growl notify on child process exit (needs growlnotify)")
log("-h: show this usage")
process.exit(0)
}


if(!command || !watchPaths || !watchPaths.length) {
//log("Error: must provide command to run")
usage()
}

function log() {
console.log.apply(null, arguments)
}

function debug(s) {
if(0)
console.log(s)
log(s)
}

function watchDir(dir) {
debug("looking inside dir " + dir)
if(steppedInto[dir]) {
debug("we've already stepped in here, skipping")
return
}
steppedInto[dir] = true

var files = fs.readdirSync(dir)
for(var i =0; i < files.length;i++) {
var file = files[i]
var fullPath = path.join(dir, file)

try {
var stats = fs.statSync(fullPath)
if(stats.isDirectory()) {
if(!file.match(/^\./)) // ignore folders starting with .
watchDir(fullPath)
}
else if(file.match(/\.js$/))
watch(fullPath)
}
catch(e) {
debug(e.toString()) // statSync can throw exception e.g. with cyclic symbolic links
debug("skipping")
}
var files = []

for(var i in watchPaths) {
files = files.concat(glob.globSync(watchPaths[i]))
}

function watchFiles(files) {
for(var i in files) {
log("Watching: " + files[i]);
watch(files[i])
}
}

Expand All @@ -61,22 +79,35 @@ function watch(source) {


function spawn() {

var args = command.split(" ")
var prog = args.shift()
//log(prog, args)
child = require('child_process').spawn(prog, args);
console.log('Spawned ' + prog + " " + args.join(" ") + ", with PID=" + child.pid);
log('Spawned ' + prog + " " + args.join(" ") + ", with PID=" + child.pid);
child.stdout.on('data', function(data) {
data = data.toString().replace(/\n$/,"")
console.log(data);
log(data);
});
child.stderr.on('data', function(data) {
data = data.toString().replace(/\n$/,"")
console.log(data);
log(data);
});
return child.on('exit', function(code, signal) {
if(code == null && signal == "SIGTERM")
respawn()
else {
console.log('node exited with code ' + (code ? code.toString() : "null") + " and signal " + (signal ? signal.toString() : "null"));
process.exit(0)
log('child exited with code ' + (code ? code.toString() : "null") + " and signal " + (signal ? signal.toString() : "null"));
if(growlNotify)
growl.notify(command + " child process exited with code " + (code ? code.toString() : "null"))
if(respawnOnExit) {
log("Respawning")
respawn()
}
else {
log("Exiting")
process.exit(0)
}
}
});
};
Expand All @@ -89,8 +120,8 @@ function respawn() {
}

spawn();
watchDir(wdir);
setInterval(function() {
watchDir(wdir);
}, 10000) // look for new files every 10s

watchFiles(files);
// setInterval(function() {
// watchDir(wdir);
// }, 10000) // look for new files every 10s
2 changes: 1 addition & 1 deletion bin/install
@@ -1,3 +1,3 @@
cp bin/bounce /usr/local/bin
echo "Bounce installed"
echo "now use 'bounce' instead of 'node' to reload"

79 changes: 79 additions & 0 deletions lib/growl.js
@@ -0,0 +1,79 @@
// Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)

/**
* Module dependencies.
*/

var child_process = require('child_process'),
path = require('path')

/**
* Node-growl version.
*/

exports.version = '1.0.2'

/**
* Fetch the binary version when available.
*
* @param {function} callback
* @api public
*/

exports.binVersion = function(callback) {
child_process.exec('growlnotify -v', function(err, stdout, stderr){
if (err) callback(err)
else callback(null, stdout)
})
}

/**
* Send growl notification _msg_ with _options_.
*
* Options:
*
* - title Notification title
* - sticky Make the notification stick (defaults to false)
* - name Application name (defaults to growlnotify)
* - image
* - path to an icon sets --iconpath
* - path to an image sets --image
* - capitalized word sets --appIcon
* - filename uses extname as --icon
* - otherwise treated as --icon
*
* Examples:
*
* growl.notify('New email')
* growl.notify('5 new emails', { title: 'Thunderbird' })
* growl.notify('Email sent', function(){
* // ... notification sent
* })
*
* @param {string} msg
* @param {object} options
* @param {function} callback
* @api public
*/

exports.notify = function(msg, options, callback) {
var image,
args = ['growlnotify', '-m', '"' + msg + '"'],
options = options || {}
exports.binVersion(function(err, version){
if (err) return callback(err)
if (image = options.image) {
var flag, ext = path.extname(image).substr(1)
flag = flag || ext == 'icns' && 'iconpath'
flag = flag || /^[A-Z]/.test(image) && 'appIcon'
flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image'
flag = flag || ext && (image = ext) && 'icon'
flag = flag || 'icon'
args.push('--' + flag, image)
}
if (options.sticky) args.push('--sticky')
if (options.name) args.push('--name', options.name)
if (options.title) args.push(options.title)
child_process.exec(args.join(' '), callback)
})
}
2 changes: 1 addition & 1 deletion test/error.js
@@ -1 +1 @@
throw "error"
throw "error"
3 changes: 3 additions & 0 deletions test/test.js
Expand Up @@ -9,6 +9,9 @@ function go() {
} else {
process.exit(0)
}

if(i == 10)
throw "error!"
}


Expand Down

0 comments on commit 831151b

Please sign in to comment.