Skip to main content

rustic_core/blob/tree/
excludes.rs

1use derive_setters::Setters;
2use ignore::overrides::{Override, OverrideBuilder};
3use serde::{Deserialize, Serialize};
4
5use crate::{ErrorKind, RusticError, RusticResult};
6
7#[cfg_attr(feature = "clap", derive(clap::Parser))]
8#[cfg_attr(feature = "merge", derive(conflate::Merge))]
9#[derive(Clone, Debug, Default, PartialEq, Eq, Setters, Serialize, Deserialize)]
10#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
11#[setters(into)]
12#[non_exhaustive]
13/// Options for including/excluding based on globs
14pub struct Excludes {
15    /// Glob pattern to exclude/include (can be specified multiple times)
16    #[cfg_attr(feature = "clap", clap(long = "glob", value_name = "GLOB"))]
17    #[cfg_attr(feature = "merge", merge(strategy = conflate::vec::overwrite_empty))]
18    pub globs: Vec<String>,
19
20    /// Same as --glob pattern but ignores the casing of filenames
21    #[cfg_attr(feature = "clap", clap(long = "iglob", value_name = "GLOB"))]
22    #[cfg_attr(feature = "merge", merge(strategy = conflate::vec::overwrite_empty))]
23    pub iglobs: Vec<String>,
24
25    /// Read glob patterns to exclude/include from this file (can be specified multiple times)
26    #[cfg_attr(feature = "clap", clap(long = "glob-file", value_name = "FILE",))]
27    #[cfg_attr(feature = "merge", merge(strategy = conflate::vec::overwrite_empty))]
28    pub glob_files: Vec<String>,
29
30    /// Same as --glob-file ignores the casing of filenames in patterns
31    #[cfg_attr(feature = "clap", clap(long = "iglob-file", value_name = "FILE",))]
32    #[cfg_attr(feature = "merge", merge(strategy = conflate::vec::overwrite_empty))]
33    pub iglob_files: Vec<String>,
34}
35
36impl Excludes {
37    #[must_use]
38    /// Determines if no exclude is in fact given
39    pub fn is_empty(&self) -> bool {
40        self == &Self::default()
41    }
42
43    pub(crate) fn as_override(&self) -> RusticResult<Override> {
44        let mut override_builder = OverrideBuilder::new("");
45        for g in &self.globs {
46            _ = override_builder.add(g).map_err(|err| {
47                RusticError::with_source(
48                    ErrorKind::Internal,
49                    "Failed to add glob pattern `{glob}` to override builder.",
50                    err,
51                )
52                .attach_context("glob", g)
53                .ask_report()
54            })?;
55        }
56
57        for file in &self.glob_files {
58            for line in std::fs::read_to_string(file)
59                .map_err(|err| {
60                    RusticError::with_source(
61                        ErrorKind::Internal,
62                        "Failed to read string from glob file `{glob_file}` ",
63                        err,
64                    )
65                    .attach_context("glob_file", file)
66                    .ask_report()
67                })?
68                .lines()
69            {
70                _ = override_builder.add(line).map_err(|err| {
71                    RusticError::with_source(
72                        ErrorKind::Internal,
73                        "Failed to add glob pattern line `{glob_pattern_line}` to override builder.",
74                        err,
75                    )
76                    .attach_context("glob_pattern_line", line.to_string())
77                    .ask_report()
78                })?;
79            }
80        }
81
82        _ = override_builder.case_insensitive(true).map_err(|err| {
83            RusticError::with_source(
84                ErrorKind::Internal,
85                "Failed to set case insensitivity in override builder.",
86                err,
87            )
88            .ask_report()
89        })?;
90        for g in &self.iglobs {
91            _ = override_builder.add(g).map_err(|err| {
92                RusticError::with_source(
93                    ErrorKind::Internal,
94                    "Failed to add iglob pattern `{iglob}` to override builder.",
95                    err,
96                )
97                .attach_context("iglob", g)
98                .ask_report()
99            })?;
100        }
101
102        for file in &self.iglob_files {
103            for line in std::fs::read_to_string(file)
104                .map_err(|err| {
105                    RusticError::with_source(
106                        ErrorKind::Internal,
107                        "Failed to read string from iglob file `{iglob_file}`",
108                        err,
109                    )
110                    .attach_context("iglob_file", file)
111                    .ask_report()
112                })?
113                .lines()
114            {
115                _ = override_builder.add(line).map_err(|err| {
116                    RusticError::with_source(
117                        ErrorKind::Internal,
118                        "Failed to add iglob pattern line `{iglob_pattern_line}` to override builder.",
119                        err,
120                    )
121                    .attach_context("iglob_pattern_line", line.to_string())
122                    .ask_report()
123                })?;
124            }
125        }
126        let overrides = override_builder.build().map_err(|err| {
127            RusticError::with_source(
128                ErrorKind::Internal,
129                "Failed to build matcher for a set of glob overrides.",
130                err,
131            )
132            .ask_report()
133        })?;
134        Ok(overrides)
135    }
136}