1use std::collections::HashMap;
2use std::str::SplitAsciiWhitespace;
3use common::logging::LogLevel;
4use common::logging::LogLevel::*;
5use common::fixture::ChannelError::{FineDegreeTooHigh, FineDegreeExists, FineDegreeOutOfRange};
6use common::fixture::FixtureError::InvalidPropertyType;
7use common::fixture::{
8 ChannelIndex, ChannelValue, ChannelParameter, PropertyType, MAX_FINE_DEGREES,
9 FloatChannelValue
10};
11use common::cli_actions::CliAction;
12use crate::cli::cli_executing::execute_cli_action;
13
14
15pub fn run_command(is_kernel: bool, command: String) -> (LogLevel, String) {
22 match parse_cli_string(command) {
23 Ok(command) => execute_cli_action(is_kernel, &command),
24 Err(e) => (UserError, e.to_string())
25 }
26}
27
28fn parse_cli_string(command_string: String) -> Result<CliAction,String> {
39 let mut line_iter = command_string.split_ascii_whitespace();
40 let arg_count = line_iter.clone().count().saturating_sub(1);
41
42 match line_iter.next() {
43 Some("help") => Ok(CliAction::Help),
44
45 Some("new") if arg_count % 2 == 1 && arg_count > 1 => {
46 parse_new_fixture_type(line_iter)
47 }
48
49 Some("new") => {
50 Err("Error: \"new\"-Command needs a name for the new Fixture-Type, and then a list of properties with \
51 their channels.".to_string())
52 }
53
54 Some("add") if arg_count == 3 => {
55 parse_new_fixture(line_iter)
56 }
57
58 Some("add") => {
59 Err("Error: \"add\" needs a name, a fixture-type and a start-channel (including a start-universe) as \
60 arguments".to_string())
61 }
62
63 Some("move") if arg_count == 2 => {
64 parse_move_fixture(line_iter)
65 }
66
67 Some("move") => {
68 Err("Error: \"move\" needs a fixture and a start-channel (including a start-universe) as \
69 arguments".to_string())
70 }
71
72 Some("remove") if arg_count == 1 => {
73 parse_remove_fixture(line_iter)
74 }
75
76 Some("remove") => {
77 Err("Error: \"remove\" needs a fixture as argument".to_string())
78 }
79
80 Some("set") if arg_count == 3 => {
81 parse_set_value(line_iter)
82 }
83
84 Some("set") => {
85 Err("Error: \"set\" needs a fixture, a property, and a value as arguments".to_string())
86 }
87
88 Some("type") if arg_count == 1 => {
89 parse_get_type(line_iter)
90 }
91
92 Some("type") => {
93 Err("Error: \"type\" needs a fixture as argument".to_string())
94 }
95
96 Some("exit") if arg_count <= 1 => {
97 parse_exit(line_iter)
98 },
99
100 Some("exit") => {
101 Err("Error: \"exit\" accepts at most one optional argument ('save' or 'discard').".to_string())
102 },
103
104 Some(command) => {
105 let args = line_iter.collect::<Vec<_>>().join(" ");
106 Ok(CliAction::OtherCommands {
107 command: format!("{} {}",command,args)
108 })
109 }
110
111 _ => Err("Unknown command. Please enter help, to get a list of commands.".to_string())
112 }
113}
114
115fn parse_new_fixture_type(mut args: SplitAsciiWhitespace) -> Result<CliAction,String> {
125 let name = args.next().unwrap().to_string();
126 let mut properties: HashMap<PropertyType, ChannelParameter> = HashMap::new();
127
128 while let Some(property_name) = args.next() {
129 let channel = args.next().unwrap();
131
132 let channel_index = match channel.parse::<ChannelIndex>() {
133 Ok(channel) => channel,
134 Err(_) => {
135 return Err(format!("ErrorPenis: \"{channel}\" is not a valid channel-number"));
136 }
137 };
138
139 if let Some((property_name, suffix)) = property_name.rsplit_once("_f") {
141
142 let fine_degree = if suffix.is_empty() {
143 1
144 } else {
145 match suffix.parse::<u8>() {
146 Ok(fine_degree) => fine_degree,
147 Err(_) => return Err(format!("Error: \"{suffix}\" is not a valid fine degree"))
148 }
149 };
150
151 let property_type = parse_property_type(property_name)?;
152
153 match properties.get_mut(&property_type) {
154 Some(channel_object) => {
155 match channel_object.add_fine(fine_degree.into(), channel_index, 0) {
156 Err(FineDegreeTooHigh(f)) =>
157 return Err(format!("Fine-degree {} is too high, add the lower ones first", f)),
158 Err(FineDegreeExists(f)) =>
159 return Err(format!("Fine-degree {} already exists", f)),
160 Err(FineDegreeOutOfRange(f)) =>
161 return Err(format!("Fine-degree {} out of range, must be lower than {}",
162 f, MAX_FINE_DEGREES)),
163 Ok(()) => {}
164 _ => unreachable!()
165 }
166 }
167
168 None => return Err(format!(
169 "Error: Cannot add fine channel for '{}' because the coarse channel is missing! Add '{}' first.",
170 property_name, property_type)),
171 }
172 } else {
173 let property_type = parse_property_type(property_name)?;
176
177 if !properties.contains_key(&property_type) {
178 properties.insert(property_type, ChannelParameter::new(channel_index, 0));
179 } else {
180 return Err(format!("{property_name} can only have one coarse Channel"))
181 }
182 }
183 }
184 Ok(CliAction::FixtureNew {
185 name,
186 channels: properties,
187 })
188
189}
190
191fn parse_new_fixture(mut args: SplitAsciiWhitespace) -> Result<CliAction,String> {
201 let name = args.next().unwrap().to_string();
202 let fixture_type_name = args.next().unwrap().to_string();
203 let (universe, channel) = parse_universe_and_channel(args)?;
204
205 Ok(CliAction::FixtureAdd {
206 name,
207 fixture_type_name,
208 channel,
209 universe,
210 })
211}
212
213fn parse_move_fixture(mut args: SplitAsciiWhitespace) -> Result<CliAction, String> {
223 let fixture_name = args.next().unwrap().to_string();
224 let (new_universe, new_channel) = parse_universe_and_channel(args)?;
225
226 Ok(CliAction::FixtureMove {
227 fixture_name,
228 new_channel,
229 new_universe
230 })
231}
232
233fn parse_remove_fixture(mut args: SplitAsciiWhitespace) -> Result<CliAction, String> {
239 let fixture_name = args.next().unwrap().to_string();
240
241 Ok(CliAction::FixtureRemove {
242 fixture_name
243 })
244}
245
246fn parse_set_value(mut args: SplitAsciiWhitespace) -> Result<CliAction,String> {
256 let name = args.next().unwrap().to_string();
257 let property_name = args.next().unwrap().to_string();
258 let value = args.next().unwrap().to_string();
259
260 let property_type = parse_property_type(&property_name)?;
261
262 let value = parse_cli_value(&*value)?;
263
264 Ok(CliAction::FixtureSet {
265 name,
266 property_type,
267 value,
268 })
269}
270
271fn parse_get_type(mut args: SplitAsciiWhitespace) -> Result<CliAction,String> {
277 let fixture_name = args.next().unwrap().to_string();
278
279 Ok(CliAction::FixtureGetType {
280 fixture_name,
281 })
282}
283
284fn parse_exit(mut args: SplitAsciiWhitespace) -> Result<CliAction,String> {
294 match args.next() {
295 Some("save") => Ok(CliAction::Exit { save_changes: Some(true) }),
296 Some("discard") => Ok(CliAction::Exit { save_changes: Some(false) }),
297 Some(invalid) => Err(format!("Error: Invalid argument '{}' for exit. Use 'save' or 'discard'.", invalid)),
298 None => Ok(CliAction::Exit { save_changes: None }),
299 }
300}
301
302pub(super) fn parse_cli_value(input: &str) -> Result<ChannelValue,String> {
309
310 let sanitized_input = input.trim().replace("_", "");
311 let input_str = sanitized_input.as_str();
312
313 if let Some(percent_string) = input_str.strip_suffix("%") {
314 match percent_string.parse::<FloatChannelValue>() {
315 Ok(p) if (0.0..=100.0).contains(&p) => {
316 let fraction = p / 100.0;
317 let raw_value = fraction * (ChannelValue::MAX as FloatChannelValue);
318
319 Ok(raw_value.round() as ChannelValue)
320 }
321 Ok(_) => Err(format!("\"{}\" must be between 0 and 100.", percent_string)),
322 Err(_) => Err(format!("Invalid percentage format {}", percent_string)),
323 }
324 } else if let Some(hex_str) = input_str.strip_prefix("#") {
325 let hex_len = hex_str.len();
326
327 match ChannelValue::from_str_radix(hex_str, 16) {
328 Ok(val) => {
329 let scaled_val = match hex_len {
330 2 => val * 0x01010101,
331 4 => val * 0x00010001,
332 6 => val << 8,
333 8 => val,
334 _ => return Err(format!(
335 "Unsupported hex length: {}. Valid lengths are 2, 4, 6, or 8 digits (excluding '_').",
336 hex_len
337 )),
338 };
339 Ok(scaled_val)
340 }
341
342 Err(_) => Err(format!("Invalid hex format {}", hex_str))
343 }
344 } else {
345 input_str.parse::<ChannelValue>()
346 .map_err(|_| format!("Invalid value {}. ", input_str))
347 }
348}
349
350
351fn parse_universe_and_channel(mut args: SplitAsciiWhitespace) -> Result<(usize, ChannelIndex),String> {
361 let parsed_string = args.next().unwrap();
362 let (universe, channel_string) = match parsed_string.split_once(".") {
363 Some(pair) => pair,
364 None => return Err("Error: Please specify the channel with [universe].[channel]".to_string()),
365 };
366 let universe = match universe.parse::<usize>() {
367 Ok(universe) => universe,
368 Err(_) => {
369 return Err(format!("Error: \"{universe}\" is not a valid universe-number"))
370 }
371 };
372
373 let channel = match channel_string.parse::<ChannelIndex>() {
374 Ok(channel) => channel,
375 Err(_) => {
376 return Err(format!("Error: \"{}\" is not a valid channel-number", channel_string))
377 }
378 };
379 Ok((universe, channel))
380}
381
382fn parse_property_type(property_name: &str) -> Result<PropertyType, String> {
392 Ok(match PropertyType::from_str(property_name) {
393 Ok(property_type) => property_type,
394 Err(InvalidPropertyType(property_type)) => {
395 return Err(format!("Error: \"{property_type}\" is not a valid PropertyType"))
396 }
397 Err(_) => unreachable!() })
399}