1use std::path::PathBuf;
2
3fn 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 verbosity = std::cmp::min(3, count as u8);
20 }
21 "--verbose" => {
22 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 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 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 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 }
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}