Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

170 lines (126 sloc) 4.883 kb
_ = require 'underscore'
_.str = require 'underscore.string'
program = require 'commander'
colors = require 'colors'
fs = require 'fs'
{exec} = require 'child_process'
watch = require 'watch'
class App
constructor: ->
program
.option('-x, --exec [command]', 'string to execute globally on any file change', '')
.option('-l, --local [command]', "string to execute locally on any file that's changed", '')
.option('-w, --watch [directory]', 'directory to watch', '.')
.option('-e, --extension [extensions]', 'list of file extensions to watch', '')
.option('-d, --dot', 'watch hidden dot files')
.parse(process.argv)
@extensions = _.str.words program.extension, '|'
@pollInterval = 500
@lastTime = @currentTime()
# releases keep commands and logs from getting printed over and over
# when there is a sudden influx of changes or messages
@errRelease = @gRelease = @currentTime() + 1
if process.platform == 'darwin'
@setDarwinWatcher()
else if not fs.watch?
@setWatchWatcher()
else
@setNodeFsWatcher()
@gPiper = @execAndPipe program.exec
@lPipers = {}
@run()
currentTime: ->
Math.round(new Date().getTime() / 1000)
setDarwinWatcher: ->
repeatCheck = {}
@watcher = (cb) =>
piper = exec "find -L #{program.watch} -type f -mtime -#{@currentTime() - @lastTime}s -print"
# piper.stderr.pipe process.stderr
piper.stdout.on 'data', (data) =>
@lastTime = @currentTime()
files = _.str.words data, '\n'
for file in files
# protect against random 'no such file or directory' errors
try
stats = fs.statSync file
catch err
continue
isRepeat = repeatCheck[file]?.getTime() == stats.mtime.getTime()
if isRepeat then continue
repeatCheck[file] = stats.mtime
cb(file)
piper.on 'exit', (code) =>
setTimeout (=> @watcher(cb)), @pollInterval
setWatchWatcher: ->
@watcher = (cb) =>
watch.watchTree program.watch, (file, curr, prev) =>
if prev? and curr.nlink != 0
cb(file)
setNodeFsWatcher: ->
@watcher = (cb) =>
fs.watch program.watch, (e, file) =>
if e == 'change' or e == 'rename'
cb(file)
parsePath: (path) ->
cleanPath = path
# remove './' prefix
if path[0..1] == './' then cleanPath = path[2..]
# escape any spaces in path
cleanPath = cleanPath.replace /\ /g, '\\ '
fileName = cleanPath[cleanPath.lastIndexOf('/')+1..]
extension = fileName[fileName.lastIndexOf('.')+1..]
# make sure we didn't get garbage if e.g. there's no extension on the file
if extension == fileName or extension == cleanPath then extension = ''
return [cleanPath, fileName, extension]
execAndPipe: (command) ->
piper = exec command
piper.stderr.on 'data', (data) =>
if @currentTime() > @errRelease
@errRelease = @currentTime() + 1
console.log ''
console.log " [noprob] Error detected.".red.bold
console.log " [noprob] No worries, I'll wait until you've changed something...".red.italic
piper.stderr.pipe process.stdout
piper.stdout.pipe process.stdout
piper.on 'exit', (code) =>
piper.dead = true
return piper
takeCareOfIt: (what) ->
console.log " [noprob] No prob, I'll take care of that...".green.italic
console.log " [noprob] Executing '#{what}'".green
globalExec: ->
emptyOrTooSoon = program.exec == '' or @currentTime() < @gRelease
if emptyOrTooSoon then return
@gRelease = @currentTime() + 1
@takeCareOfIt program.exec
@gPiper?.kill()
@gPiper = @execAndPipe program.exec
localExec: (cleanPath, fileName, extension) ->
empty = program.local == ''
if empty then return
directory = cleanPath[0...cleanPath.length-fileName.length]
tempCmd = program.local
tempCmd = tempCmd.replace(/<full>/g, cleanPath)
tempCmd = tempCmd.replace(/<dir>/g, directory)
tempCmd = tempCmd.replace(/<ext>/g, extension)
tempCmd = tempCmd.replace(/<fullfile>/g, fileName)
if extension != ''
nameOnly = fileName[0...fileName.length-extension.length-1]
tempCmd = tempCmd.replace(/<name>/g, nameOnly)
else
tempCmd = tempCmd.replace(/<name>/g, fileName)
@takeCareOfIt tempCmd
@lPipers[cleanPath]?.kill()
@lPipers[cleanPath] = @execAndPipe tempCmd
run: ->
console.log ' [noprob] Watching for changes...'.green.bold
@watcher (file) =>
[cleanPath, fileName, extension] = @parsePath file
noRequiredExtension = @extensions.indexOf(extension) == -1 and program.extension != ''
if noRequiredExtension then return
hasDotFile = fileName[0] == '.' or cleanPath[0] == '.' or cleanPath.indexOf('/.') != -1
if hasDotFile and not program.dot then return
console.log " [noprob] Change detected in #{fileName}.".green.bold
@globalExec()
@localExec(cleanPath, fileName, extension)
app = new App()
Jump to Line
Something went wrong with that request. Please try again.