use std::collections::HashMap;
use crate::cli;
use crate::git_config::GitConfig;
use crate::options::option_value::ProvenancedOptionValue;
use ProvenancedOptionValue::*;
pub type BuiltinFeature = HashMap<String, OptionValueFunction>;
type OptionValueFunction = Box<dyn Fn(&cli::Opt, &Option<GitConfig>) -> ProvenancedOptionValue>;
pub fn make_builtin_features() -> HashMap<String, BuiltinFeature> {
vec![
(
"color-only".to_string(),
color_only::make_feature().into_iter().collect(),
),
(
"diff-highlight".to_string(),
diff_highlight::make_feature().into_iter().collect(),
),
(
"diff-so-fancy".to_string(),
diff_so_fancy::make_feature().into_iter().collect(),
),
(
"hyperlinks".to_string(),
hyperlinks::make_feature().into_iter().collect(),
),
(
"line-numbers".to_string(),
line_numbers::make_feature().into_iter().collect(),
),
(
"navigate".to_string(),
navigate::make_feature().into_iter().collect(),
),
("raw".to_string(), raw::make_feature().into_iter().collect()),
(
"side-by-side".to_string(),
side_by_side::make_feature().into_iter().collect(),
),
]
.into_iter()
.collect()
}
macro_rules! builtin_feature {
([$( ($option_name:expr, $type:ty, $git_config_key:expr, $opt:ident => $value:expr) ),*]) => {
vec![$(
(
$option_name.to_string(),
Box::new(move |$opt: &$crate::cli::Opt, git_config: &Option<$crate::git_config::GitConfig>| {
match (git_config, $git_config_key) {
(Some(git_config), Some(git_config_key)) => git_config.get::<$type>(git_config_key).map(|value| $crate::features::GitConfigValue(value.into())),
_ => None,
}
.unwrap_or_else(|| $crate::features::DefaultValue($value.into()))
}) as OptionValueFunction
)
),*]
}
}
pub mod color_only;
pub mod diff_highlight;
pub mod diff_so_fancy;
pub mod hyperlinks;
pub mod line_numbers;
pub mod navigate;
pub mod raw;
pub mod side_by_side;
#[cfg(test)]
pub mod tests {
use std::collections::HashSet;
use std::fs::remove_file;
use crate::cli;
use crate::env::DeltaEnv;
use crate::features::make_builtin_features;
use crate::tests::integration_test_utils::make_options_from_args_and_git_config;
#[test]
fn test_builtin_features_have_flags_and_these_set_features() {
let builtin_features = make_builtin_features();
let mut args = vec!["delta".to_string()];
args.extend(builtin_features.keys().map(|s| format!("--{}", s)));
let opt = cli::Opt::from_iter_and_git_config(DeltaEnv::default(), args, None);
let features: HashSet<&str> = opt
.features
.as_deref()
.unwrap_or("")
.split_whitespace()
.collect();
for feature in builtin_features.keys() {
assert!(features.contains(feature.as_str()))
}
}
#[test]
fn test_builtin_feature_from_gitconfig() {
let git_config_contents = b"
[delta]
navigate = true
";
let git_config_path = "delta__test_builtin_feature_from_gitconfig.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&[],
Some(git_config_contents),
Some(git_config_path)
)
.features
.unwrap(),
"navigate"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_features_on_command_line_replace_features_in_gitconfig() {
let git_config_contents = b"
[delta]
features = my-feature
";
let git_config_path =
"delta__test_features_on_command_line_replace_features_in_gitconfig.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "navigate raw"],
Some(git_config_contents),
Some(git_config_path),
)
.features
.unwrap(),
"navigate raw"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--navigate", "--features", "raw"],
Some(git_config_contents),
Some(git_config_path),
)
.features
.unwrap(),
"navigate raw"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_feature_flag_on_command_line_does_not_replace_features_in_gitconfig() {
let git_config_contents = b"
[delta]
features = my-feature
";
let git_config_path =
"delta__test_feature_flag_on_command_line_does_not_replace_features_in_gitconfig.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&["--navigate", "--raw"],
Some(git_config_contents),
Some(git_config_path),
)
.features
.unwrap(),
"my-feature navigate raw"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_recursive_feature_gathering_1() {
let git_config_contents = b"
[delta]
features = h g
[delta \"a\"]
features = c b
diff-highlight = true
[delta \"d\"]
features = f e
diff-so-fancy = true
";
let git_config_path = "delta__test_feature_collection.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&["--raw", "--features", "d a"],
Some(git_config_contents),
Some(git_config_path),
)
.features
.unwrap(),
"raw diff-so-fancy f e d diff-highlight c b a"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_recursive_feature_gathering_2() {
let git_config_contents = b"
[delta]
features = feature-1
[delta \"feature-1\"]
features = feature-2 feature-3
[delta \"feature-2\"]
features = feature-4
[delta \"feature-4\"]
minus-style = blue
";
let git_config_path = "delta__test_recursive_features.gitconfig";
let opt = make_options_from_args_and_git_config(
&["delta"],
Some(git_config_contents),
Some(git_config_path),
);
assert_eq!(
opt.features.unwrap(),
"feature-4 feature-2 feature-3 feature-1"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_main_section() {
let git_config_contents = b"
[delta]
minus-style = blue
";
let git_config_path = "delta__test_main_section.gitconfig";
assert_ne!(
make_options_from_args_and_git_config(&[], None, None).minus_style,
"blue"
);
assert_eq!(
make_options_from_args_and_git_config(&["--minus-style", "red"], None, None)
.minus_style,
"red"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--minus-style", "red"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"red"
);
assert_eq!(
make_options_from_args_and_git_config(
&[],
Some(git_config_contents),
Some(git_config_path)
)
.minus_style,
"blue"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_feature() {
let git_config_contents = b"
[delta]
[delta \"my-feature\"]
minus-style = green
";
let git_config_path = "delta__test_feature.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"green"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_main_section_overrides_feature() {
let git_config_contents = b"
[delta]
minus-style = blue
[delta \"my-feature-1\"]
minus-style = green
";
let git_config_path = "delta__test_main_section_overrides_feature.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&[],
Some(git_config_contents),
Some(git_config_path)
)
.minus_style,
"blue"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-1"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"blue"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_multiple_features() {
let git_config_contents = b"
[delta]
[delta \"my-feature-1\"]
minus-style = green
[delta \"my-feature-2\"]
minus-style = yellow
";
let git_config_path = "delta__test_multiple_features.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-1 my-feature-2"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"yellow"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-2 my-feature-1"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"green"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_invalid_features() {
let git_config_contents = b"
[delta \"my-feature-1\"]
minus-style = green
[delta \"my-feature-2\"]
minus-style = yellow
";
let git_config_path = "delta__test_invalid_features.gitconfig";
let default = make_options_from_args_and_git_config(&[], None, None).minus_style;
assert_ne!(default, "green");
assert_ne!(default, "yellow");
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-1"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"green"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-x"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
default
);
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-1 my-feature-x"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"green"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-feature-x my-feature-2 my-feature-x"],
Some(git_config_contents),
Some(git_config_path),
)
.minus_style,
"yellow"
);
remove_file(git_config_path).unwrap();
}
#[test]
fn test_whitespace_error_style() {
let git_config_contents = b"
[color \"diff\"]
whitespace = yellow dim ul magenta
";
let git_config_path = "delta__test_whitespace_error_style.gitconfig";
assert_eq!(
make_options_from_args_and_git_config(&[], None, None).whitespace_error_style,
"magenta reverse"
);
assert_eq!(
make_options_from_args_and_git_config(
&[],
Some(git_config_contents),
Some(git_config_path)
)
.whitespace_error_style,
"yellow dim ul magenta"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--whitespace-error-style", "red reverse"],
Some(git_config_contents),
Some(git_config_path)
)
.whitespace_error_style,
"red reverse"
);
let git_config_contents = b"
[color \"diff\"]
whitespace = yellow dim ul magenta
[delta]
whitespace-error-style = blue reverse
[delta \"my-whitespace-error-style-feature\"]
whitespace-error-style = green reverse
";
assert_eq!(
make_options_from_args_and_git_config(
&["--whitespace-error-style", "red reverse"],
Some(git_config_contents),
Some(git_config_path)
)
.whitespace_error_style,
"red reverse"
);
assert_eq!(
make_options_from_args_and_git_config(
&[],
Some(git_config_contents),
Some(git_config_path)
)
.whitespace_error_style,
"blue reverse"
);
assert_eq!(
make_options_from_args_and_git_config(
&["--features", "my-whitespace-error-style-feature"],
Some(git_config_contents),
Some(git_config_path)
)
.whitespace_error_style,
"blue reverse"
);
remove_file(git_config_path).unwrap();
}
}