# HG changeset patch # User Steve Losh # Date 1353786242 18000 # Node ID 6cdd3ed3ef128c6c6a875e93f48b3fff684cfbc3 # Parent 77798c15666acea7b819135988366f7eb9f27253 Add --dynamic. diff -r 77798c15666a -r 6cdd3ed3ef12 README.markdown --- a/README.markdown Sat Nov 24 13:54:27 2012 -0500 +++ b/README.markdown Sat Nov 24 14:44:02 2012 -0500 @@ -27,17 +27,17 @@ Generate a list of files you want to watch for changes, separated by whitespace. echo(1), find(1) or [friendly-find][] are good for this: - ffind '.*.py$' + $ ffind '.*.py$' ./foo.py ./bar.py - echo *.py + $ echo *.py foo.py bar.py Now pipe that to `peat`, and specify the command you want to run whenever one of those files changes: - ffind '.*.py$' | peat 'echo "A file changed!"' + $ ffind '.*.py$' | peat 'echo "A file changed!"' Use `Ctrl-C` to stop. @@ -45,19 +45,48 @@ can do this with a shell string as seen above. Using a single-quoted string like this will preserve wildcards and such: - ffind '.*.py$' | peat 'rm *.pyc' + $ ffind '.*.py$' | peat 'rm *.pyc' This will delete all `.pyc` files in the current directory when a Python file is modified. Google around for "shell quoting" if you don't understand what's happening here. +### Dynamic File Listing + +If you want to build the file list fresh each time (so that `peat` will pick up +newly created files without having to restart it) you can use the `--dynamic` +option. + +Instead of piping in the list of files to watch, you'll pipe in a *command* that +`peat` will run to generate the list before every check. For example: + + $ ffind ".markdown$" + ./foo.markdown + ./bar/baz.markdown + + $ echo 'ffind ".markdown$"' + ffind ".markdown$" + + $ echo 'ffind ".markdown$"' | peat --dynamic 'echo "A file changed!"' + +If your command contains quotes you'll need to make sure they get passed +into peat properly. For example, the following will **not** work: + + $ echo "find . -name '*.markdown'" | peat --dynamic ... + +The problem is that the shell will expand the `*` in the double-quoted string +before it ever gets to `peat`. Google around and learn about shell quoting if +you don't understand. This can be tricky. You've been warned. + +### Full Usage + Here's the full usage: Usage: peat [options] COMMAND - A list of paths to watch should be piped in on standard input. + COMMAND should be given as a single argument using a shell string. - COMMAND should be given as a single argument using a shell string. + A list of paths to watch should be piped in on standard input. For example: @@ -65,11 +94,27 @@ find . -name '*.py' | peat 'rm *.pyc' find . -name '*.py' -print0 | peat -0 'rm *.pyc' + If --dynamic is given, a command to generate the list should be piped in + on standard input instead. It will be used to generate the list of files + to check before each run. + + This command must be quoted properly, and this can be tricky. Make sure + you know what you're doing. + + For example: + + echo find . | peat --dynamic './test.sh' + echo find . -name '*.py' | peat --dynamic 'rm *.pyc' + + Options: -h, --help show this help message and exit -i N, --interval=N interval between checks in milliseconds -I, --smart-interval determine the interval based on number of files watched (default) + -d, --dynamic take a command on standard input to generate the list + of files to watch + -D, --no-dynamic take a list of files to watch on standard in (default) -c, --clear clear screen before runs (default) -C, --no-clear don't clear screen before runs -v, --verbose show extra logging output (default) diff -r 77798c15666a -r 6cdd3ed3ef12 peat --- a/peat Sat Nov 24 13:54:27 2012 -0500 +++ b/peat Sat Nov 24 14:44:02 2012 -0500 @@ -21,8 +21,36 @@ interval = 1.0 command = 'true' clear = True -paths = set() +get_paths = lambda: set() verbose = True +dynamic = False +paths_command = None + +USAGE = """\ +usage: %prog [options] COMMAND + +COMMAND should be given as a single argument using a shell string. + +A list of paths to watch should be piped in on standard input. + +For example: + + find . | peat './test.sh' + find . -name '*.py' | peat 'rm *.pyc' + find . -name '*.py' -print0 | peat -0 'rm *.pyc' + +If --dynamic is given, a command to generate the list should be piped in +on standard input instead. It will be used to generate the list of files +to check before each run. + +This command must be quoted properly, and this can be tricky. Make sure +you know what you're doing. + +For example: + + echo find . | peat --dynamic './test.sh' + echo find . -name '*.py' | peat --dynamic 'rm *.pyc' +""" def log(s): @@ -53,14 +81,7 @@ subprocess.call(command, shell=True) def build_option_parser(): - p = OptionParser("usage: %prog [options] COMMAND\n\n" - "A list of paths to watch should be piped in on standard input.\n\n" - "COMMAND should be given as a single argument using a " - "shell string.\n\nFor example:\n\n" - " find . | peat './test.sh'\n" - " find . -name '*.py' | peat 'rm *.pyc'\n" - " find . -name '*.py' -print0 | peat -0 'rm *.pyc'" - ) + p = OptionParser(USAGE) # Main options p.add_option('-i', '--interval', default=None, @@ -69,6 +90,12 @@ p.add_option('-I', '--smart-interval', dest='interval', action='store_const', const=None, help='determine the interval based on number of files watched (default)') + p.add_option('-d', '--dynamic', default=False, + action='store_true', + help='take a command on standard input to generate the list of files to watch') + p.add_option('-D', '--no-dynamic', dest='dynamic', + action='store_false', + help='take a list of files to watch on standard in (default)') p.add_option('-c', '--clear', default=True, action='store_true', dest='clear', help='clear screen before runs (default)') @@ -98,9 +125,14 @@ def _main(): + if dynamic: + log("Running the following command to generate watch list:") + log(' ' + paths_command) + log('') + log("Watching the following paths:") - for p in paths: - log(" " + p) + for p in get_paths(): + log(' ' + p) log('') log('Checking for changes every %d milliseconds.' % int(interval * 1000)) log('') @@ -109,7 +141,7 @@ while True: time.sleep(interval) - if check(paths): + if check(get_paths()): if clear: subprocess.check_call('clear') run() @@ -122,8 +154,31 @@ sq = lambda n: n * n return int(1000 * (1 - (sq(50.0 - count) / sq(50)))) +def _parse_interval(options): + global get_paths + if options.interval: + i = int(options.interval) + elif options.dynamic: + i = 1000 + else: + i = smart_interval(len(get_paths())) + + return i / 1000.0 + +def _parse_paths(sep, data): + if not sep: + paths = data.split() + else: + paths = data.split(sep) + + paths = [p.rstrip('\n') for p in paths if p] + paths = map(os.path.abspath, paths) + paths = set(paths) + + return paths + def main(): - global interval, command, clear, paths, verbose + global interval, command, clear, get_paths, verbose, dynamic, paths_command (options, args) = build_option_parser().parse_args() @@ -134,29 +189,33 @@ clear = options.clear verbose = options.verbose sep = options.sep + dynamic = options.dynamic - data = sys.stdin.read() - if not sep: - paths = data.split() - else: - paths = data.split(sep) + if dynamic: + paths_command = sys.stdin.read().rstrip() - paths = [p.rstrip('\n') for p in paths if p] - paths = map(os.path.abspath, paths) - paths = set(paths) + if not paths_command: + die("no command to generate watch list was given on standard input") + + def _get_paths(): + data = subprocess.check_output(paths_command, shell=True) + return _parse_paths(sep, data) - if options.interval: - interval = int(options.interval) + get_paths = _get_paths else: - interval = smart_interval(len(paths)) - interval = interval / 1000.0 + data = sys.stdin.read() + paths = _parse_paths(sep, data) + + if not paths: + die("no paths to watch were given on standard input") - for path in paths: - if not os.path.exists(path): - die('path to watch does not exist: ' + repr(path)) + for path in paths: + if not os.path.exists(path): + die('path to watch does not exist: ' + repr(path)) - if not paths: - die("no paths to watch were given on standard input") + get_paths = lambda: paths + + interval = _parse_interval(options) _main()