ファイルの更新を検知して、自動的にCoffeeやSassをコンパイルしたり、ブラウザをリロードしたりするスクリプト

### :warning: この記事の内容はかなり古いです。gulpやlivereloadなどの別のツールを使ってください

---------------------------

拡張子がcoffee, less, sass, scssのファイルの更新を検知すると、コンパイラを呼び出します。
拡張子がhtml, css, jsのファイルの更新を検知すると、ブラウザをリロードします。
######インストールの方法と使い方
1. easy_installでseleniumとwatchdogをインストールします。
2. [ChromeDriver](http://code.google.com/p/chromedriver/downloads/list)をPATHの通っているところに置きます。
3. ```$ python watcher.py --dir=path/to/dir```


```python:watcher.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)
```