use super::*;
pub(crate) fn render_config(plan: &InitPlan) -> String {
let mut text = String::new();
text.push_str(
"# rag-rat configuration — generated by `rag-rat init`; edit freely.\n# Active lines \
below reflect this repo; commented tables show the tunable defaults.\n# Full reference: \
docs/config.md.\n\n",
);
text.push_str("[index]\n");
text.push_str(&format!("root = {}\n", toml_string(&plan.root_value)));
text.push_str(&format!("database = {}\n\n", toml_string(DEFAULT_DATABASE)));
text.push_str(
"# Language → directories to index. Each language indexes its default file extensions in \
the\n# listed dirs; a `cpp` binding also claims `.h` headers (indexed as C++), a `c` \
binding\n# claims `.c` + `.h`. Add or remove languages and directories as your layout \
needs.\n",
);
text.push_str("[target_bindings]\n");
for language in &plan.languages {
let dirs = plan.bindings.get(language).cloned().unwrap_or_default();
text.push_str(&format!("{} = [{}]\n", language.as_str(), quoted_paths(&dirs)));
}
text.push('\n');
text.push_str(
"# Richer targets (uncomment to use): a named target with an explicit kind and custom \
file\n# globs — the only way to bind generated / test / docs trees or a non-default file \
set.\n# `kind` is source|generated|docs|tests; `include`/`exclude` are `**/*.<ext>` \
globs.\n# [[target]]\n# name = \"generated-bindings\"\n# language = \"typescript\"\n# \
directories = [\"packages/app/src/generated\"]\n# kind = \"generated\"\n# include = \
[\"**/*.ts\"]\n# exclude = [\"**/*.map\"]\n\n",
);
text.push_str("[local_ai.embedding]\n");
text.push_str(&format!("# {}\n", backend_label(plan.backend)));
text.push_str(&format!("model = {}\n\n", toml_string(plan.backend.as_str())));
text.push_str(
"# Embedding runtime tuning (defaults shown; uncomment to override).\n# \
[local_ai.embedding.runtime]\n# batch_size = 64\n# ort_threads = 4 # ONNX \
intra-op threads\n# omp_threads = 1\n# max_embedding_chars = 4000\n\n",
);
text.push_str(
"# Background file watcher — keeps the index fresh as files change (defaults shown).\n# \
[watch]\n# enabled = true\n# debounce_ms = 400 # quiet window before a reindex \
pass\n# max_latency_ms = 2500 # force a pass after this much continuous \
activity\n# periodic_sweep_secs = 300 # backstop pass interval, 0 disables\n\n",
);
text.push_str(
"# Check crates.io for a newer rag-rat and surface it. Best-effort, cached, never \
blocks.\n# [version_check]\n# enabled = true\n\n",
);
text.push_str("[oracle]\n");
text.push_str(
"# Background auto-refresh of compiler-grade (SCIP) importance ranking. Needs a \
language\n# tool on PATH (e.g. rust-analyzer); runs throttled in the MCP server only. \
Default off.\n",
);
text.push_str(&format!("auto_run = {}\n", plan.oracle_auto_run));
text.push_str(
"# auto_run_quiet_period_secs = 900 # run only after the index has been this quiet\n# \
auto_run_min_interval_secs = 21600 # and at most this often, regardless of churn\n",
);
text
}
pub(crate) fn quoted_paths(paths: &[PathBuf]) -> String {
paths.iter().map(|path| toml_string(&display_rel(path))).collect::<Vec<_>>().join(", ")
}
pub(crate) fn config_root_value(root: &Path, config_path: &Path) -> String {
let Some(parent) = config_path.parent().filter(|path| !path.as_os_str().is_empty()) else {
return ".".to_string();
};
if config_path.is_absolute() {
absolute_config_root_value(root, parent)
} else {
relative_config_root_value(parent)
}
}
pub(crate) fn absolute_config_root_value(root: &Path, parent: &Path) -> String {
if let Ok(relative_parent) = parent.strip_prefix(root) {
return relative_config_root_value(relative_parent);
}
root.display().to_string()
}
pub(crate) fn relative_config_root_value(parent: &Path) -> String {
let depth = parent.components().filter(normal_component).count();
if depth == 0 {
".".to_string()
} else {
std::iter::repeat_n("..", depth).collect::<Vec<_>>().join("/")
}
}
pub(crate) fn normal_component(component: &std::path::Component<'_>) -> bool {
matches!(component, std::path::Component::Normal(_))
}
pub(crate) fn toml_string(value: &str) -> String {
format!("{value:?}")
}
pub(crate) fn display_rel(path: &Path) -> String {
let text = path.to_string_lossy().replace('\\', "/");
if text.is_empty() { ".".to_string() } else { text }
}
pub(crate) fn supported_languages() -> Vec<Language> {
Language::all().to_vec()
}