use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use serde::Deserialize;
use crate::cli::ConfigInitArgs;
use super::resolution::{CONFIG_FILE_NAME, resolve_start_dir};
pub(crate) fn init_config(args: ConfigInitArgs) -> Result<(), String> {
init_config_with_existing_config_path(args, existing_config_path)
}
#[cfg(test)]
pub(crate) fn init_config_without_existing_config(args: ConfigInitArgs) -> Result<(), String> {
init_config_with_existing_config_path(args, |_| Ok(None))
}
fn init_config_with_existing_config_path(
args: ConfigInitArgs,
existing_config_path: impl FnOnce(&Path) -> Result<Option<PathBuf>, String>,
) -> Result<(), String> {
let start_dir = resolve_start_dir(args.start_dir())?;
let path = existing_config_path(start_dir.as_path())?
.unwrap_or_else(|| init_config_path(start_dir.as_path()));
if path.exists() && !args.force() {
return Err(config_exists_message(path.as_path()));
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|err| format!("create config directory '{}': {err}", parent.display()))?;
}
fs::write(path.as_path(), render_default_config(&args))
.map_err(|err| format!("write IcyDB config '{}': {err}", path.display()))?;
println!("Wrote IcyDB config: {}", path.display());
Ok(())
}
fn init_config_path(start_dir: &Path) -> PathBuf {
cargo_metadata_workspace_root(start_dir)
.unwrap_or_else(|| start_dir.to_path_buf())
.join(CONFIG_FILE_NAME)
}
fn existing_config_path(start_dir: &Path) -> Result<Option<PathBuf>, String> {
icydb_config_build::load_resolved_icydb_toml(start_dir, &[])
.map(|resolved| resolved.config_path().map(Path::to_path_buf))
.map_err(|err| err.to_string())
}
fn cargo_metadata_workspace_root(start_dir: &Path) -> Option<PathBuf> {
let output = Command::new("cargo")
.arg("metadata")
.arg("--no-deps")
.arg("--format-version")
.arg("1")
.current_dir(start_dir)
.output()
.ok()?;
if !output.status.success() {
return None;
}
serde_json::from_slice::<CargoMetadata>(output.stdout.as_slice())
.ok()
.map(|metadata| metadata.workspace_root)
}
#[derive(Deserialize)]
struct CargoMetadata {
workspace_root: PathBuf,
}
fn config_exists_message(path: &Path) -> String {
format!(
"IcyDB config already exists at '{}'; pass --force to replace it",
path.display()
)
}
fn render_default_config(args: &ConfigInitArgs) -> String {
let update = args.update_config_value();
format!(
"\
[canisters.{canister}.sql]
readonly = {readonly}
ddl = {ddl}
fixtures = {fixtures}
update = {update}
[canisters.{canister}.metrics]
enabled = {metrics}
extended = {metrics_extended}
[canisters.{canister}.snapshot]
enabled = {snapshot}
[canisters.{canister}.schema]
enabled = {schema}
",
canister = args.canister_name(),
readonly = args.readonly(),
ddl = args.ddl(),
fixtures = args.fixtures(),
update = update,
metrics = args.metrics(),
metrics_extended = args.metrics_extended(),
snapshot = args.snapshot(),
schema = args.schema(),
)
}