Bubble Foundry


Watcher

by Peter.

In doing Python development I’ve found that pyflakes is a great tool to catch stupid errors before I’m halfway through a long run and it fails. However, it only runs once. Inspired by sbt’s ~ option, I whipped up a generic Python program to call another program whenever one or more of the given files change. The only requirements are Growl and growl-py, which can easily be removed.

#!/usr/bin/env python
 
import sys
import os
import subprocess
import sched
import time
from Growl import GrowlNotifier
 
def run():  
  if (len(sys.argv) < 3):
    print 'Usage: watcher files command'
    return False
  relative_files = sys.argv[1:-1]
  command = sys.argv[-1]
  # subtract 1 from the timestamps so that the script is called on the initial versions of the files
  files = [{'file': f, 'ts': os.path.getmtime(f) - 1} for f in relative_files]
 
  # Growl
  growl = GrowlNotifier(applicationName='watcher', notifications=['File(s) changed'])
  growl.register()
 
  interval = 1.0
  s = sched.scheduler(time.time, time.sleep)
  def doWork(sc):
    modified_files = []
    command_list = [command]
    for k, v in enumerate(files):
      f = files[k]['file']
      ts = os.path.getmtime(f)
      if (files[k]['ts'] < ts):
        modified_files.append(f)
      files[k]['ts'] = ts
    if len(modified_files) > 0:
      command_list.extend(modified_files)
      print '-' * 10
      output = subprocess.Popen(command_list, stdout=subprocess.PIPE).communicate()[0].strip()
      if output:
        print output
        growl.notify('File(s) changed', 'Watcher', 'There are problems in one or more of %d file(s).' % len(modified_files))
      else:
        print 'OK'
        growl.notify('File(s) changed', 'Watcher', 'Problems fixed.')
    sc.enter(interval, 1, doWork, (sc,))  
  s.enter(interval, 1, doWork, (s,))
  s.run()
 
if __name__ == '__main__':
  sys.exit(run())

I also added the following alias to my ~/.profile:

alias watcher="python /Users/peter/watcher.py"

I use the script like so:

watcher *.py *.tac pyflakes