Created
November 27, 2017 18:15
-
-
Save mateor/8dd618acbc88ad376760f76fa72086c2 to your computer and use it in GitHub Desktop.
Pants onchange
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding=utf-8 | |
# Copyright 2013 Foursquare Labs Inc. All Rights Reserved. | |
from __future__ import (nested_scopes, generators, division, absolute_import, with_statement, | |
print_function, unicode_literals) | |
import argparse | |
from threading import Event | |
import subprocess | |
import sys | |
import time | |
DEFAULT_BUILD_GENERATED=['.pyc', '.class', '.obj', '.o'] | |
class Watcher(object): | |
def __init__(self, run, roots=None, useful_endings=None, ignored_endings=None): | |
if useful_endings: | |
useful_endings = useful_endings.split(',') | |
if ignored_endings: | |
ignored_endings = ignored_endings.split(',') | |
if roots: | |
roots = roots.split(',') | |
else: | |
roots = ['.'] | |
self.useful_endings = useful_endings | |
self.ignored_endings = ignored_endings | |
self.roots = [str(i) for i in roots] | |
self._dirty = Event() | |
self.run = run | |
def is_ignored(self, name): | |
if not self.ignored_endings: | |
return False | |
return any(name.endswith(i) for i in self.ignored_endings) | |
def is_useful(self, name): | |
if not self.useful_endings: | |
return True | |
return any(name.endswith(i) for i in self.useful_endings) | |
def callback(self, changed_name): | |
if not self._dirty.isSet(): | |
if not self.is_ignored(changed_name): | |
if self.is_useful(changed_name): | |
self._dirty.set() | |
def start_fsevents(self): | |
from fsevents import Observer, Stream | |
observer = Observer() | |
observer.daemon = True | |
observer.start() | |
stream = Stream(lambda x: self.callback(x.name), *self.roots, file_events=True) | |
observer.schedule(stream) | |
started = True | |
return self.loop() | |
def start_inotify(self): | |
import pyinotify | |
class OnWriteHandler(pyinotify.ProcessEvent): | |
def __init__(self, watcher): | |
self.watcher = watcher | |
def process_IN_CREATE(self, event): | |
self.watcher.callback(event.pathname) | |
def process_IN_DELETE(self, event): | |
self.watcher.callback(event.pathname) | |
def process_IN_MODIFY(self, event): | |
self.watcher.callback(event.pathname) | |
wm = pyinotify.WatchManager() | |
handler = OnWriteHandler(self) | |
notifier = pyinotify.ThreadedNotifier(wm, default_proc_fun=handler) | |
for path in self.roots: | |
wm.add_watch(path, pyinotify.ALL_EVENTS, rec=True, auto_add=True) | |
notifier.setDaemon(True) | |
notifier.start() | |
return self.loop() | |
def loop(self): | |
self.msg() | |
self._dirty.set() | |
while True: | |
if self._dirty.isSet(): | |
self._dirty.clear() | |
print('[{}] Running "{}"...'.format(time.strftime('%H:%M:%S'), self.run)) | |
subprocess.call(self.run, shell=True) | |
if not self._dirty.isSet(): | |
print('[{}] Waiting for changes...'.format(time.strftime('%H:%M:%S'))) | |
try: | |
self._dirty.wait(60) | |
except KeyboardInterrupt: | |
print('') | |
sys.exit(0) | |
@staticmethod | |
def comma_and(lst): | |
if not lst: | |
return '' | |
if len(lst) == 1: | |
return '"{}"'.format(lst[0]) | |
return '"{}", and "{}"'.format('", "'.join(lst[:-1]), lst[-1]) | |
def msg(self): | |
msg = '\nWatching for changes in {}'.format(self.comma_and(self.roots)) | |
if self.useful_endings: | |
msg += ', to files ending in: {}'.format(self.comma_and(self.useful_endings)) | |
else: | |
msg += '.' | |
print(msg) | |
if self.ignored_endings: | |
print('Ingoring changes to files ending in: {}'.format(self.comma_and(self.ignored_endings))) | |
print('') | |
def start(self): | |
try: | |
from fsevents import Observer, Stream | |
return self.start_fsevents() | |
except ImportError: | |
pass | |
try: | |
import pyinotify | |
return self.start_inotify() | |
except ImportError: | |
pass | |
print('\n./onchange requires MacFSEvents on OSX or pyinotify on Linux.\n') | |
sys.exit(1) | |
if __name__ == '__main__': | |
ignored = DEFAULT_BUILD_GENERATED + ['src/resources/config/release.conf'] | |
parser = argparse.ArgumentParser() | |
parser.add_argument('--roots', default=','.join(['src', 'test'])) | |
parser.add_argument('--endings', default=','.join(['.scala', '.java', '.thrift', '.soy', '.py', 'BUILD'])) | |
parser.add_argument('--ignored', default=','.join(ignored)) | |
parser.add_argument('command', metavar="COMMAND", nargs=argparse.REMAINDER) | |
options = parser.parse_args() | |
Watcher(' '.join(options.command), options.roots, options.endings, options.ignored).start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment