use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct HistoryConfig {
pub retention_count: u32,
}
impl Default for HistoryConfig {
fn default() -> Self {
Self {
retention_count: 10,
}
}
}
#[derive(Serialize, Deserialize, Default)]
pub struct TokfProjectConfig {
pub history: Option<TokfHistorySection>,
pub sync: Option<TokfSyncSection>,
pub shims: Option<TokfShimsSection>,
pub output: Option<TokfOutputSection>,
}
#[derive(Serialize, Deserialize)]
pub struct TokfOutputSection {
pub show_indicator: Option<bool>,
}
#[derive(Serialize, Deserialize)]
pub struct TokfHistorySection {
pub retention: Option<u32>,
}
#[derive(Serialize, Deserialize)]
pub struct TokfSyncSection {
pub auto_sync_threshold: Option<u32>,
pub upload_usage_stats: Option<bool>,
}
#[derive(Serialize, Deserialize)]
pub struct TokfShimsSection {
pub enabled: Option<bool>,
}
fn read_indicator(path: &std::path::Path) -> Option<bool> {
let content = std::fs::read_to_string(path).ok()?;
let cfg: TokfProjectConfig = toml::from_str(&content).ok()?;
cfg.output?.show_indicator
}
fn read_retention_from_config(path: &std::path::Path) -> Option<u32> {
let content = std::fs::read_to_string(path).ok()?;
let cfg: TokfProjectConfig = toml::from_str(&content).ok()?;
cfg.history?.retention
}
impl HistoryConfig {
pub fn load(project_root: Option<&std::path::Path>) -> Self {
let global = crate::paths::user_dir().map(|d| d.join("config.toml"));
Self::load_from(project_root, global.as_deref())
}
pub fn load_from(
project_root: Option<&std::path::Path>,
global_config: Option<&std::path::Path>,
) -> Self {
let from_project = project_root
.and_then(|root| read_retention_from_config(&root.join(".tokf").join("config.toml")));
let from_global = global_config.and_then(read_retention_from_config);
let retention_count = from_project.or(from_global).unwrap_or(10);
Self { retention_count }
}
}
#[derive(Debug, Clone)]
pub struct SyncConfig {
pub auto_sync_threshold: u32,
pub upload_usage_stats: Option<bool>,
}
impl Default for SyncConfig {
fn default() -> Self {
Self {
auto_sync_threshold: 100,
upload_usage_stats: None,
}
}
}
impl SyncConfig {
pub fn load(project_root: Option<&std::path::Path>) -> Self {
let global = crate::paths::user_dir().map(|d| d.join("config.toml"));
Self::load_from(project_root, global.as_deref())
}
pub fn load_from(
project_root: Option<&std::path::Path>,
global_config: Option<&std::path::Path>,
) -> Self {
let project_cfg = project_root.and_then(|root| {
let path = root.join(".tokf").join("config.toml");
let content = std::fs::read_to_string(path).ok()?;
toml::from_str::<TokfProjectConfig>(&content).ok()
});
let global_cfg = global_config.and_then(|p| {
let content = std::fs::read_to_string(p).ok()?;
toml::from_str::<TokfProjectConfig>(&content).ok()
});
let threshold_from_project = project_cfg
.as_ref()
.and_then(|c| c.sync.as_ref()?.auto_sync_threshold);
let threshold_from_global = global_cfg
.as_ref()
.and_then(|c| c.sync.as_ref()?.auto_sync_threshold);
let auto_sync_threshold = threshold_from_project
.or(threshold_from_global)
.unwrap_or(100);
let upload_from_project = project_cfg
.as_ref()
.and_then(|c| c.sync.as_ref()?.upload_usage_stats);
let upload_from_global = global_cfg
.as_ref()
.and_then(|c| c.sync.as_ref()?.upload_usage_stats);
let upload_usage_stats = upload_from_project.or(upload_from_global);
Self {
auto_sync_threshold,
upload_usage_stats,
}
}
}
#[derive(Debug, Clone)]
pub struct ShimsConfig {
pub enabled: bool,
}
impl Default for ShimsConfig {
fn default() -> Self {
Self { enabled: true }
}
}
impl ShimsConfig {
pub fn load(_project_root: Option<&std::path::Path>) -> Self {
let global = crate::paths::user_dir().map(|d| d.join("config.toml"));
Self::load_from(global.as_deref())
}
pub fn load_from(global_config: Option<&std::path::Path>) -> Self {
let from_global = global_config.and_then(|p| {
let content = std::fs::read_to_string(p).ok()?;
let cfg: TokfProjectConfig = toml::from_str(&content).ok()?;
cfg.shims?.enabled
});
let enabled = from_global.unwrap_or(true);
Self { enabled }
}
}
pub fn load_project_config(path: &std::path::Path) -> TokfProjectConfig {
std::fs::read_to_string(path)
.ok()
.and_then(|c| toml::from_str(&c).ok())
.unwrap_or_default()
}
pub fn save_project_config(
path: &std::path::Path,
config: &TokfProjectConfig,
) -> anyhow::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = toml::to_string_pretty(config)?;
crate::fs::write_config_file(path, &content)
}
pub fn save_upload_stats(enabled: bool) -> anyhow::Result<()> {
let path = crate::paths::user_dir()
.map(|d| d.join("config.toml"))
.ok_or_else(|| anyhow::anyhow!("cannot determine config directory"))?;
save_upload_stats_to_path(&path, enabled)
}
pub fn save_upload_stats_to_path(path: &std::path::Path, enabled: bool) -> anyhow::Result<()> {
let mut config = load_project_config(path);
let sync = config.sync.get_or_insert(TokfSyncSection {
auto_sync_threshold: None,
upload_usage_stats: None,
});
sync.upload_usage_stats = Some(enabled);
save_project_config(path, &config)
}
pub fn global_config_path() -> Option<std::path::PathBuf> {
crate::paths::user_dir().map(|d| d.join("config.toml"))
}
pub fn local_config_path(project_root: &std::path::Path) -> std::path::PathBuf {
project_root.join(".tokf").join("config.toml")
}
#[derive(Debug, Clone)]
pub struct OutputConfig {
pub show_indicator: bool,
}
impl Default for OutputConfig {
fn default() -> Self {
Self {
show_indicator: true,
}
}
}
impl OutputConfig {
pub fn load(project_root: Option<&std::path::Path>) -> Self {
if let Ok(val) = std::env::var("TOKF_SHOW_INDICATOR")
&& let Ok(b) = val.parse::<bool>()
{
return Self { show_indicator: b };
}
let global = crate::paths::user_dir().map(|d| d.join("config.toml"));
Self::load_from(project_root, global.as_deref())
}
pub fn load_from(
project_root: Option<&std::path::Path>,
global_config: Option<&std::path::Path>,
) -> Self {
let from_project =
project_root.and_then(|root| read_indicator(&root.join(".tokf").join("config.toml")));
let from_global = global_config.and_then(read_indicator);
let show_indicator = from_project.or(from_global).unwrap_or(true);
Self { show_indicator }
}
}
pub fn project_root_for(dir: &std::path::Path) -> std::path::PathBuf {
let mut current = dir.to_path_buf();
loop {
if current.join(".git").exists() || current.join(".tokf").is_dir() {
return current;
}
if !current.pop() {
break;
}
}
dir.to_path_buf()
}
pub fn current_project() -> String {
let cwd = std::env::current_dir().unwrap_or_default();
project_root_for(&cwd).to_string_lossy().into_owned()
}