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#[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 pub tags: Vec<Tag>,
21
22 pub metadata: HashMap<String, Vec<String>>,
24}
25
26#[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 Path {
34 path: PathBuf,
36
37 file_type: Option<FileType>,
39 },
40
41 FileEventKind(FileEventKind),
43
44 Source(Source),
46
47 Keyboard(Keyboard),
49
50 Process(u32),
52
53 Signal(Signal),
55
56 ProcessCompletion(Option<ProcessEnd>),
58
59 #[cfg(feature = "serde")]
60 Unknown,
62}
63
64impl Tag {
65 #[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#[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 Filesystem,
92
93 Keyboard,
95
96 Mouse,
98
99 Os,
101
102 Time,
104
105 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#[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,
141
142 Normal,
147
148 High,
153
154 Urgent,
159}
160
161impl Default for Priority {
162 fn default() -> Self {
163 Self::Normal
164 }
165}
166
167impl Event {
168 #[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 #[must_use]
178 pub fn is_empty(&self) -> bool {
179 self.tags.is_empty()
180 }
181
182 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 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 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}