use std::path::PathBuf;
use crate::cli;
use clap::ValueEnum;
use tokmd_settings::{Profile, TomlConfig, UserConfig, ViewProfile};
#[derive(Debug, Default)]
pub struct ConfigContext {
pub toml: Option<TomlConfig>,
pub toml_path: Option<PathBuf>,
pub json: Option<UserConfig>,
}
impl ConfigContext {
pub fn get_toml_view(&self, name: &str) -> Option<&ViewProfile> {
self.toml.as_ref().and_then(|t| t.view.get(name))
}
pub fn get_json_profile(&self, name: &str) -> Option<&Profile> {
self.json.as_ref().and_then(|c| c.profiles.get(name))
}
}
pub fn load_config() -> ConfigContext {
let toml_result = discover_toml_config();
let json = load_json_config();
ConfigContext {
toml: toml_result.as_ref().map(|(config, _)| config.clone()),
toml_path: toml_result.map(|(_, path)| path),
json,
}
}
fn discover_toml_config() -> Option<(TomlConfig, PathBuf)> {
if let Ok(config_path) = std::env::var("TOKMD_CONFIG") {
let path = PathBuf::from(&config_path);
if let Some(result) = try_load_toml(&path) {
return Some(result);
}
}
if let Ok(cwd) = std::env::current_dir() {
let mut dir = Some(cwd.as_path());
while let Some(d) = dir {
let config_path = d.join("tokmd.toml");
if let Some(result) = try_load_toml(&config_path) {
return Some(result);
}
dir = d.parent();
}
}
if let Some(config_dir) = dirs::config_dir() {
let user_config_path = config_dir.join("tokmd").join("tokmd.toml");
if let Some(result) = try_load_toml(&user_config_path) {
return Some(result);
}
}
None
}
fn try_load_toml(path: &std::path::Path) -> Option<(TomlConfig, PathBuf)> {
if path.exists() {
TomlConfig::from_file(path)
.ok()
.map(|config| (config, path.to_path_buf()))
} else {
None
}
}
fn load_json_config() -> Option<UserConfig> {
let config_dir = dirs::config_dir()?.join("tokmd");
let config_path = config_dir.join("config.json");
if config_path.exists() {
let content = std::fs::read_to_string(&config_path).ok()?;
serde_json::from_str(&content).ok()
} else {
None
}
}
pub fn get_profile_name(cli_profile: Option<&String>) -> Option<String> {
if let Some(name) = cli_profile {
return Some(name.clone());
}
std::env::var("TOKMD_PROFILE")
.ok()
.filter(|s| !s.is_empty())
}
pub fn resolve_profile<'a>(
config: &'a Option<UserConfig>,
name: Option<&String>,
) -> Option<&'a Profile> {
config.as_ref().and_then(|c| {
let key = name.map(|s| s.as_str()).unwrap_or("default");
c.profiles.get(key)
})
}
#[derive(Debug, Default)]
pub struct ResolvedConfig<'a> {
pub toml_view: Option<&'a ViewProfile>,
pub json_profile: Option<&'a Profile>,
pub toml: Option<&'a TomlConfig>,
}
impl ResolvedConfig<'_> {
pub fn format(&self) -> Option<&str> {
self.toml_view
.and_then(|v| v.format.as_deref())
.or_else(|| self.json_profile.and_then(|p| p.format.as_deref()))
}
pub fn top(&self) -> Option<usize> {
self.toml_view
.and_then(|v| v.top)
.or_else(|| self.json_profile.and_then(|p| p.top))
}
pub fn files(&self) -> Option<bool> {
self.toml_view
.and_then(|v| v.files)
.or_else(|| self.json_profile.and_then(|p| p.files))
}
pub fn module_roots(&self) -> Option<Vec<String>> {
self.toml_view
.and_then(|v| v.module_roots.clone())
.or_else(|| self.toml.and_then(|t| t.module.roots.clone()))
.or_else(|| self.json_profile.and_then(|p| p.module_roots.clone()))
}
pub fn module_depth(&self) -> Option<usize> {
self.toml_view
.and_then(|v| v.module_depth)
.or_else(|| self.toml.and_then(|t| t.module.depth))
.or_else(|| self.json_profile.and_then(|p| p.module_depth))
}
pub fn children(&self) -> Option<&str> {
self.toml_view
.and_then(|v| v.children.as_deref())
.or_else(|| self.toml.and_then(|t| t.module.children.as_deref()))
.or_else(|| self.json_profile.and_then(|p| p.children.as_deref()))
}
pub fn min_code(&self) -> Option<usize> {
self.toml_view
.and_then(|v| v.min_code)
.or_else(|| self.toml.and_then(|t| t.export.min_code))
.or_else(|| self.json_profile.and_then(|p| p.min_code))
}
pub fn max_rows(&self) -> Option<usize> {
self.toml_view
.and_then(|v| v.max_rows)
.or_else(|| self.toml.and_then(|t| t.export.max_rows))
.or_else(|| self.json_profile.and_then(|p| p.max_rows))
}
pub fn redact(&self) -> Option<&str> {
self.toml_view
.and_then(|v| v.redact.as_deref())
.or_else(|| self.toml.and_then(|t| t.export.redact.as_deref()))
}
pub fn meta(&self) -> Option<bool> {
self.toml_view
.and_then(|v| v.meta)
.or_else(|| self.json_profile.and_then(|p| p.meta))
}
}
pub fn resolve_config<'a>(
ctx: &'a ConfigContext,
profile_name: Option<&str>,
) -> ResolvedConfig<'a> {
let toml_view = profile_name.and_then(|name| ctx.get_toml_view(name));
let json_profile = profile_name.and_then(|name| ctx.get_json_profile(name));
ResolvedConfig {
toml_view,
json_profile,
toml: ctx.toml.as_ref(),
}
}
pub fn resolve_lang(
cli_args: &cli::CliLangArgs,
profile: Option<&Profile>,
) -> tokmd_types::LangArgs {
tokmd_types::LangArgs {
paths: cli_args
.paths
.clone()
.unwrap_or_else(|| vec![PathBuf::from(".")]),
format: cli_args
.format
.or_else(|| {
profile
.and_then(|p| p.format.as_deref())
.and_then(|s| cli::TableFormat::from_str(s, true).ok())
})
.unwrap_or(cli::TableFormat::Md),
top: cli_args
.top
.or_else(|| profile.and_then(|p| p.top))
.unwrap_or(0),
files: cli_args.files || profile.and_then(|p| p.files).unwrap_or(false),
children: cli_args
.children
.or_else(|| {
profile
.and_then(|p| p.children.as_deref())
.and_then(|s| cli::ChildrenMode::from_str(s, true).ok())
})
.unwrap_or(cli::ChildrenMode::Collapse),
}
}
pub fn resolve_lang_with_config(
cli_args: &cli::CliLangArgs,
resolved: &ResolvedConfig,
) -> tokmd_types::LangArgs {
tokmd_types::LangArgs {
paths: cli_args
.paths
.clone()
.unwrap_or_else(|| vec![PathBuf::from(".")]),
format: cli_args
.format
.or_else(|| {
resolved
.format()
.and_then(|s| cli::TableFormat::from_str(s, true).ok())
})
.unwrap_or(cli::TableFormat::Md),
top: cli_args.top.or(resolved.top()).unwrap_or(0),
files: cli_args.files || resolved.files().unwrap_or(false),
children: cli_args
.children
.or_else(|| {
resolved
.children()
.and_then(|s| cli::ChildrenMode::from_str(s, true).ok())
})
.unwrap_or(cli::ChildrenMode::Collapse),
}
}
pub fn resolve_module(
cli_args: &cli::CliModuleArgs,
profile: Option<&Profile>,
) -> tokmd_types::ModuleArgs {
tokmd_types::ModuleArgs {
paths: cli_args
.paths
.clone()
.unwrap_or_else(|| vec![PathBuf::from(".")]),
format: cli_args
.format
.or_else(|| {
profile
.and_then(|p| p.format.as_deref())
.and_then(|s| cli::TableFormat::from_str(s, true).ok())
})
.unwrap_or(cli::TableFormat::Md),
top: cli_args
.top
.or_else(|| profile.and_then(|p| p.top))
.unwrap_or(0),
module_roots: cli_args
.module_roots
.clone()
.or_else(|| profile.and_then(|p| p.module_roots.clone()))
.unwrap_or_else(|| vec!["crates".into(), "packages".into()]),
module_depth: cli_args
.module_depth
.or_else(|| profile.and_then(|p| p.module_depth))
.unwrap_or(2),
children: cli_args
.children
.or_else(|| {
profile
.and_then(|p| p.children.as_deref())
.and_then(|s| cli::ChildIncludeMode::from_str(s, true).ok())
})
.unwrap_or(cli::ChildIncludeMode::Separate),
}
}
pub fn resolve_module_with_config(
cli_args: &cli::CliModuleArgs,
resolved: &ResolvedConfig,
) -> tokmd_types::ModuleArgs {
tokmd_types::ModuleArgs {
paths: cli_args
.paths
.clone()
.unwrap_or_else(|| vec![PathBuf::from(".")]),
format: cli_args
.format
.or_else(|| {
resolved
.format()
.and_then(|s| cli::TableFormat::from_str(s, true).ok())
})
.unwrap_or(cli::TableFormat::Md),
top: cli_args.top.or(resolved.top()).unwrap_or(0),
module_roots: cli_args
.module_roots
.clone()
.or(resolved.module_roots())
.unwrap_or_else(|| vec!["crates".into(), "packages".into()]),
module_depth: cli_args
.module_depth
.or(resolved.module_depth())
.unwrap_or(2),
children: cli_args
.children
.or_else(|| {
resolved
.children()
.and_then(|s| cli::ChildIncludeMode::from_str(s, true).ok())
})
.unwrap_or(cli::ChildIncludeMode::Separate),
}
}
pub fn resolve_export(
cli_args: &cli::CliExportArgs,
profile: Option<&Profile>,
) -> tokmd_types::ExportArgs {
tokmd_types::ExportArgs {
paths: cli_args
.paths
.clone()
.unwrap_or_else(|| vec![PathBuf::from(".")]),
format: cli_args
.format
.or_else(|| {
profile
.and_then(|p| p.format.as_deref())
.and_then(|s| cli::ExportFormat::from_str(s, true).ok())
})
.unwrap_or(cli::ExportFormat::Jsonl),
output: cli_args.output.clone(),
module_roots: cli_args
.module_roots
.clone()
.or_else(|| profile.and_then(|p| p.module_roots.clone()))
.unwrap_or_else(|| vec!["crates".into(), "packages".into()]),
module_depth: cli_args
.module_depth
.or_else(|| profile.and_then(|p| p.module_depth))
.unwrap_or(2),
children: cli_args
.children
.or_else(|| {
profile
.and_then(|p| p.children.as_deref())
.and_then(|s| cli::ChildIncludeMode::from_str(s, true).ok())
})
.unwrap_or(cli::ChildIncludeMode::Separate),
min_code: cli_args
.min_code
.or(profile.and_then(|p| p.min_code))
.unwrap_or(0),
max_rows: cli_args
.max_rows
.or(profile.and_then(|p| p.max_rows))
.unwrap_or(0),
redact: cli_args
.redact
.or(profile.and_then(|p| p.redact))
.unwrap_or(cli::RedactMode::None),
meta: cli_args
.meta
.or(profile.and_then(|p| p.meta))
.unwrap_or(true),
strip_prefix: cli_args.strip_prefix.clone(),
}
}
pub fn resolve_export_with_config(
cli_args: &cli::CliExportArgs,
resolved: &ResolvedConfig,
) -> tokmd_types::ExportArgs {
tokmd_types::ExportArgs {
paths: cli_args
.paths
.clone()
.unwrap_or_else(|| vec![PathBuf::from(".")]),
format: cli_args
.format
.or_else(|| {
resolved
.format()
.and_then(|s| cli::ExportFormat::from_str(s, true).ok())
})
.or_else(|| {
resolved
.toml
.and_then(|t| t.export.format.as_deref())
.and_then(|s| cli::ExportFormat::from_str(s, true).ok())
})
.unwrap_or(cli::ExportFormat::Jsonl),
output: cli_args.output.clone(),
module_roots: cli_args
.module_roots
.clone()
.or(resolved.module_roots())
.unwrap_or_else(|| vec!["crates".into(), "packages".into()]),
module_depth: cli_args
.module_depth
.or(resolved.module_depth())
.unwrap_or(2),
children: cli_args
.children
.or_else(|| {
resolved
.children()
.and_then(|s| cli::ChildIncludeMode::from_str(s, true).ok())
})
.or_else(|| {
resolved
.toml
.and_then(|t| t.export.children.as_deref())
.and_then(|s| cli::ChildIncludeMode::from_str(s, true).ok())
})
.unwrap_or(cli::ChildIncludeMode::Separate),
min_code: cli_args.min_code.or(resolved.min_code()).unwrap_or(0),
max_rows: cli_args.max_rows.or(resolved.max_rows()).unwrap_or(0),
redact: cli_args
.redact
.or_else(|| {
resolved
.redact()
.and_then(|s| cli::RedactMode::from_str(s, true).ok())
})
.unwrap_or(cli::RedactMode::None),
meta: cli_args.meta.or(resolved.meta()).unwrap_or(true),
strip_prefix: cli_args.strip_prefix.clone(),
}
}