use std::path::{Path, PathBuf};
use serde_json::Value;
use crate::PluginKind;
use crate::subprocess::DocumentDir;
use super::{ConfigError, Merged, Origin, SettingPath};
pub fn resolve_document_relative_paths(settings: &mut Merged) -> Result<(), ConfigError> {
let mut rewrites: Vec<(SettingPath, Value)> = Vec::new();
for setting in settings.values() {
let Origin::File { path: document } = &setting.origin else {
continue;
};
let Some((source, field)) = source_config_field(&setting.key) else {
continue;
};
let Some(declared) = declared_paths_of(settings, source) else {
continue;
};
if !declared.contains(&field.as_str()) {
continue;
}
let Some(raw) = setting.value.as_str() else {
continue;
};
let Some(directory) = document.parent() else {
continue;
};
let Some(rebased) = rebased(directory, raw) else {
continue;
};
let rebased = rebased.into_os_string().into_string().map_err(|_| {
ConfigError::setting(
setting.key.to_string(),
format!(
"this path is measured from {}, the directory holding the configuration \
document that set it, and that directory's name is not valid UTF-8, so \
the path it resolves to cannot be written down",
directory.display()
),
"give this setting an absolute path, or move the configuration document \
under a directory whose name is valid UTF-8.",
)
})?;
rewrites.push((setting.key.clone(), Value::String(rebased)));
}
for (key, value) in rewrites {
settings.set_value(&key, value);
}
Ok(())
}
pub(crate) fn rebased(directory: &Path, raw: &str) -> Option<PathBuf> {
if raw.is_empty() || Path::new(raw).is_absolute() {
return None;
}
Some(directory.join(raw))
}
pub(super) fn supplying_document_dir(
settings: &Merged,
name: &str,
) -> Result<Option<DocumentDir>, ConfigError> {
let block = ["sources", name, "config", crate::subprocess::SETTINGS_FIELD];
let mut supplying: Option<&Path> = None;
for setting in settings.values() {
if !setting
.key
.segments()
.starts_with(&block.map(str::to_owned))
{
continue;
}
let Origin::File { path: document } = &setting.origin else {
return Ok(None);
};
match supplying {
Some(earlier) if earlier != document.as_path() => return Ok(None),
_ => supplying = Some(document),
}
}
let Some(directory) = supplying.and_then(Path::parent) else {
return Ok(None);
};
let directory = if directory.as_os_str().is_empty() {
Path::new(".")
} else {
directory
};
let directory = std::path::absolute(directory).unwrap_or_else(|_| directory.to_path_buf());
DocumentDir::new(&directory).map(Some).map_err(|problem| {
ConfigError::setting(
block.join("."),
format!(
"the paths in this block are measured from the directory holding the \
configuration document that set it, and {problem}"
),
"give this block's paths absolute values, or move the configuration document \
under a directory whose name is valid UTF-8.",
)
})
}
fn source_config_field(key: &SettingPath) -> Option<(&str, String)> {
match key.segments() {
[sources, name, config, field @ ..]
if sources == "sources" && config == "config" && !field.is_empty() =>
{
Some((name.as_str(), field.join(".")))
}
_ => None,
}
}
fn declared_paths_of(settings: &Merged, name: &str) -> Option<&'static [&'static str]> {
let key = SettingPath::new(
vec!["sources".to_owned(), name.to_owned(), "plugin".to_owned()],
"sources",
)
.ok()?;
let kind = PluginKind::parse(settings.get(&key)?.value.as_str()?)?;
Some(kind.plugin().document_relative_paths())
}