use super::{
artifacts::RetainedAttemptCount,
error::{ElenchusError, Result},
};
use std::env;
const DEFAULT_MAX_LINES: u64 = 2_000;
#[derive(Clone, Debug)]
pub(super) struct Settings {
pub(super) codex_bin: String,
pub(super) review_model: String,
pub(super) effort: String,
pub(super) max_lines: u64,
pub(super) test_cmd: String,
pub(super) risk_accepted_token: Option<String>,
pub(super) test_policy: TestPolicy,
pub(super) transcript_policy: TranscriptPolicy,
pub(super) main_branch_policy: MainBranchPolicy,
pub(super) commit_policy: CommitPolicy,
pub(super) retained_attempts: RetainedAttemptCount,
}
impl Settings {
pub(super) fn from_env() -> Result<Self> {
Ok(Self {
codex_bin: env_value("ELENCHUS_CODEX_BIN").unwrap_or_else(|| String::from("codex")),
review_model: env_value("ELENCHUS_REVIEW_MODEL")
.unwrap_or_else(|| String::from("gpt-5.5")),
effort: env_value("ELENCHUS_EFFORT").unwrap_or_else(|| String::from("high")),
max_lines: env_value("ELENCHUS_MAX_LINES")
.map(|value| {
value.parse::<u64>().map_err(|error| {
ElenchusError::usage(format!(
"error: ELENCHUS_MAX_LINES must be an integer: {error}"
))
})
})
.transpose()?
.unwrap_or(DEFAULT_MAX_LINES),
test_cmd: env_value("ELENCHUS_TEST_CMD")
.unwrap_or_else(|| String::from("cargo test --workspace --all-features")),
risk_accepted_token: env_value("ELENCHUS_RISK_ACCEPTED")
.filter(|value| !value.is_empty()),
test_policy: TestPolicy::from_skip_flag(env_flag("ELENCHUS_SKIP_TESTS")),
transcript_policy: TranscriptPolicy::from_keep_flag(env_flag(
"ELENCHUS_KEEP_REVIEW_TRANSCRIPT",
)),
main_branch_policy: MainBranchPolicy::from_allow_flag(env_flag("ELENCHUS_ALLOW_MAIN")),
commit_policy: CommitPolicy::from_dry_run_flag(env_flag("ELENCHUS_DRY_RUN")),
retained_attempts: env_value("ELENCHUS_RETAIN_ATTEMPTS")
.map(|value| {
let count = value.parse::<usize>().map_err(|error| {
ElenchusError::usage(format!(
"error: ELENCHUS_RETAIN_ATTEMPTS must be a positive integer: {error}"
))
})?;
RetainedAttemptCount::new(count).ok_or_else(|| {
ElenchusError::usage(
"error: ELENCHUS_RETAIN_ATTEMPTS must be greater than zero",
)
})
})
.transpose()?
.unwrap_or_else(RetainedAttemptCount::default_count),
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum TestPolicy {
Run,
Skip,
}
impl TestPolicy {
const fn from_skip_flag(skip: bool) -> Self {
if skip { Self::Skip } else { Self::Run }
}
pub(super) const fn skips_tests(self) -> bool {
matches!(self, Self::Skip)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum TranscriptPolicy {
KeepRaw,
DiscardRaw,
}
impl TranscriptPolicy {
const fn from_keep_flag(keep: bool) -> Self {
if keep {
Self::KeepRaw
} else {
Self::DiscardRaw
}
}
pub(super) const fn keeps_raw(self) -> bool {
matches!(self, Self::KeepRaw)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum MainBranchPolicy {
Refuse,
Allow,
}
impl MainBranchPolicy {
const fn from_allow_flag(allow: bool) -> Self {
if allow { Self::Allow } else { Self::Refuse }
}
pub(super) const fn allows_main(self) -> bool {
matches!(self, Self::Allow)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum CommitPolicy {
Commit,
DryRun,
}
impl CommitPolicy {
const fn from_dry_run_flag(dry_run: bool) -> Self {
if dry_run { Self::DryRun } else { Self::Commit }
}
pub(super) const fn is_dry_run(self) -> bool {
matches!(self, Self::DryRun)
}
}
fn env_value(name: &str) -> Option<String> {
env::var(name).ok()
}
fn env_flag(name: &str) -> bool {
env::var(name).is_ok_and(|value| value == "1")
}