tur-watch Guide

tur-watch provides cross-platform filesystem watching (inotify on Linux, kqueue on Darwin) with debounce and coalescing helpers for CLI tools that need a "file changed, react" loop.


Installation

:spices {
  "watch" {:url    "https://github.com/rjungemann/turmeric-spices"
           :ref    "watch-v0.2.0"
           :subdir "spices/watch"}
}

Modules

Module Contents
watch/event watch-event record, kind constants, accessors
watch/opts watch-opts record, default-watch-opts
watch/watch watch-open-one, watch-open-tree, watch-close, watch-next, watch-drain
watch/debounce debounce-batch-* primitives for path coalescing

watch/backend is internal and is not exported.


Quick start -- Single file

(import watch/event :refer [watch-event-kind watch-event-path watch-event-free
                             watch-kind-write watch-kind-rename])
(import watch/opts  :refer [default-watch-opts])
(import watch/watch :refer [watch-open-one watch-close watch-next])

(defn main [] : int
  (let [opts (default-watch-opts)
        w    (watch-open-one "notes.md" opts)]
    (loop []
      (let [ev (watch-next w -1)]   ;; block indefinitely
        (if (= ev 0)
          0
          (do
            (println (str-concat "changed: " (watch-event-path ev)))
            (watch-event-free ev)
            (recur)))))
    (watch-close w)
    0))

Quick start -- Recursive directory tree

(import watch/event :refer [watch-event-path watch-event-kind
                             watch-kind->cstr watch-event-free])
(import watch/opts  :refer [watch-opts-make watch-opts-free])
(import watch/watch :refer [watch-open-tree watch-close watch-next])

(defn main [] : int
  (let [opts (watch-opts-make 1 150 1 0 1)  ;; recursive, 150 ms debounce
        w    (watch-open-tree "src/" opts)]
    (watch-opts-free opts)
    (loop []
      (let [ev (watch-next w -1)]
        (if (= ev 0)
          0
          (do
            (println (str-concat (watch-kind->cstr (watch-event-kind ev))
                                 " "
                                 (watch-event-path ev)))
            (watch-event-free ev)
            (recur)))))
    (watch-close w)
    0))

API reference

Opening a watcher

;; Watch a single file or directory
(watch-open-one path opts)   ;; returns watcher handle, or 0 on failure

;; Watch a directory tree recursively
(watch-open-tree dir opts)   ;; returns watcher handle

;; Release the watcher
(watch-close w)

Options

;; Use the defaults (150 ms debounce, non-recursive)
(default-watch-opts)

;; Construct custom options
;; watch-opts-make recursive debounce-ms coalesce? reserved periodic?
(watch-opts-make 1 200 1 0 0)

(watch-opts-free opts)   ;; release options handle
Parameter Type Default Meaning
recursive int 0 1 to recurse into subdirectories (tree mode only)
debounce-ms int 150 Coalescing window in milliseconds
coalesce? int 1 1 to coalesce burst events into one
reserved int 0 Must be 0
periodic? int 0 Reserved for future periodic polling

Reading events

;; Block until the next event (timeout-ms = -1 blocks forever)
(watch-next w timeout-ms)    ;; returns event handle, or 0 on timeout

;; Drain all pending events without blocking
(watch-drain w)              ;; returns a cons list of event handles (may be 0)

Event accessors

(watch-event-kind  ev)        ;; event kind constant (see below)
(watch-event-path  ev)        ;; file path that changed
(watch-event-free  ev)        ;; release the event handle

Event kinds

(watch-kind-write)    ;; file content changed
(watch-kind-create)   ;; file created
(watch-kind-delete)   ;; file deleted
(watch-kind-rename)   ;; file renamed / moved
(watch-kind-none)     ;; no change (returned only on timeout/drain miss)

(watch-kind->cstr kind)  ;; human-readable string for a kind constant

Debounce helper

For tools that re-run a command on any change, watch/debounce coalesces a burst of events into one batched notification:

(import watch/debounce :refer [debounce-batch-new debounce-batch-add
                               debounce-batch-ready? debounce-batch-drain
                               debounce-batch-free])

(let [batch (debounce-batch-new 300)]  ;; 300 ms window
  (loop []
    (let [ev (watch-next w 50)]       ;; 50 ms poll step
      (if (not= ev 0)
        (do
          (debounce-batch-add batch ev)
          (recur))
        ;; No event -- check if debounce window elapsed
        (if (debounce-batch-ready? batch)
          (let [paths (debounce-batch-drain batch)]
            ;; paths is a cons list of changed cstr paths
            (rebuild! paths)
            (recur))
          (recur)))))
  (debounce-batch-free batch))

Notes


See also