use std::path::{Path, PathBuf};
use crate::error::RippyError;
use crate::verdict::Decision;
use super::Config;
use super::parser::{parse_action_word, parse_rule};
use super::types::{ConfigDirective, Rule};
pub(super) fn apply_setting(config: &mut Config, key: &str, value: &str) {
match key {
"default" => config.default_action = parse_action_word(value),
"log" => config.log_file = Some(PathBuf::from(value)),
"log-full" => config.log_full = true,
"tracking" => {
config.tracking_db = Some(if value == "on" || value.is_empty() {
home_dir().map_or_else(
|| PathBuf::from(".rippy/tracking.db"),
|h| h.join(".rippy/tracking.db"),
)
} else {
PathBuf::from(value)
});
}
"trust-project-configs" => {
config.trust_project_configs = value != "off" && value != "false";
}
"self-protect" => {
config.self_protect = value != "off";
}
_ => {}
}
}
pub(super) fn detect_dangerous_setting(key: &str, value: &str, notes: &mut Vec<String>) {
if key == "default" && value == "allow" {
notes.push("sets default action to allow (all unknown commands auto-approved)".to_string());
}
if key == "self-protect" && value == "off" {
notes.push("disables self-protection (AI tools can modify rippy config)".to_string());
}
}
pub(super) fn detect_broad_allow(rule: &Rule, notes: &mut Vec<String>) {
if rule.decision != Decision::Allow {
return;
}
let raw = rule.pattern.raw();
if raw == "*" || raw == "**" || raw == "*|" {
notes.push(format!("allows all commands with pattern \"{raw}\""));
}
}
pub(super) fn build_weakening_suffix(notes: &[String]) -> String {
if notes.is_empty() {
return String::new();
}
let mut suffix = String::from(" | NOTE: project config ");
for (i, note) in notes.iter().enumerate() {
if i > 0 {
suffix.push_str(", ");
}
suffix.push_str(note);
}
suffix
}
pub(super) fn load_first_existing(
paths: &[PathBuf],
directives: &mut Vec<ConfigDirective>,
) -> Result<(), RippyError> {
for path in paths {
if path.is_file() {
return load_file(path, directives);
}
}
Ok(())
}
pub fn load_file(path: &Path, directives: &mut Vec<ConfigDirective>) -> Result<(), RippyError> {
let content = std::fs::read_to_string(path).map_err(|e| RippyError::Config {
path: path.to_owned(),
line: 0,
message: format!("could not read: {e}"),
})?;
load_file_from_content(&content, path, directives)
}
pub(super) fn load_file_from_content(
content: &str,
path: &Path,
directives: &mut Vec<ConfigDirective>,
) -> Result<(), RippyError> {
if path.extension().is_some_and(|ext| ext == "toml") {
let parsed = crate::toml_config::parse_toml_config(content, path)?;
directives.extend(parsed);
return Ok(());
}
for (line_num, line) in content.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let directive = parse_rule(line).map_err(|msg| RippyError::Config {
path: path.to_owned(),
line: line_num + 1,
message: msg,
})?;
directives.push(directive);
}
Ok(())
}
pub(super) fn has_trust_setting(directives: &[ConfigDirective]) -> bool {
directives.iter().rev().any(|d| {
matches!(
d,
ConfigDirective::Set { key, value }
if key == "trust-project-configs"
&& value != "off"
&& value != "false"
)
})
}
pub(super) fn load_project_config_if_trusted(
path: &Path,
trust_all: bool,
directives: &mut Vec<ConfigDirective>,
) -> Result<(), RippyError> {
let content = std::fs::read_to_string(path).map_err(|e| RippyError::Config {
path: path.to_owned(),
line: 0,
message: format!("could not read: {e}"),
})?;
if trust_all {
return load_file_from_content(&content, path, directives);
}
let db = crate::trust::TrustDb::load();
match db.check(path, &content) {
crate::trust::TrustStatus::Trusted => load_file_from_content(&content, path, directives),
crate::trust::TrustStatus::Untrusted => {
eprintln!(
"[rippy] untrusted project config: {} — run `rippy trust` to review and enable",
path.display()
);
Ok(())
}
crate::trust::TrustStatus::Modified { .. } => {
eprintln!(
"[rippy] project config modified since last trust: {} — \
run `rippy trust` to re-approve",
path.display()
);
Ok(())
}
}
}
pub fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME").map(PathBuf::from)
}
pub(super) fn extract_package_setting(path: &Path) -> Option<String> {
let content = std::fs::read_to_string(path).ok()?;
if path.extension().is_some_and(|ext| ext == "toml") {
let config: crate::toml_config::TomlConfig = toml::from_str(&content).ok()?;
config.settings?.package
} else {
for line in content.lines() {
let line = line.trim();
if let Some(rest) = line.strip_prefix("set package ") {
let value = rest.trim().trim_matches('"');
if !value.is_empty() {
return Some(value.to_string());
}
}
}
None
}
}