xvc_pipeline/pipeline/deps/
regex_items.rs

1//! A dependency that depends on a regex searched in a text file. Unline [RegexDep], this
2//! dependency tracks all the lines that matches the regex.
3use std::io::{self, BufRead};
4
5use regex::Regex;
6use serde::{Deserialize, Serialize};
7use xvc_core::types::diff::Diffable;
8use xvc_core::{Diff, XvcMetadata, XvcPath, XvcPathMetadataMap, XvcRoot};
9use xvc_core::persist;
10
11use crate::XvcDependency;
12
13/// When a step depends to a regex searched in a text file
14#[derive(Debug, PartialOrd, Ord, Clone, Eq, PartialEq, Serialize, Deserialize)]
15pub struct RegexItemsDep {
16    /// Path of the file in the workspace
17    pub path: XvcPath,
18    /// The regex to search in the file
19    // We use this because Regex is not Serializable
20    pub regex: String,
21    /// Lines that match the regex in the file
22    pub lines: Vec<String>,
23    /// Metadata of the file
24    pub xvc_metadata: Option<XvcMetadata>,
25}
26
27persist!(RegexItemsDep, "regex-dependency");
28
29impl From<RegexItemsDep> for XvcDependency {
30    fn from(val: RegexItemsDep) -> Self {
31        XvcDependency::RegexItems(val)
32    }
33}
34
35impl RegexItemsDep {
36    /// Create a new RegexItemsDep with empty lines and metadata
37    pub fn new(path: XvcPath, regex: String) -> Self {
38        Self {
39            path,
40            regex,
41            lines: Vec::new(),
42            xvc_metadata: None,
43        }
44    }
45
46    /// Update the metadata of the dependency
47    pub fn update_metadata(self, xvc_metadata: Option<XvcMetadata>) -> Self {
48        Self {
49            xvc_metadata,
50            ..self
51        }
52    }
53
54    /// Update the metadata of the dependency from the given path metadata map
55    pub fn update_metadata_from_pmm(self, pmm: &XvcPathMetadataMap) -> Self {
56        let xvc_metadata = pmm.get(&self.path).cloned();
57        self.update_metadata(xvc_metadata)
58    }
59
60    /// Update the lines of the dependency by reading the file and searching the regex in it
61    pub fn update_lines(self, xvc_root: &XvcRoot) -> Self {
62        let path = self.path.to_absolute_path(xvc_root);
63        let regex = self.regex();
64        let file = std::fs::File::open(path).unwrap();
65        let line_reader = io::BufReader::new(file).lines();
66        let lines = line_reader
67            .filter_map(|line| {
68                if let Ok(line) = line {
69                    if regex.is_match(&line) {
70                        Some(line)
71                    } else {
72                        None
73                    }
74                } else {
75                    None
76                }
77            })
78            .collect();
79
80        Self { lines, ..self }
81    }
82
83    /// Returns the regex of the dependency
84    pub fn regex(&self) -> Regex {
85        Regex::new(&self.regex).unwrap()
86    }
87}
88
89impl Diffable for RegexItemsDep {
90    type Item = Self;
91
92    /// ⚠️ Call actual.update_metadata before calling this function ⚠️
93    fn diff_superficial(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
94        assert!(record.path == actual.path);
95
96        match (record.xvc_metadata, actual.xvc_metadata) {
97            (Some(rec_md), Some(act_md)) => {
98                if rec_md == act_md {
99                    Diff::Identical
100                } else {
101                    Diff::Different {
102                        record: record.clone(),
103                        actual: actual.clone(),
104                    }
105                }
106            }
107            (None, Some(_)) => Diff::RecordMissing {
108                actual: actual.clone(),
109            },
110            (Some(_), None) => Diff::ActualMissing {
111                record: record.clone(),
112            },
113            (None, None) => unreachable!("Either record or actual should have metadata"),
114        }
115    }
116
117    /// ⚠️ Call actual.update_lines before calling this function ⚠️
118    fn diff_thorough(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
119        assert!(record.path == actual.path);
120        if record.lines == actual.lines {
121            Diff::Identical
122        } else {
123            Diff::Different {
124                record: record.clone(),
125                actual: actual.clone(),
126            }
127        }
128    }
129}