Commits

Steve Losh  committed 6cdd3ed

Add --dynamic.

  • Participants
  • Parent commits 77798c1
  • Tags v0.3.0

Comments (0)

Files changed (2)

File README.markdown

 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.
 
 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
 
+    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.
 
-    COMMAND should be given as a single argument using a shell string.
-
     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'
+
+
     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)
 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):
     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,
     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)')
 
 
 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('')
 
     while True:
         time.sleep(interval)
-        if check(paths):
+        if check(get_paths()):
             if clear:
                 subprocess.check_call('clear')
             run()
         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()
 
     clear = options.clear
     verbose = options.verbose
     sep = options.sep
+    dynamic = options.dynamic
 
-    data = sys.stdin.read()
-    if not sep:
-        paths = data.split()
+    if dynamic:
+        paths_command = sys.stdin.read().rstrip()
+
+        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)
+
+        get_paths = _get_paths
     else:
-        paths = data.split(sep)
+        data = sys.stdin.read()
+        paths = _parse_paths(sep, data)
 
-    paths = [p.rstrip('\n') for p in paths if p]
-    paths = map(os.path.abspath, paths)
-    paths = set(paths)
+        if not paths:
+            die("no paths to watch were given on standard input")
 
-    if options.interval:
-        interval = int(options.interval)
-    else:
-        interval = smart_interval(len(paths))
-    interval = interval / 1000.0
+        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))
+        get_paths = lambda: paths
 
-    if not paths:
-        die("no paths to watch were given on standard input")
+    interval = _parse_interval(options)
 
     _main()