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, /// Arbitrary other information, cannot be used for filtering. pub metadata: HashMap>, } /// 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, }, /// 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), #[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)> { 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 + '_ { 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> + '_ { 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(()) } }