mirror of
https://github.com/watchexec/watchexec.git
synced 2024-11-16 17:18:30 +01:00
236 lines
6.1 KiB
Rust
236 lines
6.1 KiB
Rust
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 the 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(())
|
|
}
|
|
}
|