Rust · 5915 bytes Raw Blame History
1 use rush_parser::ast::{BraceExpansion, Word, WordPart};
2
3 /// Detect and parse brace patterns in a literal string
4 /// Returns a new Word with BraceExpansion parts if found
5 pub fn detect_brace_patterns(word: &Word) -> Word {
6 let mut new_parts = Vec::new();
7
8 for part in &word.parts {
9 match part {
10 WordPart::Literal(s) => {
11 // First check for arithmetic patterns $((expr))
12 if let Some(arith_word) = try_parse_arithmetic_literal(s) {
13 new_parts.extend(arith_word.parts);
14 } else if let Some(brace_word) = try_parse_brace_literal(s) {
15 // Then check for brace patterns
16 new_parts.extend(brace_word.parts);
17 } else {
18 new_parts.push(part.clone());
19 }
20 }
21 _ => {
22 // Keep other parts as-is
23 new_parts.push(part.clone());
24 }
25 }
26 }
27
28 Word::new(new_parts)
29 }
30
31 /// Try to parse a literal string for arithmetic patterns $((expr))
32 fn try_parse_arithmetic_literal(s: &str) -> Option<Word> {
33 // Find the pattern $((
34 let start = s.find("$((")?;
35
36 // Find the matching ))
37 let rest = &s[start + 3..];
38 let mut paren_count = 1;
39 let mut end = None;
40
41 for (i, ch) in rest.chars().enumerate() {
42 match ch {
43 '(' => paren_count += 1,
44 ')' => {
45 paren_count -= 1;
46 if paren_count == 0 {
47 end = Some(i);
48 break;
49 }
50 }
51 _ => {}
52 }
53 }
54
55 let end_pos = end?;
56
57 // Extract the expression
58 let before = &s[..start];
59 let expr = &rest[..end_pos];
60 let after = &s[start + 3 + end_pos + 2..];
61
62 // Build the word
63 let mut parts = Vec::new();
64 if !before.is_empty() {
65 parts.push(WordPart::Literal(before.to_string()));
66 }
67 parts.push(WordPart::ArithmeticExpansion(expr.to_string()));
68 if !after.is_empty() {
69 // Recursively handle remaining patterns
70 if let Some(after_word) = try_parse_arithmetic_literal(after) {
71 parts.extend(after_word.parts);
72 } else if let Some(after_word) = try_parse_brace_literal(after) {
73 parts.extend(after_word.parts);
74 } else {
75 parts.push(WordPart::Literal(after.to_string()));
76 }
77 }
78
79 Some(Word::new(parts))
80 }
81
82 /// Try to parse a literal string for brace patterns
83 /// Returns None if no braces found, or a Word with BraceExpansion if found
84 fn try_parse_brace_literal(s: &str) -> Option<Word> {
85 // Find the first {
86 let start = s.find('{')?;
87 let end = s[start..].find('}')? + start;
88
89 // Extract the brace content
90 let before = &s[..start];
91 let content = &s[start + 1..end];
92 let after = &s[end + 1..];
93
94 // Try to parse the content as a brace expansion
95 let brace_exp = parse_brace_content(content)?;
96
97 // Build the word
98 let mut parts = Vec::new();
99 if !before.is_empty() {
100 parts.push(WordPart::Literal(before.to_string()));
101 }
102 parts.push(WordPart::BraceExpansion(brace_exp));
103 if !after.is_empty() {
104 // Recursively handle remaining braces
105 if let Some(after_word) = try_parse_brace_literal(after) {
106 parts.extend(after_word.parts);
107 } else {
108 parts.push(WordPart::Literal(after.to_string()));
109 }
110 }
111
112 Some(Word::new(parts))
113 }
114
115 /// Parse the content inside braces
116 fn parse_brace_content(content: &str) -> Option<BraceExpansion> {
117 // Check if it's a sequence pattern: start..end or start..end..increment
118 if content.contains("..") {
119 parse_sequence(content)
120 } else {
121 // It's a list pattern: a,b,c
122 parse_list(content)
123 }
124 }
125
126 /// Parse a sequence pattern: 1..5 or a..z
127 fn parse_sequence(content: &str) -> Option<BraceExpansion> {
128 let parts: Vec<&str> = content.split("..").collect();
129
130 match parts.len() {
131 2 => {
132 // start..end
133 Some(BraceExpansion::Sequence {
134 start: parts[0].to_string(),
135 end: parts[1].to_string(),
136 increment: None,
137 })
138 }
139 3 => {
140 // start..end..increment
141 let increment = parts[2].parse::<i32>().ok()?;
142 Some(BraceExpansion::Sequence {
143 start: parts[0].to_string(),
144 end: parts[1].to_string(),
145 increment: Some(increment),
146 })
147 }
148 _ => None,
149 }
150 }
151
152 /// Parse a list pattern: a,b,c
153 fn parse_list(content: &str) -> Option<BraceExpansion> {
154 // Split by comma
155 let items: Vec<String> = content.split(',').map(|s| s.to_string()).collect();
156
157 // Need at least 2 items for a valid list
158 if items.len() >= 2 {
159 Some(BraceExpansion::List(items))
160 } else {
161 None
162 }
163 }
164
165 #[cfg(test)]
166 mod tests {
167 use super::*;
168
169 #[test]
170 fn test_detect_list_pattern() {
171 let word = Word::from_literal("file{a,b,c}.txt");
172 let result = detect_brace_patterns(&word);
173
174 assert_eq!(result.parts.len(), 3);
175 assert!(matches!(result.parts[0], WordPart::Literal(_)));
176 assert!(matches!(result.parts[1], WordPart::BraceExpansion(_)));
177 assert!(matches!(result.parts[2], WordPart::Literal(_)));
178 }
179
180 #[test]
181 fn test_detect_sequence_pattern() {
182 let word = Word::from_literal("num{1..5}");
183 let result = detect_brace_patterns(&word);
184
185 assert_eq!(result.parts.len(), 2);
186 assert!(matches!(result.parts[1], WordPart::BraceExpansion(_)));
187 }
188
189 #[test]
190 fn test_no_brace_pattern() {
191 let word = Word::from_literal("simple.txt");
192 let result = detect_brace_patterns(&word);
193
194 assert_eq!(result.parts.len(), 1);
195 assert!(matches!(result.parts[0], WordPart::Literal(_)));
196 }
197 }
198