--- 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)
--- 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()