Skip to main content

agentic_tools_utils/
cli.rs

1//! CLI and environment parsing helpers.
2//!
3//! This module provides utilities for parsing environment variables
4//! and CLI inputs in consistent ways.
5
6use std::collections::BTreeSet;
7
8/// Parse a comma/whitespace separated string into a lowercase-trimmed set.
9///
10/// # Example
11///
12/// ```
13/// use agentic_tools_utils::cli::parse_comma_set;
14///
15/// let set = parse_comma_set("foo, BAR, baz");
16/// assert!(set.contains("foo"));
17/// assert!(set.contains("bar"));
18/// assert!(set.contains("baz"));
19/// assert_eq!(set.len(), 3);
20/// ```
21pub fn parse_comma_set(input: &str) -> BTreeSet<String> {
22    input
23        .split(|c: char| c == ',' || c.is_whitespace())
24        .filter(|s| !s.trim().is_empty())
25        .map(|s| s.trim().to_lowercase())
26        .collect()
27}
28
29/// Read a boolean from an environment variable.
30///
31/// Accepted truthy values: `1`, `true`, `yes`, `on`
32/// Accepted falsy values: `0`, `false`, `no`, `off`
33/// Any other value or unset variable returns the default.
34///
35/// # Example
36///
37/// ```
38/// use agentic_tools_utils::cli::bool_from_env;
39///
40/// // Returns default when var is not set
41/// let value = bool_from_env("NONEXISTENT_VAR_12345", true);
42/// assert!(value);
43/// ```
44pub fn bool_from_env(var: &str, default: bool) -> bool {
45    match std::env::var(var) {
46        Ok(v) => match v.trim().to_ascii_lowercase().as_str() {
47            "1" | "true" | "yes" | "on" => true,
48            "0" | "false" | "no" | "off" => false,
49            _ => default,
50        },
51        Err(_) => default,
52    }
53}
54
55/// Read a usize from an environment variable.
56///
57/// Returns the default if the variable is not set or cannot be parsed.
58///
59/// # Example
60///
61/// ```
62/// use agentic_tools_utils::cli::usize_from_env;
63///
64/// // Returns default when var is not set
65/// let value = usize_from_env("NONEXISTENT_VAR_12345", 100);
66/// assert_eq!(value, 100);
67/// ```
68pub fn usize_from_env(var: &str, default: usize) -> usize {
69    std::env::var(var)
70        .ok()
71        .and_then(|v| v.trim().parse::<usize>().ok())
72        .unwrap_or(default)
73}
74
75/// Return `Option<BTreeSet>` if the environment variable is present and non-empty.
76///
77/// # Example
78///
79/// ```
80/// use agentic_tools_utils::cli::set_from_env;
81///
82/// // Returns None when var is not set
83/// let value = set_from_env("NONEXISTENT_VAR_12345");
84/// assert!(value.is_none());
85/// ```
86pub fn set_from_env(var: &str) -> Option<BTreeSet<String>> {
87    std::env::var(var)
88        .ok()
89        .map(|s| parse_comma_set(&s))
90        .filter(|s| !s.is_empty())
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn parse_comma_set_basic() {
99        let set = parse_comma_set("foo,bar,baz");
100        assert_eq!(set.len(), 3);
101        assert!(set.contains("foo"));
102        assert!(set.contains("bar"));
103        assert!(set.contains("baz"));
104    }
105
106    #[test]
107    fn parse_comma_set_with_spaces() {
108        let set = parse_comma_set("foo, bar , baz");
109        assert_eq!(set.len(), 3);
110        assert!(set.contains("foo"));
111        assert!(set.contains("bar"));
112        assert!(set.contains("baz"));
113    }
114
115    #[test]
116    fn parse_comma_set_whitespace_separated() {
117        let set = parse_comma_set("foo bar baz");
118        assert_eq!(set.len(), 3);
119    }
120
121    #[test]
122    fn parse_comma_set_mixed_separators() {
123        let set = parse_comma_set("foo, bar baz");
124        assert_eq!(set.len(), 3);
125    }
126
127    #[test]
128    fn parse_comma_set_lowercases() {
129        let set = parse_comma_set("FOO, Bar, BAZ");
130        assert!(set.contains("foo"));
131        assert!(set.contains("bar"));
132        assert!(set.contains("baz"));
133        assert!(!set.contains("FOO"));
134    }
135
136    #[test]
137    fn parse_comma_set_empty() {
138        let set = parse_comma_set("");
139        assert!(set.is_empty());
140    }
141
142    #[test]
143    fn parse_comma_set_only_separators() {
144        let set = parse_comma_set(", , , ");
145        assert!(set.is_empty());
146    }
147
148    #[test]
149    fn parse_comma_set_duplicates_deduplicated() {
150        let set = parse_comma_set("foo, FOO, Foo");
151        assert_eq!(set.len(), 1);
152        assert!(set.contains("foo"));
153    }
154
155    #[test]
156    fn bool_from_env_returns_default_when_unset() {
157        // Use a var name that definitely doesn't exist
158        let result = bool_from_env("__AGENTIC_TEST_NONEXISTENT_VAR__", true);
159        assert!(result);
160
161        let result = bool_from_env("__AGENTIC_TEST_NONEXISTENT_VAR__", false);
162        assert!(!result);
163    }
164
165    #[test]
166    fn usize_from_env_returns_default_when_unset() {
167        let result = usize_from_env("__AGENTIC_TEST_NONEXISTENT_VAR__", 42);
168        assert_eq!(result, 42);
169    }
170
171    #[test]
172    fn set_from_env_returns_none_when_unset() {
173        let result = set_from_env("__AGENTIC_TEST_NONEXISTENT_VAR__");
174        assert!(result.is_none());
175    }
176}