use std::path::{Path, PathBuf};
use crate::Environment;
use super::ConfigError;
pub const PROJECT_DOCUMENT_NAME: &str = "onetaskgraph.yaml";
pub const USER_DOCUMENT_RELATIVE_PATH: &str = "onetaskgraph/config.yaml";
pub const SECRETS_RELATIVE_PATH: &str = "onetaskgraph/secrets.env";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Document {
pub path: PathBuf,
pub text: String,
}
pub fn documents(
working_directory: &Path,
environment: &Environment,
) -> Result<Vec<Document>, ConfigError> {
let mut found = Vec::new();
if let Some(path) = user_document_path(environment)
&& let Some(text) = read_optional(&path)?
{
found.push(Document { path, text });
}
if let Some(path) = nearest_project_document(working_directory)?
&& let Some(text) = read_optional(&path)?
{
found.push(Document { path, text });
}
Ok(found)
}
#[must_use]
pub fn readable_documents(
working_directory: Option<&Path>,
environment: &Environment,
) -> Vec<Document> {
let project =
working_directory.and_then(|directory| nearest_project_document(directory).ok().flatten());
[user_document_path(environment), project]
.into_iter()
.flatten()
.filter_map(|path| {
let text = read_optional(&path).ok().flatten()?;
Some(Document { path, text })
})
.collect()
}
#[must_use]
pub fn user_document_path(environment: &Environment) -> Option<PathBuf> {
Some(configuration_home(environment)?.join(USER_DOCUMENT_RELATIVE_PATH))
}
#[must_use]
pub fn secrets_path(environment: &Environment) -> Option<PathBuf> {
if let Some(override_path) = environment.non_empty(super::SECRETS_FILE_VARIABLE) {
return Some(PathBuf::from(override_path));
}
Some(configuration_home(environment)?.join(SECRETS_RELATIVE_PATH))
}
fn configuration_home(environment: &Environment) -> Option<PathBuf> {
if let Some(xdg) = environment.non_empty("XDG_CONFIG_HOME") {
return Some(PathBuf::from(xdg));
}
Some(PathBuf::from(environment.non_empty("HOME")?).join(".config"))
}
fn nearest_project_document(working_directory: &Path) -> Result<Option<PathBuf>, ConfigError> {
for directory in working_directory.ancestors() {
let candidate = directory.join(PROJECT_DOCUMENT_NAME);
match candidate.try_exists() {
Ok(true) => return Ok(Some(candidate)),
Ok(false) => {}
Err(error) => return Err(ConfigError::read(&candidate, &error)),
}
}
Ok(None)
}
pub fn read_optional(path: &Path) -> Result<Option<String>, ConfigError> {
match std::fs::read_to_string(path) {
Ok(text) => Ok(Some(text)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(ConfigError::read(path, &error)),
}
}