Skip to main content

jobberdb/
tag_set.rs

1//! Set of job tags.
2
3use super::prelude::*;
4use serde::{Deserialize, Serialize};
5
6/// Set of job tags.
7#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
8pub struct TagSet(pub Vec<String>);
9
10impl TagSet {
11    /// Create from scratch.
12    pub const fn new() -> Self {
13        Self(Vec::new())
14    }
15    /// Filter tags.
16    pub fn filter<P>(&self, pred: P) -> Self
17    where
18        P: Fn(&&String) -> bool,
19    {
20        TagSet(self.0.iter().filter(pred).map(|t| t.to_string()).collect())
21    }
22    /// Return read-only iterator
23    pub fn iter(&self) -> core::slice::Iter<'_, String> {
24        self.0.iter()
25    }
26    /// Check if tag is contained.
27    pub fn contains(&self, tag: &String) -> bool {
28        self.0.contains(tag)
29    }
30    /// Check if this set is empty.
31    pub fn is_empty(&self) -> bool {
32        self.0.is_empty()
33    }
34    /// Return number of contained tags.
35    pub fn len(&self) -> usize {
36        self.0.len()
37    }
38    /// Modify tags like described in the manual.
39    pub fn modify(&self, modification: &TagSet) -> TagSet {
40        // check if modification markers ('+' or '-') are used
41        if modification
42            .iter()
43            .any(|tag| tag.starts_with('+') || tag.ends_with('+'))
44        {
45            let mut tags = self.clone();
46            for tag in modification.iter() {
47                if tag.is_empty() {
48                    // ignore empty tags
49                    continue;
50                } else if tag.starts_with('-') || tag.ends_with('-') {
51                    tags.remove(tag[1..].into());
52                } else {
53                    tags.insert(tag[1..].into());
54                }
55            }
56            tags
57        } else {
58            modification.clone()
59        }
60    }
61}
62
63impl TagSet {
64    /// Insert net tag.
65    pub fn insert(&mut self, tag: &str) -> bool {
66        if self.0.contains(&tag.to_string()) {
67            false
68        } else {
69            self.0.push(tag.to_string());
70            true
71        }
72    }
73    /// Insert a lot of tags.
74    pub fn insert_many(&mut self, tags: TagSet) {
75        for tag in tags.iter() {
76            self.insert(tag);
77        }
78    }
79    /// Remove a tag from the set.
80    pub fn remove(&mut self, tag: &str) {
81        self.0 = self
82            .0
83            .iter()
84            .filter(|t| *t != tag)
85            .map(|t| t.to_string())
86            .collect();
87    }
88}
89impl std::fmt::Display for TagSet {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        for (n, tag) in self.0.iter().enumerate() {
92            tags::format(f, tag)?;
93            if n + 1 < self.0.len() {
94                write!(f, ", ")?;
95            }
96        }
97        Ok(())
98    }
99}
100
101impl From<Option<TagSet>> for TagSet {
102    /// Flatten optional [TagSet] (empty if option was `None`).
103    fn from(tags: Option<TagSet>) -> Self {
104        if let Some(tags) = tags {
105            tags.clone()
106        } else {
107            TagSet::new()
108        }
109    }
110}
111
112impl From<&Option<String>> for TagSet {
113    /// Convert from optional comma separated string of tag names without spaces.
114    fn from(tag: &Option<String>) -> Self {
115        if let Some(tag) = tag {
116            Self(tag.split(',').map(|t| t.to_string()).collect())
117        } else {
118            Self(Vec::new())
119        }
120    }
121}
122impl From<&str> for TagSet {
123    /// convert from comma separated string of tag names without spaces
124    fn from(tag: &str) -> Self {
125        assert!(!tag.contains(' '));
126        Self(tag.split(',').map(|t| t.to_string()).collect())
127    }
128}