1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! This module defines the [IgnoreGlobs]. To set it up from [Cli], a [Config] and its
//! [Default] value, use the [configure_from](IgnoreGlobs::configure_from) method.
use crate::app::Cli;
use crate::config_file::Config;
use clap::Error;
use clap::error::ErrorKind;
use globset::{Glob, GlobSet, GlobSetBuilder};
/// The struct holding a [GlobSet] and methods to build it.
#[derive(Clone, Debug)]
pub struct IgnoreGlobs(pub GlobSet);
impl IgnoreGlobs {
/// Returns a value from either [Cli], a [Config] or a [Default] value. The first value
/// that is not [None] is used. The order of precedence for the value used is:
/// - [from_cli](IgnoreGlobs::from_cli)
/// - [from_config](IgnoreGlobs::from_config)
/// - [Default::default]
///
/// # Errors
///
/// If either of the [Glob::new] or [GlobSetBuilder.build] methods return an [Err].
pub fn configure_from(cli: &Cli, config: &Config) -> Result<Self, Error> {
if let Some(value) = Self::from_cli(cli) {
return value;
}
if let Some(value) = Self::from_config(config) {
return value;
}
Ok(Default::default())
}
/// Get a potential [IgnoreGlobs] from [Cli].
///
/// If the "ignore-glob" argument has been passed, this returns a [Result] in a [Some] with
/// either the built [IgnoreGlobs] or an [Error], if any error was encountered while creating the
/// [IgnoreGlobs]. If the argument has not been passed, this returns [None].
fn from_cli(cli: &Cli) -> Option<Result<Self, Error>> {
if cli.ignore_glob.is_empty() {
return None;
}
let mut glob_set_builder = GlobSetBuilder::new();
for value in &cli.ignore_glob {
match Self::create_glob(value) {
Ok(glob) => {
glob_set_builder.add(glob);
}
Err(err) => return Some(Err(err)),
}
}
Some(Self::create_glob_set(&glob_set_builder).map(Self))
}
/// Get a potential [IgnoreGlobs] from a [Config].
///
/// If the `Config::ignore-globs` contains an Array of Strings,
/// each of its values is used to build the [GlobSet]. If the building
/// succeeds, the [IgnoreGlobs] is returned in the [Result] in a [Some]. If any error is
/// encountered while building, an [Error] is returned in the Result instead. If the Config does
/// not contain such a key, this returns [None].
fn from_config(config: &Config) -> Option<Result<Self, Error>> {
let globs = config.ignore_globs.as_ref()?;
let mut glob_set_builder = GlobSetBuilder::new();
for glob in globs {
match Self::create_glob(glob) {
Ok(glob) => {
glob_set_builder.add(glob);
}
Err(err) => return Some(Err(err)),
}
}
Some(Self::create_glob_set(&glob_set_builder).map(Self))
}
/// Create a [Glob] from a provided pattern.
///
/// This method is mainly a helper to wrap the handling of potential errors.
fn create_glob(pattern: &str) -> Result<Glob, Error> {
Glob::new(pattern).map_err(|err| Error::raw(ErrorKind::ValueValidation, err))
}
/// Create a [GlobSet] from a provided [GlobSetBuilder].
///
/// This method is mainly a helper to wrap the handling of potential errors.
fn create_glob_set(builder: &GlobSetBuilder) -> Result<GlobSet, Error> {
builder
.build()
.map_err(|err| Error::raw(ErrorKind::ValueValidation, err))
}
}
/// The default value of `IgnoreGlobs` is the empty [GlobSet], returned by [GlobSet::empty()].
impl Default for IgnoreGlobs {
fn default() -> Self {
Self(GlobSet::empty())
}
}
#[cfg(test)]
mod test {
use clap::Parser;
use super::IgnoreGlobs;
use crate::app::Cli;
use crate::config_file::Config;
// The following tests are implemented using match expressions instead of the assert_eq macro,
// because clap::Error does not implement PartialEq.
//
// Further no tests for actually returned GlobSets are implemented, because GlobSet does not
// even implement PartialEq and thus can not be easily compared.
#[test]
fn test_configuration_from_none() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
assert!(matches!(
IgnoreGlobs::configure_from(&cli, &Config::with_none()),
Ok(..)
));
}
#[test]
fn test_configuration_from_args() {
let argv = ["lsd", "--ignore-glob", ".git"];
let cli = Cli::try_parse_from(argv).unwrap();
assert!(matches!(
IgnoreGlobs::configure_from(&cli, &Config::with_none()),
Ok(..)
));
}
#[test]
fn test_configuration_from_config() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
let mut c = Config::with_none();
c.ignore_globs = Some(vec![".git".into()]);
assert!(matches!(IgnoreGlobs::configure_from(&cli, &c), Ok(..)));
}
#[test]
fn test_from_cli_none() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
assert!(IgnoreGlobs::from_cli(&cli).is_none());
}
#[test]
fn test_from_config_none() {
assert!(IgnoreGlobs::from_config(&Config::with_none()).is_none());
}
}