Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make crates/triggers API suitable for usysconf #240

Open
wants to merge 4 commits into
base: fnmatch-simplify
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/dag/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ where
Self::default()
}

pub fn with_capacity(nodes: usize, edges: usize) -> Self {
Self(DiGraph::with_capacity(nodes, edges))
}

/// Adds node N to the graph and returns the index.
/// If N already exists, it'll return the index of that node.
pub fn add_node_or_get_index(&mut self, node: N) -> NodeIndex {
Expand Down
32 changes: 31 additions & 1 deletion crates/fnmatch/src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0

use std::fmt;
use std::{collections::HashMap, convert, path::MAIN_SEPARATOR, str::FromStr};

use serde::de;
Expand All @@ -18,7 +19,6 @@ use crate::token::{tokens, Matcher, Token};
/// supported matchers.
///
/// The matchers and the the characters used in the group syntax can be escaped with a backslash ("\\").
///
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Pattern {
tokens: Vec<Token>,
Expand All @@ -42,6 +42,16 @@ impl<'de> de::Deserialize<'de> for Pattern {
}
}

impl fmt::Display for Pattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut string = String::new();
for token in &self.tokens {
string.push_str(&token.to_string());
}
write!(f, "{string}")
}
}

impl Pattern {
/// Creates a new Pattern. Equivalent to `s.parse::<Pattern>()`.
pub fn new(s: impl AsRef<str>) -> Self {
Expand Down Expand Up @@ -79,6 +89,26 @@ impl Pattern {
matc.path = path.as_ref().to_string();
Some(matc)
}

/// Returns a String representation of this Pattern suitable for the [`glob`] crate.
pub fn to_std_glob(&self) -> String {
let mut glob_str = String::new();
for tok in &self.tokens {
match tok {
Token::Text(txt) => {
glob_str.push_str(txt);
}
Token::Glob { name: _, matcher } => {
let wildcard = match matcher {
Matcher::One => "?",
Matcher::Any => "*",
};
glob_str.push_str(wildcard);
}
}
}
glob_str
}
}

/// Path match for a [Pattern].
Expand Down
24 changes: 24 additions & 0 deletions crates/fnmatch/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ pub enum Token {
Glob { name: Option<String>, matcher: Matcher },
}

impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Token::Text(txt) => write!(f, "{txt}"),
Token::Glob { name, matcher } => {
if let Some(name) = name {
write!(f, "({name}:{matcher})")
} else {
write!(f, "{matcher}")
}
}
}
}
}

/// Types of globs.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Matcher {
Expand All @@ -36,6 +51,15 @@ impl From<&RawToken> for Matcher {
}
}

impl fmt::Display for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Matcher::One => write!(f, "?"),
Matcher::Any => write!(f, "*"),
}
}
}

