watchexec_events/
event.rs

1use std::{
2	collections::HashMap,
3	fmt,
4	path::{Path, PathBuf},
5};
6
7use watchexec_signals::Signal;
8
9#[cfg(feature = "serde")]
10use crate::serde_formats::{SerdeEvent, SerdeTag};
11
12use crate::{filekind::FileEventKind, FileType, Keyboard, ProcessEnd};
13
14/// An event, as far as watchexec cares about.
15#[derive(Clone, Debug, Default, Eq, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "serde", serde(from = "SerdeEvent", into = "SerdeEvent"))]
18pub struct Event {
19	/// Structured, classified information which can be used to filter or classify the event.
20	pub tags: Vec<Tag>,
21
22	/// Arbitrary other information, cannot be used for filtering.
23	pub metadata: HashMap<String, Vec<String>>,
24}
25
26/// Something which can be used to filter or qualify an event.
27#[derive(Clone, Debug, Eq, PartialEq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(feature = "serde", serde(from = "SerdeTag", into = "SerdeTag"))]
30#[non_exhaustive]
31pub enum Tag {
32	/// The event is about a path or file in the filesystem.
33	Path {
34		/// Path to the file or directory.
35		path: PathBuf,
36
37		/// Optional file type, if known.
38		file_type: Option<FileType>,
39	},
40
41	/// Kind of a filesystem event (create, remove, modify, etc).
42	FileEventKind(FileEventKind),
43
44	/// The general source of the event.
45	Source(Source),
46
47	/// The event is about a keyboard input.
48	Keyboard(Keyboard),
49
50	/// The event was caused by a particular process.
51	Process(u32),
52
53	/// The event is about a signal being delivered to the main process.
54	Signal(Signal),
55
56	/// The event is about a subprocess ending.
57	ProcessCompletion(Option<ProcessEnd>),
58
59	#[cfg(feature = "serde")]
60	/// The event is unknown (or not yet implemented).
61	Unknown,
62}
63
64impl Tag {
65	/// The name of the variant.
66	#[must_use]
67	pub const fn discriminant_name(&self) -> &'static str {
68		match self {
69			Self::Path { .. } => "Path",
70			Self::FileEventKind(_) => "FileEventKind",
71			Self::Source(_) => "Source",
72			Self::Keyboard(_) => "Keyboard",
73			Self::Process(_) => "Process",
74			Self::Signal(_) => "Signal",
75			Self::ProcessCompletion(_) => "ProcessCompletion",
76			#[cfg(feature = "serde")]
77			Self::Unknown => "Unknown",
78		}
79	}
80}
81
82/// The general origin of the event.
83///
84/// This is set by the event source. Note that not all of these are currently used.
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
88#[non_exhaustive]
89pub enum Source {
90	/// Event comes from a file change.
91	Filesystem,
92
93	/// Event comes from a keyboard input.
94	Keyboard,
95
96	/// Event comes from a mouse click.
97	Mouse,
98
99	/// Event comes from the OS.
100	Os,
101
102	/// Event is time based.
103	Time,
104
105	/// Event is internal to Watchexec.
106	Internal,
107}
108
109impl fmt::Display for Source {
110	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111		write!(
112			f,
113			"{}",
114			match self {
115				Self::Filesystem => "filesystem",
116				Self::Keyboard => "keyboard",
117				Self::Mouse => "mouse",
118				Self::Os => "os",
119				Self::Time => "time",
120				Self::Internal => "internal",
121			}
122		)
123	}
124}
125
126/// The priority of the event in the queue.
127///
128/// In the event queue, events are inserted with a priority, such that more important events are
129/// delivered ahead of others. This is especially important when there is a large amount of events
130/// generated and relatively slow filtering, as events can become noticeably delayed, and may give
131/// the impression of stalling.
132#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
135pub enum Priority {
136	/// Low priority
137	///
138	/// Used for:
139	/// - process completion events
140	Low,
141
142	/// Normal priority
143	///
144	/// Used for:
145	/// - filesystem events
146	Normal,
147
148	/// High priority
149	///
150	/// Used for:
151	/// - signals to main process, except Interrupt and Terminate
152	High,
153
154	/// Urgent events bypass filtering entirely.
155	///
156	/// Used for:
157	/// - Interrupt and Terminate signals to main process
158	Urgent,
159}
160
161impl Default for Priority {
162	fn default() -> Self {
163		Self::Normal
164	}
165}
166
167impl Event {
168	/// Returns true if the event has an Internal source tag.
169	#[must_use]
170	pub fn is_internal(&self) -> bool {
171		self.tags
172			.iter()
173			.any(|tag| matches!(tag, Tag::Source(Source::Internal)))
174	}
175
176	/// Returns true if the event has no tags.
177	#[must_use]
178	pub fn is_empty(&self) -> bool {
179		self.tags.is_empty()
180	}
181
182	/// Return all paths in the event's tags.
183	pub fn paths(&self) -> impl Iterator<Item = (&Path, Option<&FileType>)> {
184		self.tags.iter().filter_map(|p| match p {
185			Tag::Path { path, file_type } => Some((path.as_path(), file_type.as_ref())),
186			_ => None,
187		})
188	}
189
190	/// Return all signals in the event's tags.
191	pub fn signals(&self) -> impl Iterator<Item = Signal> + '_ {
192		self.tags.iter().filter_map(|p| match p {
193			Tag::Signal(s) => Some(*s),
194			_ => None,
195		})
196	}
197
198	/// Return all process completions in the event's tags.
199	pub fn completions(&self) -> impl Iterator<Item = Option<ProcessEnd>> + '_ {
200		self.tags.iter().filter_map(|p| match p {
201			Tag::ProcessCompletion(s) => Some(*s),
202			_ => None,
203		})
204	}
205}
206
207impl fmt::Display for Event {
208	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209		write!(f, "Event")?;
210		for p in &self.tags {
211			match p {
212				Tag::Path { path, file_type } => {
213					write!(f, " path={}", path.display())?;
214					if let Some(ft) = file_type {
215						write!(f, " filetype={ft}")?;
216					}
217				}
218				Tag::FileEventKind(kind) => write!(f, " kind={kind:?}")?,
219				Tag::Source(s) => write!(f, " source={s:?}")?,
220				Tag::Keyboard(k) => write!(f, " keyboard={k:?}")?,
221				Tag::Process(p) => write!(f, " process={p}")?,
222				Tag::Signal(s) => write!(f, " signal={s:?}")?,
223				Tag::ProcessCompletion(None) => write!(f, " command-completed")?,
224				Tag::ProcessCompletion(Some(c)) => write!(f, " command-completed({c:?})")?,
225				#[cfg(feature = "serde")]
226				Tag::Unknown => write!(f, " unknown")?,
227			}
228		}
229
230		if !self.metadata.is_empty() {
231			write!(f, " meta: {:?}", self.metadata)?;
232		}
233
234		Ok(())
235	}
236}