Skip to main content

watchmake.py (Source)

"""
Requires the ``watchdog`` package.
Watch the current directory and its children for changes to files, and run ``make`` when certain files are changed.
Can be configured by a .watchmakerc file, containing settings in YAML format:
    path = <the path to watch for changes> (default: .)
    default_make: <a list of `make` targets to run> (default: empty list, so the first target in the Makefile)
    extensions: <a list of file extensions that should trigger a ``make`` run> (default: '.js')
"""
from datetime import datetime
import os
import subprocess
import sys
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import yaml
from pathlib import Path
class MakeHandler(FileSystemEventHandler):
    last_time = None
    gap = 2
    def __init__(self,command,config):
        self.command = command
        self.config = config
        roots = self.config['path']
        self.roots = [Path(root) for root in roots]
        super(FileSystemEventHandler,self).__init__()
    def on_modified(self, event):
        if not event.is_directory:
            t = datetime.now()
            src_path = Path(event.src_path).resolve()
            if self.config['extensions'] is None or src_path.suffix in self.config['extensions']:
                for root in self.roots:
                    if src_path.is_relative_to(root.resolve()):
                        print('{} modified at {}'.format(root / src_path.relative_to(root.resolve()),t))
                if self.last_time is None or (t-self.last_time).seconds>self.gap:
                    self.run()
                self.last_time = t
    def run(self):
        subprocess.call(self.command)
class WatchMaker(object):
    def __init__(self,rootpath):
        self.rootpath = rootpath
        config = {
            'path': '.',
            'default_make': [],
            'extensions': ['.js'],
        }
        try:
            with open(rootpath / '.watchmakerc') as f:
                config.update(yaml.load(f.read(),Loader=yaml.SafeLoader))
        except IOError:
            pass
        self.config = config
    def run(self,targets=None):
        paths = self.config.get('path','.')
        if isinstance(paths,str):
            paths = [paths]
        paths = [self.rootpath / p for p in paths]
        paths = [p for p in paths if os.path.exists(p)]
        print("Watching {}".format(', '.join(str(p) for p in paths)))
        command = ['make']+(targets if targets else self.config['default_make'])
        event_handler = MakeHandler(command,self.config)
        event_handler.run()
        observer = Observer()
        for path in paths:
            observer.schedule(event_handler,str(path),recursive=True)
        observer.start()
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            observer.stop()
        observer.join()