Calls the compiler when it detects an update for a file with the extensions coffee, less, sass, scss. Detects updates to files with extensions html, css, js and reloads the browser.
$ python watcher.py --dir=path/to/dirwatcher.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import time
import commands
from os.path import splitext, extsep, abspath
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
try:
    from selenium import webdriver
    selenium_enabled = True
except ImportError:
    selenium_enabled = False
try:
    import pynotify
except ImportError:
    def notify(*args):
        """ dummy """
else:
    pynotify.init("Watcher")
    def notify(filename, error):
        notify = pynotify.Notification(filename, error, "dialog-warning")
        notify.set_timeout(1000)
        notify.show()
def get_ext(filename):
    return splitext(filename)[1].lstrip(extsep).lower()
class Compiler(FileSystemEventHandler):
    def __init__(self):
        self.compilers = {}
    def on_modified(self, event):
        ext = get_ext(event.src_path)
        if ext in self.compilers:
            status, output = self.compilers[ext](event.src_path)
            if status != 0:
                self.fail(event.src_path, output)
            else:
                self.success(event.src_path)
    def fail(self, filename, error):
        print "\x1b[31mError!: %s" % filename
        print error, "\x1b[39m"
        notify(filename, error)
    def success(self, filename):
        print "\x1b[32mCompiled: %s\x1b[39m" % filename
    def register(self, ext):
        def _register(compiler):
            self.compilers[ext] = compiler
            return compiler
        return _register
default_compiler = Compiler()
@default_compiler.register("coffee")
def coffee_compiler(path):
    return commands.getstatusoutput("coffee -c %s" % path)
@default_compiler.register("sass")
def sass_compiler(path):
    return commands.getstatusoutput("sass --update %s" % path)
@default_compiler.register("scss")
def sass_compiler(path):
    return commands.getstatusoutput("scss --update %s" % path)
@default_compiler.register("less")
def sass_compiler(path):
    out = splitext(path)[0]
    return commands.getstatusoutput("lessc %s > %s.css" % (path, out))
class BrowserReloader(FileSystemEventHandler):
    watch_ext = ["html", "js", "css"]
    def __init__(self, browser_name, url):
        super(BrowserReloader, self).__init__()
        if browser_name == "Chrome":
            self.browser = webdriver.Chrome()
        elif browser_name == "Firefox":
            self.browser = webdriver.Firefox()
        else:
            raise ValueError("Unknown browser '%s'" % browser_name)
        self.browser.get(url)
    def on_modified(self, event):
        ext = get_ext(event.src_path)
        if ext in self.watch_ext:
            self.browser.refresh()
def watch(dir, url, browser):
    observer = Observer()
    if selenium_enabled:
        reloader = BrowserReloader(browser.title(), url)
        observer.schedule(reloader, path=dir, recursive=True)
    observer.schedule(default_compiler, path=dir, recursive=True)
    print "Watching for %s" % abspath(dir)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()
if __name__ == '__main__':
    import optparse
    parser = optparse.OptionParser()
    parser.add_option("-d", "--dir", default=".")
    parser.add_option("-u", "--url", default="http://localhost/")
    parser.add_option("-b", "--browser", default="Chrome")
    options, args = parser.parse_args()
    watch(options.dir, options.url, options.browser)
        Recommended Posts