use std::collections::HashMap;
use anyhow::Error as AnyError;
use regex::Regex;
use crate::defs::{ConfgetError, Config, FileData, SectionData};
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
#[allow(clippy::module_name_repetitions)]
pub struct FormatOutput {
pub name: String,
pub value: String,
pub output_name: String,
pub output_value: String,
pub output_full: String,
}
fn compile_fnmatch(glob_pattern: &str) -> Result<Regex, ConfgetError> {
let pattern_vec: Vec<String> = glob_pattern
.chars()
.map(|chr| match chr {
'?' => ".".to_owned(),
'*' => ".*".to_owned(),
'.' | '+' | '(' | ')' | '[' | ']' => format!("\\{}", chr),
_ => chr.to_string(),
})
.collect();
let pattern = format!("^{}$", pattern_vec.iter().cloned().collect::<String>());
Regex::new(&pattern).map_err(|err| {
ConfgetError::Glob(
glob_pattern.to_owned(),
AnyError::new(ConfgetError::Regex(pattern, err)),
)
})
}
fn compile_regex(pattern: &str) -> Result<Regex, ConfgetError> {
Regex::new(pattern).map_err(|err| ConfgetError::Regex(pattern.to_owned(), err))
}
fn get_varnames_only<'data>(
config: &'data Config,
sect_data: &HashMap<&'data String, &String>,
) -> Result<Vec<&'data String>, ConfgetError> {
let mut res: Vec<&String> = if config.list_all {
sect_data.keys().copied().collect()
} else if config.match_var_names {
let regexes: Vec<Regex> = config
.varnames
.iter()
.map(|pat| {
if config.match_regex {
compile_regex(pat)
} else {
compile_fnmatch(pat)
}
})
.collect::<Result<_, _>>()?;
sect_data
.keys()
.filter(|value| regexes.iter().any(|re| re.is_match(value)))
.copied()
.collect()
} else {
config
.varnames
.iter()
.filter(|name| sect_data.contains_key(name))
.collect()
};
res.sort();
Ok(res)
}
fn get_varnames<'data>(
config: &'data Config,
sect_data: &HashMap<&'data String, &String>,
) -> Result<Vec<&'data String>, ConfgetError> {
let varnames = get_varnames_only(config, sect_data)?;
if let Some(ref pattern) = config.match_var_values {
let re = if config.match_regex {
compile_regex(pattern)?
} else {
compile_fnmatch(pattern)?
};
Ok(varnames
.iter()
.copied()
.filter(|name| {
sect_data
.get(name)
.map_or(false, |value| re.is_match(value))
})
.collect())
} else {
Ok(varnames)
}
}
#[inline]
pub fn filter_vars(
config: &Config,
data: &FileData,
section: &str,
) -> Result<Vec<FormatOutput>, ConfgetError> {
let empty: SectionData = SectionData::new();
let sect_iter_first = if config.section_override {
data.get("").map_or_else(|| empty.iter(), HashMap::iter)
} else {
empty.iter()
};
let sect_iter_second = data
.get(section)
.map_or_else(|| empty.iter(), HashMap::iter);
let sect_data: HashMap<&String, &String> = sect_iter_first.chain(sect_iter_second).collect();
get_varnames(config, §_data)?
.iter()
.map(|name| {
let value = sect_data.get(name).ok_or_else(|| {
ConfgetError::Internal(format!(
"Internal error: the '{}' variable should have been present in {:?}",
name, sect_data
))
})?;
let output_name = format!("{}{}{}", config.name_prefix, name, config.name_suffix);
let output_value = if config.shell_escape {
shell_words::quote(value).to_string()
} else {
(*value).to_string()
};
let output_full = if config.show_var_name {
format!("{}={}", output_name, output_value)
} else {
output_value.clone()
};
Ok(FormatOutput {
name: (*name).to_string(),
value: (*value).to_string(),
output_name,
output_value,
output_full,
})
})
.collect::<Result<_, _>>()
}