use std::{
fmt,
path::{Path, PathBuf},
};
use anyhow::{Context, Result, bail};
pub const PROJECT_CONFIG_FILE: &str = "fraiseql.toml";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigSource {
Input(PathBuf),
Explicit(PathBuf),
Discovered(PathBuf),
Absent {
searched_from: PathBuf,
},
}
impl ConfigSource {
pub fn resolve(input: &Path, explicit: Option<&Path>) -> Result<Self> {
let is_toml = input
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext.eq_ignore_ascii_case("toml"));
if is_toml {
if let Some(explicit) = explicit {
bail!(
"--config {} cannot be combined with the TOML input {}: that file is \
already the project config. Pass one or the other.",
explicit.display(),
input.display()
);
}
return Ok(Self::Input(input.to_path_buf()));
}
if let Some(explicit) = explicit {
if !explicit.is_file() {
bail!("--config {}: no such file", explicit.display());
}
return Ok(Self::Explicit(explicit.to_path_buf()));
}
let absolute = std::path::absolute(input)
.with_context(|| format!("Cannot resolve the directory of {}", input.display()))?;
let start = absolute.parent().unwrap_or(&absolute).to_path_buf();
Ok(discover_from(&start).map_or(
Self::Absent {
searched_from: start,
},
Self::Discovered,
))
}
#[must_use]
pub fn project_config(&self) -> Option<&Path> {
match self {
Self::Explicit(path) | Self::Discovered(path) => Some(path),
Self::Input(_) | Self::Absent { .. } => None,
}
}
}
impl fmt::Display for ConfigSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input(path) => write!(f, "{} (the input)", path.display()),
Self::Explicit(path) => write!(f, "{} (--config)", path.display()),
Self::Discovered(path) => write!(f, "{}", path.display()),
Self::Absent { searched_from } => write!(
f,
"none — no {PROJECT_CONFIG_FILE} in {} or above it; defaults apply",
searched_from.display()
),
}
}
}
fn discover_from(start: &Path) -> Option<PathBuf> {
for dir in start.ancestors() {
let candidate = dir.join(PROJECT_CONFIG_FILE);
if candidate.is_file() {
return Some(candidate);
}
if dir.join(".git").exists() {
return None;
}
}
None
}
#[cfg(test)]
mod tests;