uconsole_sleep/
args.rs

1use std::path::PathBuf;
2
3/// Parse CLI args for a minimal set: --dry-run, --toggle-wifi, --config <path>
4fn parse_cli_args_from<I: IntoIterator<Item = String>>(
5    args: I,
6) -> (bool, u8, Option<bool>, Option<bool>, Option<PathBuf>) {
7    let mut dry_run = false;
8    let mut verbosity: u8 = 0;
9    let mut config_path: Option<PathBuf> = None;
10    let mut toggle_wifi: Option<bool> = None;
11    let mut toggle_bt: Option<bool> = None;
12    let mut iter = args.into_iter();
13    while let Some(a) = iter.next() {
14        match a.as_str() {
15            "--dry-run" => dry_run = true,
16            s if s.starts_with("-v") && s.chars().skip(1).all(|c| c == 'v') => {
17                let count = s.chars().skip(1).count();
18                // bound verbosity at 3
19                verbosity = std::cmp::min(3, count as u8);
20            }
21            "--verbose" => {
22                // map --verbose to single -v
23                verbosity = std::cmp::max(verbosity, 1);
24            }
25            s if s.starts_with("--toggle-wifi") => {
26                if s == "--toggle-wifi" {
27                    toggle_wifi = Some(true);
28                } else if let Some(eq) = s.find('=') {
29                    let val = s[eq + 1..].to_ascii_lowercase();
30                    toggle_wifi = Some(val == "true" || val == "1" || val == "yes");
31                }
32            }
33            s if s.starts_with("--toggle-bt") => {
34                if s == "--toggle-bt" {
35                    toggle_bt = Some(true);
36                } else if let Some(eq) = s.find('=') {
37                    let val = s[eq + 1..].to_ascii_lowercase();
38                    toggle_bt = Some(val == "true" || val == "1" || val == "yes");
39                }
40            }
41            s if s.starts_with("--config") => {
42                if s == "--config" {
43                    if let Some(p) = iter.next() {
44                        config_path = Some(PathBuf::from(p));
45                    }
46                } else if let Some(eq) = s.find('=') {
47                    let p = &s[eq + 1..];
48                    if !p.is_empty() {
49                        config_path = Some(PathBuf::from(p));
50                    }
51                }
52            }
53            _ => {}
54        }
55    }
56    (dry_run, verbosity, toggle_wifi, toggle_bt, config_path)
57}
58
59fn print_help() {
60    // Basic usage/help text
61    println!("uconsole-sleep {}\n", env!("CARGO_PKG_VERSION"));
62    println!("Usage: uconsole-sleep [OPTIONS]");
63    println!("\nOptions:");
64    println!("  --config[=PATH]      Load configuration from PATH");
65    println!("  --toggle-wifi[=VAL]  Toggle WiFi; VAL can be true/false/1/0/yes/no");
66    println!("  --toggle-bt[=VAL]    Toggle Bluetooth; VAL can be true/false/1/0/yes/no");
67    println!("  --dry-run            Don't actually perform changes; just log actions");
68    println!();
69    println!("  -v, -vv, -vvv        Increase verbosity (max 3)");
70    println!("  --verbose            Same as -v");
71    println!("  -h, --help           Print this help message and exit");
72}
73
74pub fn parse_cli_args() -> (bool, u8, Option<bool>, Option<bool>, Option<PathBuf>) {
75    // If the user asked for help, print and exit; keep parse_cli_args_from unchanged so unit
76    // tests that call it directly are unaffected.
77    for a in std::env::args() {
78        if a == "-h" || a == "--help" {
79            print_help();
80            std::process::exit(0);
81        }
82    }
83
84    parse_cli_args_from(std::env::args())
85}
86
87#[cfg(test)]
88mod tests {
89    use crate::Config;
90
91    use super::*;
92
93    #[test]
94    fn test_parse_cli_args_from_flags() {
95        let tmp = std::env::temp_dir().join(format!(
96            "uconsole_cli_cfg_{}",
97            std::time::SystemTime::now()
98                .duration_since(std::time::UNIX_EPOCH)
99                .unwrap()
100                .as_millis()
101        ));
102        let _ = std::fs::create_dir_all(&tmp);
103        let cfg_path = tmp.join("cli_cfg");
104        std::fs::write(&cfg_path, "SAVING_CPU_FREQ=55,66\nHOLD_TRIGGER_SEC=1.4\n").unwrap();
105
106        let args = vec![
107            String::from("prog"),
108            String::from("--dry-run"),
109            String::from("--config"),
110            cfg_path.to_string_lossy().to_string(),
111        ];
112        let (dry_run, verbosity, _toggle_wifi, _toggle_bt, cli_config_path) =
113            parse_cli_args_from(args);
114        assert!(dry_run);
115        assert_eq!(verbosity, 0);
116        assert_eq!(cli_config_path, Some(cfg_path.clone()));
117
118        // ensure the Config::load uses this file when provided
119        let loaded = Config::load(cli_config_path.clone());
120        assert_eq!(loaded.saving_cpu_freq.unwrap(), "55,66");
121        assert_eq!(loaded.hold_trigger_sec.unwrap(), 1.4_f32);
122        // no-op; this used to check default examples
123    }
124
125    #[test]
126    fn test_parse_cli_args_from_flags_eq_form() {
127        let tmp = std::env::temp_dir().join(format!(
128            "uconsole_cli_cfg_{}",
129            std::time::SystemTime::now()
130                .duration_since(std::time::UNIX_EPOCH)
131                .unwrap()
132                .as_millis()
133        ));
134        let _ = std::fs::create_dir_all(&tmp);
135        let cfg_path = tmp.join("cli_cfg2");
136        std::fs::write(&cfg_path, "SAVING_CPU_FREQ=22,33\nHOLD_TRIGGER_SEC=2.1\n").unwrap();
137
138        let args = vec![
139            String::from("prog"),
140            String::from("--dry-run"),
141            format!("--config={}", cfg_path.to_string_lossy()),
142        ];
143        let (dry_run, verbosity, _toggle_wifi, _toggle_bt, cli_config_path) =
144            parse_cli_args_from(args);
145        assert!(dry_run);
146        assert_eq!(verbosity, 0);
147        assert_eq!(cli_config_path, Some(cfg_path.clone()));
148        let loaded = Config::load(cli_config_path.clone());
149        assert_eq!(loaded.saving_cpu_freq.unwrap(), "22,33");
150        assert_eq!(loaded.hold_trigger_sec.unwrap(), 2.1_f32);
151    }
152
153    #[test]
154    fn test_toggle_wifi_cli_precedence_over_config() {
155        use crate::Config;
156        let cfg = Config {
157            toggle_wifi: true,
158            ..Default::default()
159        };
160        let toggle_wifi_flag = Some(false);
161        let final_toggle_wifi = match toggle_wifi_flag {
162            Some(v) => v,
163            None => cfg.toggle_wifi,
164        };
165        assert!(!final_toggle_wifi);
166    }
167
168    #[test]
169    fn test_parse_cli_args_verbosity_v() {
170        let args = vec![String::from("prog"), String::from("-v")];
171        let (_dry_run, verbosity, _toggle_wifi, _toggle_bt, _cli_config_path) =
172            parse_cli_args_from(args);
173        assert_eq!(verbosity, 1);
174    }
175
176    #[test]
177    fn test_parse_cli_args_verbosity_vv() {
178        let args = vec![String::from("prog"), String::from("-vv")];
179        let (_dry_run, verbosity, _toggle_wifi, _toggle_bt, _cli_config_path) =
180            parse_cli_args_from(args);
181        assert_eq!(verbosity, 2);
182    }
183
184    #[test]
185    fn test_parse_cli_args_verbosity_vvv() {
186        let args = vec![String::from("prog"), String::from("-vvv")];
187        let (_dry_run, verbosity, _toggle_wifi, _toggle_bt, _cli_config_path) =
188            parse_cli_args_from(args);
189        assert_eq!(verbosity, 3);
190    }
191}