/// Parses a globbed pattern string into its components.
pub fn tokens(pattern: impl AsRef<str>) -> Vec<Token> {
let mut tokens = Vec::new();
Expand Down
144 changes: 40 additions & 104 deletions crates/triggers/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,116 +3,52 @@
// SPDX-License-Identifier: MPL-2.0

use std::collections::BTreeMap;
use std::path::PathBuf;

use fnmatch::Pattern;
use serde::Deserialize;
use serde::{Deserialize, Deserializer};

/// Filter matched paths to a specific kind
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PathKind {
Directory,
Symlink,
}

/// Execution handlers for a trigger
#[derive(Debug, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged)]
pub enum Handler {
Run { run: String, args: Vec<String> },
Delete { delete: Vec<String> },
}
use crate::{FileKind, Inhibitor, OsEnv, Pattern};

#[derive(Debug, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CompiledHandler(Handler);

impl CompiledHandler {
pub fn handler(&self) -> &Handler {
&self.0
/// Deserializes the "inhibitors" field of a [`Trigger`].
pub fn deserialize_inhibitors<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<Inhibitor>, D::Error> {
#[derive(Default, Deserialize)]
struct Inhibitors {
pub paths: Vec<PathBuf>,
pub environment: Vec<OsEnv>,
}
}

impl Handler {
/// Substitute all paths using matched variables
pub fn compiled(&self, with_match: &fnmatch::Match) -> CompiledHandler {
match self {
Handler::Run { run, args } => {
let mut run = run.clone();
for (key, value) in &with_match.groups {
run = run.replace(&format!("$({key})"), value);
}
let args = args
.iter()
.map(|a| {
let mut a = a.clone();
for (key, value) in &with_match.groups {
a = a.replace(&format!("$({key})"), value);
}
a
})
.collect();
CompiledHandler(Handler::Run { run, args })
}
Handler::Delete { delete } => CompiledHandler(Handler::Delete { delete: delete.clone() }),
}
let de = Inhibitors::deserialize(deserializer)?;
let mut inhibitors = vec![];
for path in de.paths {
inhibitors.push(Inhibitor::Path(path));
}
for env in de.environment {
inhibitors.push(Inhibitor::Environment(env));
}
Ok(inhibitors)
}

/// Deserializes the "paths" field of a [`Trigger`].
pub fn deserialize_patterns<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<BTreeMap<Pattern, Vec<String>>, D::Error> {
#[derive(Deserialize)]
struct PathDefinition {
pub handlers: Vec<String>,
#[serde(rename = "type")]
pub kind: Option<FileKind>,
}
}

/// Inhibitors prevent handlers from running based on some constraints
#[derive(Debug, Deserialize)]
pub struct Inhibitors {
pub paths: Vec<String>,
pub environment: Vec<String>,
}

/// Map handlers to a path pattern and kind filter
#[derive(Debug, Deserialize)]
pub struct PathDefinition {
pub handlers: Vec<String>,
#[serde(rename = "type")]
pub kind: Option<PathKind>,
}

/// Serialization format of triggers
#[derive(Debug, Deserialize)]
pub struct Trigger {
/// Unique (global scope) identifier
pub name: String,

/// User friendly description
pub description: String,

/// Run before this trigger name
pub before: Option<String>,

/// Run after this trigger name
pub after: Option<String>,

/// Optional inhibitors
pub inhibitors: Option<Inhibitors>,

/// Map glob / patterns to their configuration
pub paths: BTreeMap<Pattern, PathDefinition>,

/// Named handlers within this trigger scope
pub handlers: BTreeMap<String, Handler>,
}

#[cfg(test)]
mod tests {
use crate::format::Trigger;

#[test]
fn test_trigger_file() {
let trigger: Trigger = serde_yaml::from_str(include_str!("../../../test/trigger.yml")).unwrap();

let (pattern, _) = trigger.paths.iter().next().expect("Missing path entry");
let result = pattern
.matches("/usr/lib/modules/6.6.7-267.current/kernel")
.expect("Couldn't match path");
let version = result.groups.get("version").expect("Missing kernel version");
assert_eq!(version, "6.6.7-267.current", "Wrong kernel version match");
eprintln!("trigger: {trigger:?}");
eprintln!("match: {result:?}");
let de = BTreeMap::<fnmatch::Pattern, PathDefinition>::deserialize(deserializer)?;
let mut paths = BTreeMap::new();
for (pattern, path_definition) in de {
paths.insert(
Pattern {
kind: path_definition.kind,
pattern,
},
path_definition.handlers,
);
}
Ok(paths)
}
13 changes: 13 additions & 0 deletions crates/triggers/src/iterpaths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: Copyright © 2020-2024 Serpent OS Developers
//
// SPDX-License-Identifier: MPL-2.0

//! Utility functions that accept multiple file paths.

use std::collections::BTreeSet;

use crate::{CompiledHandler, Trigger};

pub fn compiled_handlers(trigger: &Trigger, paths: impl Iterator<Item = String>) -> BTreeSet<CompiledHandler> {
paths.flat_map(|path| trigger.compiled_handlers(path)).collect()
}
Loading