use crate::opts::start_time::StartTime;
use crate::opts::GitOptions;
use crate::opts::Opts;
use crate::opts::RunOptions;
use crate::opts::SubCommand;
use crate::tasks::git;
use crate::utils::files;
use camino::Utf8Path;
use camino::Utf8PathBuf;
use color_eyre::eyre::bail;
use color_eyre::eyre::ensure;
use color_eyre::eyre::Result;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use std::collections::HashMap;
use std::env;
use std::fs;
use tracing::debug;
use tracing::info;
use tracing::trace;
#[derive(Default, Debug)]
pub struct UpConfig {
pub up_yaml_path: Option<Utf8PathBuf>,
pub config_yaml: ConfigYaml,
pub bootstrap: bool,
pub keep_going: bool,
pub tasks: Option<Vec<String>>,
pub exclude_tasks: Option<Vec<String>>,
pub console: Option<bool>,
pub temp_dir: Utf8PathBuf,
pub start_time: StartTime,
}
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigYaml {
tasks_path: Option<String>,
pub env: Option<HashMap<String, String>>,
pub inherit_env: Option<Vec<String>>,
pub bootstrap_tasks: Option<Vec<String>>,
}
impl UpConfig {
pub fn from(opts: Opts) -> Result<Self> {
let mut config_yaml = ConfigYaml::default();
let run_options = match opts.cmd {
Some(SubCommand::Run(task_opts) | SubCommand::List(task_opts)) => task_opts,
_ => RunOptions::default(),
};
let mut config_path_explicitly_specified = true;
let up_yaml_path = match (
Self::get_up_yaml_path(&opts.config),
run_options.fallback_url,
) {
(Ok(up_yaml_path), _) if up_yaml_path.exists() => up_yaml_path,
(result, Some(fallback_url)) => {
info!("Config path not found, falling back to {fallback_url}");
debug!("Yaml path failure: {result:?}");
if result.is_ok() {
config_path_explicitly_specified = false;
}
get_fallback_config_path(&opts.temp_dir, fallback_url, run_options.fallback_path)?
}
(Ok(up_yaml_path), _) => up_yaml_path,
(Err(e), None) => {
return Err(e);
}
};
let up_yaml_path = if up_yaml_path.exists() {
let read_result = fs::read(&up_yaml_path);
if let Ok(file_contents) = read_result {
let config_str = String::from_utf8_lossy(&file_contents);
debug!("config_str: {config_str:?}");
if config_str.is_empty() {
debug!("Yaml file was empty, using default config.");
} else {
config_yaml = serde_yaml::from_str::<ConfigYaml>(&config_str)?;
};
debug!("Config_yaml: {config_yaml:?}");
}
Some(up_yaml_path)
} else if config_path_explicitly_specified {
bail!("Config path explicitly provided, but not found.");
} else {
None
};
let bootstrap = run_options.bootstrap;
let keep_going = run_options.keep_going;
Ok(Self {
up_yaml_path,
config_yaml,
bootstrap,
keep_going,
temp_dir: opts.temp_dir.as_ref().to_owned(),
tasks: run_options.tasks,
exclude_tasks: run_options.exclude_tasks,
start_time: opts.start_time,
console: run_options.console,
})
}
fn get_up_yaml_path(args_config_path: &str) -> Result<Utf8PathBuf> {
debug!("args_config_file: {args_config_path}");
let mut config_path: Utf8PathBuf;
if args_config_path == "$XDG_CONFIG_HOME/up/up.yaml" {
let up_config_env = env::var("UP_CONFIG");
if let Ok(config_path) = up_config_env {
let config_path = Utf8PathBuf::from(config_path);
ensure!(
config_path.exists(),
"Config path specified in UP_CONFIG env var doesn't exist.\n config_path: \
{config_path}",
);
return Ok(config_path);
}
trace!("Checking default config paths.");
let home_dir = files::home_dir()?;
config_path = env::var("XDG_CONFIG_HOME")
.map_or_else(|_e| home_dir.join(".config"), Utf8PathBuf::from);
config_path.push("up");
config_path.push("up.yaml");
} else {
config_path = Utf8PathBuf::from(args_config_path);
ensure!(
config_path.exists(),
"Config path specified in -c/--config arg doesn't exist.\n config_path: \
{config_path}",
);
}
Ok(config_path)
}
}
fn get_fallback_config_path(
temp_dir: &Utf8Path,
mut fallback_url: String,
fallback_path: Utf8PathBuf,
) -> Result<Utf8PathBuf> {
if !fallback_url.contains("://") {
fallback_url = format!("https://github.com/{fallback_url}");
}
let fallback_repo_path = temp_dir.join("up/fallback_repo");
files::create_dir_all(&fallback_repo_path)?;
let fallback_config_path = fallback_repo_path.join(fallback_path);
git::update::update(
&GitOptions {
git_url: fallback_url,
git_path: fallback_repo_path,
remote: git::DEFAULT_REMOTE_NAME.to_owned(),
..GitOptions::default()
}
.into(),
)?;
ensure!(
fallback_config_path.exists(),
"Fallback config path doesn't exist.\n config_path: {fallback_config_path}",
);
Ok(fallback_config_path)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::UpConfig;
use color_eyre::Result;
use serial_test::serial;
use std::env;
use testutils::ensure_eq;
#[test]
#[serial(home_dir)] fn test_get_yaml_paths() -> Result<()> {
let orig_home = env::var("HOME").unwrap();
let default_path = "$XDG_CONFIG_HOME/up/up.yaml";
let fake_home_1 = testutils::fixtures_subdir(testutils::function_path!())?
.join("fake_home_dir_with_upconfig");
let config_yaml_1 = fake_home_1.join(".config/up/up.yaml");
let fake_home_2 = testutils::fixtures_subdir(testutils::function_path!())?
.join("fake_home_dir_without_upconfig");
let args_config_path = env::current_exe().unwrap();
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".config"));
let config_path = UpConfig::get_up_yaml_path(args_config_path.to_str().unwrap());
ensure_eq!(config_path.unwrap(), args_config_path);
env::set_var("UP_CONFIG", args_config_path.clone());
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".config"));
let config_path = UpConfig::get_up_yaml_path(default_path);
ensure_eq!(config_path.unwrap(), args_config_path);
env::remove_var("UP_CONFIG");
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".config"));
let config_path = UpConfig::get_up_yaml_path(default_path);
ensure_eq!(config_path.unwrap(), config_yaml_1);
env::set_var("HOME", fake_home_1.clone());
env::set_var("XDG_CONFIG_HOME", fake_home_1.join(".badconfig"));
let config_path = UpConfig::get_up_yaml_path(default_path);
ensure_eq!(
config_path.unwrap(),
fake_home_1.join(".badconfig/up/up.yaml")
);
env::remove_var("XDG_CONFIG_HOME");
let config_path = UpConfig::get_up_yaml_path(default_path);
ensure_eq!(config_path.unwrap(), config_yaml_1);
env::set_var("HOME", fake_home_2.clone());
env::remove_var("XDG_CONFIG_HOME");
let config_path = UpConfig::get_up_yaml_path(default_path);
ensure_eq!(config_path.unwrap(), fake_home_2.join(".config/up/up.yaml"),);
env::set_var("HOME", orig_home);
Ok(())
}
}