mod accessors;
mod merge;
pub(crate) mod mutation;
mod path;
mod persistence;
mod resolved;
mod schema;
mod sections;
#[cfg(test)]
mod tests;
use config::{Case, Config, ConfigError, File};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use merge::Merge;
pub use path::{
config_path, default_config_path, default_system_config_path, set_config_path,
system_config_path,
};
pub use resolved::ResolvedConfig;
pub use schema::{find_unknown_keys, valid_user_config_keys};
pub use sections::{
CommitConfig, CommitGenerationConfig, CopyIgnoredConfig, ListConfig, MergeConfig,
OverridableConfig, StageMode, StepConfig, SwitchConfig, SwitchPickerConfig,
UserProjectOverrides,
};
#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct UserConfig {
#[serde(default)]
pub projects: std::collections::BTreeMap<String, UserProjectOverrides>,
#[serde(flatten, default)]
pub configs: OverridableConfig,
#[serde(
default,
rename = "skip-shell-integration-prompt",
skip_serializing_if = "std::ops::Not::not"
)]
pub skip_shell_integration_prompt: bool,
#[serde(
default,
rename = "skip-commit-generation-prompt",
skip_serializing_if = "std::ops::Not::not"
)]
pub skip_commit_generation_prompt: bool,
}
impl UserConfig {
pub fn load() -> Result<Self, ConfigError> {
let mut builder = Config::builder();
if let Some(system_path) = path::system_config_path()
&& let Ok(content) = std::fs::read_to_string(&system_path)
{
super::deprecation::warn_unknown_fields::<UserConfig>(
&system_path,
&find_unknown_keys(&content),
"System config",
);
let migrated = super::deprecation::migrate_content(&content);
builder = builder.add_source(File::from_str(&migrated, config::FileFormat::Toml));
}
let config_path = config_path();
if let Some(config_path) = config_path.as_ref()
&& config_path.exists()
{
if let Ok(content) = std::fs::read_to_string(config_path) {
let migrated = super::deprecation::check_and_migrate(
config_path,
&content,
true,
"User config",
None,
true, )
.map(|result| result.migrated_content)
.unwrap_or_else(|_| super::deprecation::migrate_content(&content));
super::deprecation::warn_unknown_fields::<UserConfig>(
config_path,
&find_unknown_keys(&content),
"User config",
);
builder = builder.add_source(File::from_str(&migrated, config::FileFormat::Toml));
}
} else if let Some(config_path) = config_path.as_ref()
&& path::is_config_path_explicit()
{
crate::styling::eprintln!(
"{}",
crate::styling::warning_message(format!(
"Config file not found: {}",
crate::path::format_path_for_display(config_path)
))
);
}
builder = builder.add_source(
config::Environment::with_prefix("WORKTRUNK")
.prefix_separator("_")
.separator("__")
.convert_case(Case::Kebab),
);
let config: Self = builder.build()?.try_deserialize()?;
config.validate()?;
Ok(config)
}
#[cfg(test)]
pub(crate) fn load_from_str(content: &str) -> Result<Self, ConfigError> {
let migrated = crate::config::deprecation::migrate_content(content);
let config: Self =
toml::from_str(&migrated).map_err(|e| ConfigError::Message(e.to_string()))?;
config.validate()?;
Ok(config)
}
}