Video course contents16
LESSON 13 · HHY 1.7.0
Watch Files and Schedule Work
Trigger a local build from one file event and stop a timer after three ticks.
Your progress stays in this browser. No account needed.
Read the companion manual chapter →
Chapters & transcript
Select a timestamp to jump in the video. Expand a title to read the transcript and code.
Automation needs an ending
A watcher reacts to changes, while a timer schedules work at intervals. Both are useful for automation, and both naturally keep running. In this lesson we will make their lifetime explicit. First, a timer prints exactly three ticks. Then a file event triggers one small local build and the watcher exits. These bounded demonstrations are easier to test and understand than an unattended background process, and they give us a clear result to verify at the end.
tick
tick
tick
doneActual output from ticks.hhy.
Bound a timer before consuming it
Every creates a stream of timer ticks. Here the interval is one hundred milliseconds, which keeps the demonstration quick. Take limits the stream to three values before for each consumes it. Each tick prints the same short label, and the final line proves that execution continued after the stream ended. The timer interval is not a precise real time scheduling guarantee. If downstream work takes time, the pipeline follows backpressure rather than promising overlapping executions for every clock interval.
every(100ms) |> take(3)
|> for_each { tick -> print("tick") }
print("done")Run hhy run ticks.hhy.
See an unbounded barrier fail
Now run the unbounded example. Collect needs the entire input before it can return a list, but an unlimited timer never supplies a final item. HHY rejects this with a plan error telling us to apply a bound. The fix is about the lifetime of the data, not about adding more memory or waiting longer. Put take before a collecting, sorting, or grouping barrier whenever your source is naturally infinite and your task requires a finite result.
every(100ms) |> collect |> printunbounded.hhy intentionally produces PlanError.
Create the file watcher
Our watcher observes the fixtures directory recursively. Debounce reduces rapid repeated events, and take limits this demonstration to one accepted event. The documentation describes leading edge behavior: the first event is emitted immediately, and repeated events with the same key are coalesced during the window. It is not a promise to wait for every editor to finish writing. For this lesson, we create a separate trigger file only after our source content is ready, making the build input deliberate and reproducible.
watch(path("fixtures"), { recursive: true })
|> debounce(300ms)
|> take(1)Watch-build waits for one accepted event.
Run a bounded build command
Inside the event handler, run invokes the supplied Python build script with a two second timeout. The build reads source dot text and prints its contents with a built label. This is deliberately small so the lesson stays focused on orchestration. A nonzero process exit code needs an explicit decision: here we print standard error, while success prints standard output. You can later substitute your real build command without changing the basic event, process, and result handling structure.
let result = run(["python3", "build.py"], { timeout: 2s })
if result.exit_code != 0 { print_error(result.stderr) }
else { print(result.stdout) }This block runs inside the event handler.
Trigger once, then stop
Start the watcher from the examples directory, then create the trigger file in another terminal. You should see change detected, the build output, and watch complete. The included verification driver performs this sequence and cleans up its trigger file. Filesystem event delivery varies by platform and editor. In our local validation, creating a file triggered reliably, while rewriting an existing file did not. That is why this demonstration uses a creation event and does not promise identical save behavior on every system.
hhy run watch-build.hhy
# In another terminal, create fixtures/trigger.txtUse verify.py for a fully bounded automated demonstration.
Choose event and lifecycle policies
For a longer running tool, decide which event kinds should trigger work and which files should be ignored. Writing generated output into the watched source directory can cause an unwanted loop. Keep the outputs elsewhere and filter according to your actual build inputs. During this tutorial, take gives us a normal finish. If no event arrives, Control C stops the watcher; the verification driver also has an outer timeout so an unexpected platform issue cannot leave the test waiting indefinitely.
created · modified · removed · renamed
Normal finish: take(1)
Manual stop: Control CKeep generated outputs outside the watched source directory.
Practice a controlled workflow
For practice, update source dot text, start a fresh watcher, and create the trigger file again. Predict the build output before you run the sequence. Then change the timer example to five ticks and verify that done still appears exactly once. Keep these two sources separate until their lifetimes feel familiar. You can now schedule finite work and respond to a filesystem signal with an explicit stopping condition. In the next lesson, we will combine several earlier techniques into a real log report.
Source ready → create trigger → build → exitExercise: change source.txt, then trigger another fresh run.
