1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
use std::{
	collections::HashMap,
	fmt,
	path::{Path, PathBuf},
};

use watchexec_signals::Signal;

#[cfg(feature = "serde")]
use crate::serde_formats::{SerdeEvent, SerdeTag};

use crate::{filekind::FileEventKind, FileType, Keyboard, ProcessEnd};

/// An event, as far as watchexec cares about.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "SerdeEvent", into = "SerdeEvent"))]
pub struct Event {
	/// Structured, classified information which can be used to filter or classify the event.
	pub tags: Vec<Tag>,

	/// Arbitrary other information, cannot be used for filtering.
	pub metadata: HashMap<String, Vec<String>>,
}

/// Something which can be used to filter or qualify an event.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "SerdeTag", into = "SerdeTag"))]
#[non_exhaustive]
pub enum Tag {
	/// The event is about a path or file in the filesystem.
	Path {
		/// Path to the file or directory.
		path: PathBuf,

		/// Optional file type, if known.
		file_type: Option<FileType>,
	},

	/// Kind of a filesystem event (create, remove, modify, etc).
	FileEventKind(FileEventKind),

	/// The general source of the event.
	Source(Source),

	/// The event is about a keyboard input.
	Keyboard(Keyboard),

	/// The event was caused by a particular process.
	Process(u32),

	/// The event is about a signal being delivered to the main process.
	Signal(Signal),

	/// The event is about a subprocess ending.
	ProcessCompletion(Option<ProcessEnd>),

	#[cfg(feature = "serde")]
	/// The event is unknown (or not yet implemented).
	Unknown,
}

impl Tag {
	/// The name of the variant.
	#[must_use]
	pub const fn discriminant_name(&self) -> &'static str {
		match self {
			Self::Path { .. } => "Path",
			Self::FileEventKind(_) => "FileEventKind",
			Self::Source(_) => "Source",
			Self::Keyboard(_) => "Keyboard",
			Self::Process(_) => "Process",
			Self::Signal(_) => "Signal",
			Self::ProcessCompletion(_) => "ProcessCompletion",
			#[cfg(feature = "serde")]
			Self::Unknown => "Unknown",
		}
	}
}

/// The general origin of the event.
///
/// This is set by the event source. Note that not all of these are currently used.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
#[non_exhaustive]
pub enum Source {
	/// Event comes from a file change.
	Filesystem,

	/// Event comes from a keyboard input.
	Keyboard,

	/// Event comes from a mouse click.
	Mouse,

	/// Event comes from the OS.
	Os,

	/// Event is time based.
	Time,

	/// Event is internal to Watchexec.
	Internal,
}

impl fmt::Display for Source {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(
			f,
			"{}",
			match self {
				Self::Filesystem => "filesystem",
				Self::Keyboard => "keyboard",
				Self::Mouse => "mouse",
				Self::Os => "os",
				Self::Time => "time",
				Self::Internal => "internal",
			}
		)
	}
}

/// The priority of the event in the queue.
///
/// In the event queue, events are inserted with a priority, such that more important events are
/// delivered ahead of others. This is especially important when there is a large amount of events
/// generated and relatively slow filtering, as events can become noticeably delayed, and may give
/// the impression of stalling.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum Priority {
	/// Low priority
	///
	/// Used for:
	/// - process completion events
	Low,

	/// Normal priority
	///
	/// Used for:
	/// - filesystem events
	Normal,

	/// High priority
	///
	/// Used for:
	/// - signals to main process, except Interrupt and Terminate
	High,

	/// Urgent events bypass filtering entirely.
	///
	/// Used for:
	/// - Interrupt and Terminate signals to main process
	Urgent,
}

impl Default for Priority {
	fn default() -> Self {
		Self::Normal
	}
}

impl Event {
	/// Returns true if the event has an Internal source tag.
	#[must_use]
	pub fn is_internal(&self) -> bool {
		self.tags
			.iter()
			.any(|tag| matches!(tag, Tag::Source(Source::Internal)))
	}

	/// Returns true if the event has no tags.
	#[must_use]
	pub fn is_empty(&self) -> bool {
		self.tags.is_empty()
	}

	/// Return all paths in the event's tags.
	pub fn paths(&self) -> impl Iterator<Item = (&Path, Option<&FileType>)> {
		self.tags.iter().filter_map(|p| match p {
			Tag::Path { path, file_type } => Some((path.as_path(), file_type.as_ref())),
			_ => None,
		})
	}

	/// Return all signals in the event's tags.
	pub fn signals(&self) -> impl Iterator<Item = Signal> + '_ {
		self.tags.iter().filter_map(|p| match p {
			Tag::Signal(s) => Some(*s),
			_ => None,
		})
	}

	/// Return all process completions in the event's tags.
	pub fn completions(&self) -> impl Iterator<Item = Option<ProcessEnd>> + '_ {
		self.tags.iter().filter_map(|p| match p {
			Tag::ProcessCompletion(s) => Some(*s),
			_ => None,
		})
	}
}

impl fmt::Display for Event {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "Event")?;
		for p in &self.tags {
			match p {
				Tag::Path { path, file_type } => {
					write!(f, " path={}", path.display())?;
					if let Some(ft) = file_type {
						write!(f, " filetype={ft}")?;
					}
				}
				Tag::FileEventKind(kind) => write!(f, " kind={kind:?}")?,
				Tag::Source(s) => write!(f, " source={s:?}")?,
				Tag::Keyboard(k) => write!(f, " keyboard={k:?}")?,
				Tag::Process(p) => write!(f, " process={p}")?,
				Tag::Signal(s) => write!(f, " signal={s:?}")?,
				Tag::ProcessCompletion(None) => write!(f, " command-completed")?,
				Tag::ProcessCompletion(Some(c)) => write!(f, " command-completed({c:?})")?,
				#[cfg(feature = "serde")]
				Tag::Unknown => write!(f, " unknown")?,
			}
		}

		if !self.metadata.is_empty() {
			write!(f, " meta: {:?}", self.metadata)?;
		}

		Ok(())
	}
}