Hawk Clojure Library

repository·master·Indexed 41 days ago

https://github.com/wkf/hawk

A Clojure library for watching files and directories that wraps the JDK 7 java.nio.file.WatchService and provides an optimized macOS implementation via Barbary WatchService. It supports stateful watches, event filtering, and polling for virtual environments.

Tokens
1.4K
Snippets
6
Records
6
Agent score
2%

What's inside Hawk

  1. How stateful watches work in Hawk

    master

    Hawk allows you to maintain state across events using the :context key.

    1. Initialization: Provide a :context function. This function is called once when the watch starts. It receives the current context (initially nil) and should return the initial state value.
    2. State Updates: The :handler function receives the current state as its first argument. To update the state, the :handler must return the new state value.

    Watch Groups and Concurrency:

    • hawk/watch! accepts multiple arrays of watch specs.
    • Within an array: All specs share the same state, and events are processed sequentially.
    • Between arrays: Each array (group) gets its own independent state, and groups are processed in parallel.
    ;; Example of stateful watch: incrementing a counter on every event
    (hawk/watch! [{:paths ["src/main/hawk"]
                   :context (constantly 1)
                   :handler (fn [ctx _] (inc ctx))}])
    
    ;; Example of multiple groups with independent states
    (hawk/watch!
        ;; Group 1: Shared state, sequential processing
        [{:paths ["src/main/hawk"]
          :context (constantly 1)
          :handler (fn [ctx _]
                    (println "I'm always first!")
                    (inc ctx))}
         {:paths ["src/test/hawk"]
          :context (fn [ctx] (inc ctx))
          :handler (fn [_ _]
                    (println "I'm always second place."))}]
        ;; Group 2: Separate state, parallel to Group 1
        [{:paths ["src/main/hawk"]
          :context (constantly 3)
          :handler (fn [_ _]
                    (println "I'm also always first!"))}])
  2. Filter watched events

    master

    You can use the :filter key in a watch specification to ignore certain events. The :filter function receives the current context and the event map. It should return a truthy value to allow the event through or a falsy value to drop it.

    Hawk provides a built-in filter hawk/file? which only allows file events (ignoring directory events). You can also provide any custom predicate function.

    (hawk/watch! [{:paths ["src/main/hawk"]
                   :filter hawk/file?
                   :handler (fn [_ _]
                              (println "look ma, just files!"))}])
  3. Use polling watches for virtual environments

    master

    In environments where the OS does not receive native file system events (like some virtual environments), you can use a polling watcher by setting :watcher :polling in the options passed to hawk/watch!.

    You can control the frequency of polling using the :sensitivity key. Supported values are:

    • :high (default)
    • :medium
    • :low
    (hawk/watch! {:watcher :polling}
                 [{:paths ["src/main/hawk"]
                   :handler (fn [ctx e]
                              (println "event: " e)
                              (println "context: " ctx)
                              ctx)}])
  4. Start a simple file watch with `hawk/watch!`

    master

    To start watching files or directories, require hawk.core and call hawk/watch!. You pass a vector of watch specifications. Each specification requires :paths (a vector of strings) and a :handler function.

    The :handler function receives two arguments: ctx (the current context) and e (the event). The handler must return the new context value.

    The event map e contains:

    • :kind: The event type, one of #{:create :modify :delete}.
    • :file: The canonicalized java.io.File object affected by the event.
    (ns hawk.sample
        (:require [hawk.core :as hawk]))
    
    (hawk/watch! [{:paths ["src/main/hawk"]
                   :handler (fn [ctx e]
                              (println "event: " e)
                              (println "context: " ctx)
                              ctx)}])