1use std::collections::HashMap;
17
18#[derive(Debug, Clone)]
20pub struct CompDescSet {
21 pub tag: String,
22 pub group: String,
23 pub items: Vec<CompDescItem>,
24 pub options: DescOptions,
25}
26
27#[derive(Debug, Clone)]
29pub struct CompDescItem {
30 pub word: String,
31 pub description: String,
32 pub hidden: bool,
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct DescOptions {
38 pub verbose: bool,
39 pub sort: bool,
40 pub unique: bool,
41 pub group_name: Option<String>,
42 pub separator: String,
43}
44
45impl Default for CompDescSet {
46 fn default() -> Self {
47 CompDescSet {
48 tag: String::new(),
49 group: String::new(),
50 items: Vec::new(),
51 options: DescOptions {
52 separator: " -- ".to_string(),
53 ..Default::default()
54 },
55 }
56 }
57}
58
59pub fn cd_get(spec: &str) -> CompDescItem {
61 if let Some((word, desc)) = spec.split_once(':') {
62 CompDescItem {
63 word: word.to_string(),
64 description: desc.to_string(),
65 hidden: false,
66 }
67 } else {
68 CompDescItem {
69 word: spec.to_string(),
70 description: String::new(),
71 hidden: false,
72 }
73 }
74}
75
76pub fn cd_init(specs: &[String], tag: &str, group: &str) -> CompDescSet {
78 let items: Vec<CompDescItem> = specs.iter().map(|s| cd_get(s)).collect();
79 CompDescSet {
80 tag: tag.to_string(),
81 group: group.to_string(),
82 items,
83 ..Default::default()
84 }
85}
86
87pub fn cd_sort(set: &mut CompDescSet) {
89 set.items.sort_by(|a, b| a.word.cmp(&b.word));
90}
91
92pub fn cd_calc(items: &[CompDescItem], separator: &str) -> (usize, usize) {
94 let max_word = items.iter().map(|i| i.word.len()).max().unwrap_or(0);
95 let max_desc = items.iter().map(|i| i.description.len()).max().unwrap_or(0);
96 (max_word, max_word + separator.len() + max_desc)
97}
98
99pub fn cd_prep(items: &[CompDescItem], separator: &str) -> Vec<String> {
101 let (max_word, _) = cd_calc(items, separator);
102 items
103 .iter()
104 .map(|item| {
105 if item.description.is_empty() {
106 item.word.clone()
107 } else {
108 format!(
109 "{:<width$}{}{}",
110 item.word,
111 separator,
112 item.description,
113 width = max_word
114 )
115 }
116 })
117 .collect()
118}
119
120pub fn cd_groups_want_sorting(sets: &[CompDescSet]) -> bool {
122 sets.iter().all(|s| s.options.sort)
123}
124
125pub fn cd_arrcat(sets: &[CompDescSet]) -> Vec<String> {
127 sets.iter()
128 .flat_map(|s| s.items.iter().map(|i| i.word.clone()))
129 .collect()
130}
131
132pub fn cd_arrdup(set: &CompDescSet) -> CompDescSet {
134 set.clone()
135}
136
137pub fn freecdsets(_sets: Vec<CompDescSet>) {}
139
140pub fn cd_group(items: &[CompDescItem]) -> HashMap<String, Vec<CompDescItem>> {
142 let mut groups: HashMap<String, Vec<CompDescItem>> = HashMap::new();
143 for item in items {
144 let key = if item.description.is_empty() {
145 "(no description)".to_string()
146 } else {
147 item.description.clone()
148 };
149 groups.entry(key).or_default().push(item.clone());
150 }
151 groups
152}
153
154pub fn arrcmp(a: &[String], b: &[String]) -> bool {
156 a == b
157}
158
159#[derive(Debug, Clone)]
163pub struct CompArgDef {
164 pub num: i32, pub action: String, pub description: String,
167 pub optional: bool,
168 pub repeated: bool,
169}
170
171#[derive(Debug, Clone)]
173pub struct CompOptDef {
174 pub name: String, pub description: String,
176 pub has_arg: bool, pub arg_desc: String, pub exclusive: Vec<String>, }
180
181#[derive(Debug, Clone, Default)]
183pub struct CompCommandDef {
184 pub options: Vec<CompOptDef>,
185 pub arguments: Vec<CompArgDef>,
186 pub subcommands: HashMap<String, CompCommandDef>,
187}
188
189pub fn parse_caarg(spec: &str) -> Option<CompArgDef> {
191 let parts: Vec<&str> = spec.splitn(3, ':').collect();
193 if parts.is_empty() {
194 return None;
195 }
196
197 let (num, optional) = if parts[0] == "*" {
198 (-1, false)
199 } else if parts[0].starts_with('?') {
200 (parts[0][1..].parse().unwrap_or(0), true)
201 } else {
202 (parts[0].parse().unwrap_or(0), false)
203 };
204
205 Some(CompArgDef {
206 num,
207 description: parts.get(1).unwrap_or(&"").to_string(),
208 action: parts.get(2).unwrap_or(&"").to_string(),
209 optional,
210 repeated: parts[0] == "*",
211 })
212}
213
214pub fn parse_caopt(spec: &str) -> Option<CompOptDef> {
216 let spec = spec.trim();
220 if spec.is_empty() {
221 return None;
222 }
223
224 let (exclusive, rest) = if spec.starts_with('(') {
226 if let Some(close) = spec.find(')') {
227 let excl: Vec<String> = spec[1..close]
228 .split_whitespace()
229 .map(String::from)
230 .collect();
231 (excl, spec[close + 1..].trim())
232 } else {
233 (Vec::new(), spec)
234 }
235 } else {
236 (Vec::new(), spec)
237 };
238
239 let (name, after_name) = if rest.starts_with("--") {
241 let end = rest
242 .find('[')
243 .unwrap_or(rest.find(':').unwrap_or(rest.len()));
244 (&rest[..end], &rest[end..])
245 } else if rest.starts_with('-') {
246 let end = if rest.len() > 2 { 2 } else { rest.len() };
247 let end = rest[end..]
248 .find('[')
249 .map(|i| i + end)
250 .unwrap_or(rest[end..].find(':').map(|i| i + end).unwrap_or(rest.len()));
251 (&rest[..end], &rest[end..])
252 } else {
253 return None;
254 };
255
256 let description = if let Some(start) = after_name.find('[') {
258 if let Some(end) = after_name[start..].find(']') {
259 after_name[start + 1..start + end].to_string()
260 } else {
261 String::new()
262 }
263 } else {
264 String::new()
265 };
266
267 let has_arg = after_name.contains(':');
269 let arg_desc = if has_arg {
270 after_name.rsplit(':').next().unwrap_or("").to_string()
271 } else {
272 String::new()
273 };
274
275 Some(CompOptDef {
276 name: name.to_string(),
277 description,
278 has_arg,
279 arg_desc,
280 exclusive,
281 })
282}
283
284pub fn rembslashcolon(s: &str) -> String {
286 s.replace("\\:", ":")
287}
288
289pub fn bslashcolon(s: &str) -> String {
291 s.replace(':', "\\:")
292}
293
294pub fn single_index(arr: &[String], val: &str) -> Option<usize> {
296 arr.iter().position(|s| s == val)
297}
298
299pub fn freecaargs(_args: Vec<CompArgDef>) {}
301pub fn freecadef(_def: CompCommandDef) {}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_cd_get() {
309 let item = cd_get("commit:Record changes");
310 assert_eq!(item.word, "commit");
311 assert_eq!(item.description, "Record changes");
312
313 let item = cd_get("plain");
314 assert_eq!(item.word, "plain");
315 assert_eq!(item.description, "");
316 }
317
318 #[test]
319 fn test_cd_init() {
320 let specs = vec!["a:first".into(), "b:second".into(), "c:third".into()];
321 let set = cd_init(&specs, "options", "group1");
322 assert_eq!(set.items.len(), 3);
323 assert_eq!(set.tag, "options");
324 }
325
326 #[test]
327 fn test_cd_sort() {
328 let mut set = cd_init(
329 &vec!["c:third".into(), "a:first".into(), "b:second".into()],
330 "",
331 "",
332 );
333 cd_sort(&mut set);
334 assert_eq!(set.items[0].word, "a");
335 assert_eq!(set.items[2].word, "c");
336 }
337
338 #[test]
339 fn test_cd_prep() {
340 let items = vec![
341 CompDescItem {
342 word: "short".into(),
343 description: "A short one".into(),
344 hidden: false,
345 },
346 CompDescItem {
347 word: "longer".into(),
348 description: "A longer one".into(),
349 hidden: false,
350 },
351 ];
352 let formatted = cd_prep(&items, " -- ");
353 assert!(formatted[0].contains(" -- "));
354 assert!(formatted[1].contains(" -- "));
355 }
356
357 #[test]
358 fn test_parse_caarg() {
359 let arg = parse_caarg("1:file:_files").unwrap();
360 assert_eq!(arg.num, 1);
361 assert_eq!(arg.description, "file");
362 assert_eq!(arg.action, "_files");
363
364 let arg = parse_caarg("*:rest args:_files").unwrap();
365 assert_eq!(arg.num, -1);
366 assert!(arg.repeated);
367 }
368
369 #[test]
370 fn test_parse_caopt() {
371 let opt = parse_caopt("-v[verbose output]").unwrap();
372 assert_eq!(opt.name, "-v");
373 assert_eq!(opt.description, "verbose output");
374 assert!(!opt.has_arg);
375
376 let opt = parse_caopt("--output[output file]:file:_files").unwrap();
377 assert_eq!(opt.name, "--output");
378 assert!(opt.has_arg);
379 }
380
381 #[test]
382 fn test_rembslashcolon() {
383 assert_eq!(rembslashcolon("a\\:b\\:c"), "a:b:c");
384 }
385
386 #[test]
387 fn test_bslashcolon() {
388 assert_eq!(bslashcolon("a:b:c"), "a\\:b\\:c");
389 }
390
391 #[test]
392 fn test_cd_group() {
393 let items = vec![
394 CompDescItem {
395 word: "a".into(),
396 description: "group1".into(),
397 hidden: false,
398 },
399 CompDescItem {
400 word: "b".into(),
401 description: "group1".into(),
402 hidden: false,
403 },
404 CompDescItem {
405 word: "c".into(),
406 description: "group2".into(),
407 hidden: false,
408 },
409 ];
410 let groups = cd_group(&items);
411 assert_eq!(groups.len(), 2);
412 assert_eq!(groups["group1"].len(), 2);
413 }
414}