Coverage for src / rendercv / cli / render_command / watcher.py: 100%
31 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
1import contextlib
2import pathlib
3from collections.abc import Callable
5import typer
6import watchdog.events
7import watchdog.observers
10class EventHandler(watchdog.events.FileSystemEventHandler):
11 """Trigger a callback when a watched file is modified.
13 Args:
14 function: Callback to invoke on modification.
15 watched_files: Absolute path strings of files to monitor.
16 """
18 def __init__(self, function: Callable[[], None], watched_files: set[str]) -> None:
19 super().__init__()
20 self.function = function
21 self.watched_files = watched_files
23 def on_modified(
24 self,
25 event: watchdog.events.DirModifiedEvent | watchdog.events.FileModifiedEvent,
26 ) -> None:
27 if event.src_path not in self.watched_files:
28 return
29 with contextlib.suppress(typer.Exit):
30 self.function()
33def run_function_if_files_change(
34 file_paths: list[pathlib.Path],
35 function: Callable[[], None],
36) -> None:
37 """Watch files and re-run function when any is modified.
39 Why:
40 Watch mode lets users edit CV YAML and see results instantly.
41 All config files (main input, design, locale, settings) must be
42 monitored so edits to any trigger a re-render.
44 Args:
45 file_paths: File paths to watch.
46 function: Zero-argument callback to invoke on file change.
47 """
48 watched_files = {str(fp.absolute()) for fp in file_paths}
50 # Watch parent directories (file-level watching is unreliable across platforms)
51 dirs_to_schedule = {str(fp.absolute().parent) for fp in file_paths}
53 event_handler = EventHandler(function, watched_files)
55 observer = watchdog.observers.Observer()
56 for directory in dirs_to_schedule:
57 observer.schedule(event_handler, directory, recursive=False)
58 observer.start()
60 # Run immediately for the first render:
61 with contextlib.suppress(typer.Exit):
62 function()
64 try:
65 observer.join()
66 except KeyboardInterrupt:
67 observer.stop()
68 observer.join()