use std::collections::HashMap;
use super::{Error, FunctionLines};
use crate::Repository;
use crate::attributes::{LoadError, Lookup, State};
use crate::config::{Config, InvalidValue};
use crate::workflow::Environment;
#[derive(Debug)]
pub struct DiffAttributes {
lookup: Lookup,
settings: HashMap<Vec<u8>, Settings>,
functions: HashMap<Vec<u8>, Option<FunctionLines>>,
last: Option<(Vec<u8>, Driver)>,
}
#[derive(Clone, Debug, Default)]
struct Settings {
binary: Option<bool>,
function: Option<(Vec<u8>, bool)>,
textconv: bool,
command: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(super) enum Content {
#[default]
Auto,
Text,
Binary,
}
#[derive(Clone, Debug, Default)]
pub(super) struct Driver {
pub(super) name: Option<Vec<u8>>,
pub(super) content: Content,
pub(super) function: Option<FunctionLines>,
pub(super) external: Option<&'static str>,
}
impl DiffAttributes {
pub fn new(lookup: Lookup, config: &Config) -> Result<Self, InvalidValue> {
let mut settings: HashMap<Vec<u8>, Settings> = HashMap::new();
for entry in config.entries() {
let Some(driver) = entry
.subsection
.as_ref()
.filter(|_| entry.section.eq_ignore_ascii_case(b"diff"))
else {
continue;
};
let value = entry.value.as_deref();
let name = entry.name.to_ascii_lowercase();
let invalid = |name: &str| InvalidValue::new("diff", name, value);
let driver = settings.entry(driver.clone()).or_default();
match name.as_slice() {
b"binary" => {
driver.binary = Some(crate::config::boolean(value).ok_or(invalid("binary"))?);
}
b"xfuncname" => {
driver.function = Some((value.ok_or(invalid("xfuncname"))?.to_vec(), true));
}
b"funcname" => {
driver.function = Some((value.ok_or(invalid("funcname"))?.to_vec(), false));
}
b"textconv" => driver.textconv = !value.ok_or(invalid("textconv"))?.is_empty(),
b"command" => driver.command = !value.ok_or(invalid("command"))?.is_empty(),
_ => {}
}
}
Ok(Self {
lookup,
settings,
functions: HashMap::new(),
last: None,
})
}
pub(super) fn driver(&mut self, path: &[u8]) -> Result<Driver, Error> {
if let Some((last, driver)) = &self.last
&& last == path
{
return Ok(driver.clone());
}
let driver = self.resolve(path)?;
self.last = Some((path.to_vec(), driver.clone()));
Ok(driver)
}
fn resolve(&mut self, path: &[u8]) -> Result<Driver, Error> {
let attributes = |source| Error::Attributes {
path: path.to_vec(),
source: Box::new(source),
};
let name = match self.lookup.get(path, b"diff").map_err(attributes)? {
State::Unspecified => return Ok(Driver::default()),
State::Set => {
return Ok(Driver {
content: Content::Text,
..Driver::default()
});
}
State::Unset => {
return Ok(Driver {
content: Content::Binary,
..Driver::default()
});
}
State::Value(name) => name.to_vec(),
};
let settings = self.settings.get(&name).cloned().unwrap_or_default();
let function = match self.functions.get(&name) {
Some(function) => function.clone(),
None => {
let function = match &settings.function {
Some((pattern, true)) => Some(FunctionLines::extended(pattern)),
Some((pattern, false)) => Some(FunctionLines::basic(pattern)),
None => FunctionLines::builtin(&name).map(Ok),
}
.transpose()
.map_err(|source| Error::Function {
driver: name.clone(),
source,
})?;
self.functions.insert(name.clone(), function.clone());
function
}
};
Ok(Driver {
content: match settings.binary {
Some(true) => Content::Binary,
Some(false) => Content::Text,
None => Content::Auto,
},
function,
external: if settings.textconv {
Some("textconv")
} else if settings.command {
Some("command")
} else {
None
},
name: Some(name),
})
}
}
impl DiffAttributes {
pub(crate) fn textconv_driver(&mut self, path: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let driver = self.driver(path)?;
Ok((driver.external == Some("textconv")).then(|| driver.name.unwrap_or_default()))
}
}
impl Repository {
pub fn diff_attributes(&self, environment: &Environment) -> Result<DiffAttributes, LoadError> {
let source = self.attribute_source(environment)?;
let lookup = self.attributes(environment, source)?;
Ok(DiffAttributes::new(lookup, self.config())?)
}
}