computer_use/
menu_path.rs1use serde::Serialize;
11
12use crate::error::ComputerError;
13
14pub const SEPARATORS: &[char] = &['>', '→'];
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
21pub struct MenuPath {
22 pub segments: Vec<String>,
24}
25
26impl MenuPath {
27 pub fn as_slice(&self) -> &[String] {
29 &self.segments
30 }
31}
32
33pub fn parse(input: &str) -> Result<MenuPath, ComputerError> {
35 let trimmed = input.trim();
36 if trimmed.is_empty() {
37 return Err(ComputerError::InvalidArguments("menu path is empty".into()));
38 }
39 let mut segments = Vec::new();
40 for raw in trimmed.split(SEPARATORS) {
41 let piece = raw.trim();
42 if piece.is_empty() {
43 return Err(ComputerError::InvalidArguments(format!(
44 "menu path has an empty segment in {input:?}"
45 )));
46 }
47 segments.push(piece.to_string());
48 }
49 if segments.is_empty() {
50 return Err(ComputerError::InvalidArguments(
51 "menu path has no segments".into(),
52 ));
53 }
54 Ok(MenuPath { segments })
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn single_segment_parses() {
63 let path = parse("Save").unwrap();
64 assert_eq!(path.segments, vec!["Save".to_string()]);
65 }
66
67 #[test]
68 fn standard_separator_with_padding() {
69 let path = parse("File > Save As > Save").unwrap();
70 assert_eq!(
71 path.segments,
72 vec![
73 "File".to_string(),
74 "Save As".to_string(),
75 "Save".to_string()
76 ]
77 );
78 }
79
80 #[test]
81 fn arrow_separator_is_accepted() {
82 let path = parse("File → Save").unwrap();
83 assert_eq!(path.segments, vec!["File".to_string(), "Save".to_string()]);
84 }
85
86 #[test]
87 fn empty_input_is_rejected() {
88 let error = parse("").unwrap_err();
89 assert!(matches!(error, ComputerError::InvalidArguments(_)));
90 }
91
92 #[test]
93 fn empty_segment_is_rejected() {
94 let error = parse("File > > Save").unwrap_err();
95 assert!(matches!(error, ComputerError::InvalidArguments(_)));
96 }
97
98 #[test]
99 fn fixture_menu_path_round_trip() {
100 let path = parse("File > New Finder Window").unwrap();
103 assert_eq!(path.segments.len(), 2);
104 assert_eq!(path.segments[1], "New Finder Window");
105 }
106
107 #[test]
108 fn trailing_separator_is_rejected() {
109 let error = parse("File > ").unwrap_err();
110 assert!(matches!(error, ComputerError::InvalidArguments(_)));
111 }
112}