1 | use std::io::Write; |
2 | |
3 | use clap::*; |
4 | |
5 | use crate::generator::{utils, Generator}; |
6 | use crate::INTERNAL_ERROR_MSG; |
7 | |
8 | /// Generate zsh completion file |
9 | #[derive (Copy, Clone, PartialEq, Eq, Debug)] |
10 | pub struct Zsh; |
11 | |
12 | impl Generator for Zsh { |
13 | fn file_name(&self, name: &str) -> String { |
14 | format!("_ {name}" ) |
15 | } |
16 | |
17 | fn generate(&self, cmd: &Command, buf: &mut dyn Write) { |
18 | let bin_name = cmd |
19 | .get_bin_name() |
20 | .expect("crate::generate should have set the bin_name" ); |
21 | |
22 | w!( |
23 | buf, |
24 | format!( |
25 | "#compdef {name} |
26 | |
27 | autoload -U is-at-least |
28 | |
29 | _ {name}() {{ |
30 | typeset -A opt_args |
31 | typeset -a _arguments_options |
32 | local ret=1 |
33 | |
34 | if is-at-least 5.2; then |
35 | _arguments_options=(-s -S -C) |
36 | else |
37 | _arguments_options=(-s -C) |
38 | fi |
39 | |
40 | local context curcontext= \"$curcontext \" state line |
41 | {initial_args}{subcommands} |
42 | }} |
43 | |
44 | {subcommand_details} |
45 | |
46 | if [ \"$funcstack[1] \" = \"_ {name}\" ]; then |
47 | _ {name} \"$@ \" |
48 | else |
49 | compdef _ {name} {name} |
50 | fi |
51 | " , |
52 | name = bin_name, |
53 | initial_args = get_args_of(cmd, None), |
54 | subcommands = get_subcommands_of(cmd), |
55 | subcommand_details = subcommand_details(cmd) |
56 | ) |
57 | .as_bytes() |
58 | ); |
59 | } |
60 | } |
61 | |
62 | // Displays the commands of a subcommand |
63 | // (( $+functions[_[bin_name_underscore]_commands] )) || |
64 | // _[bin_name_underscore]_commands() { |
65 | // local commands; commands=( |
66 | // '[arg_name]:[arg_help]' |
67 | // ) |
68 | // _describe -t commands '[bin_name] commands' commands "$@" |
69 | // |
70 | // Where the following variables are present: |
71 | // [bin_name_underscore]: The full space delineated bin_name, where spaces have been replaced by |
72 | // underscore characters |
73 | // [arg_name]: The name of the subcommand |
74 | // [arg_help]: The help message of the subcommand |
75 | // [bin_name]: The full space delineated bin_name |
76 | // |
77 | // Here's a snippet from rustup: |
78 | // |
79 | // (( $+functions[_rustup_commands] )) || |
80 | // _rustup_commands() { |
81 | // local commands; commands=( |
82 | // 'show:Show the active and installed toolchains' |
83 | // 'update:Update Rust toolchains' |
84 | // # ... snip for brevity |
85 | // 'help:Print this message or the help of the given subcommand(s)' |
86 | // ) |
87 | // _describe -t commands 'rustup commands' commands "$@" |
88 | // |
89 | fn subcommand_details(p: &Command) -> String { |
90 | debug!("subcommand_details" ); |
91 | |
92 | let bin_name = p |
93 | .get_bin_name() |
94 | .expect("crate::generate should have set the bin_name" ); |
95 | |
96 | let mut ret = vec![]; |
97 | |
98 | // First we do ourself |
99 | let parent_text = format!( |
100 | "\ |
101 | (( $+functions[_ {bin_name_underscore}_commands] )) || |
102 | _ {bin_name_underscore}_commands() {{ |
103 | local commands; commands=( {subcommands_and_args}) |
104 | _describe -t commands ' {bin_name} commands' commands \"$@ \" |
105 | }}" , |
106 | bin_name_underscore = bin_name.replace(' ' , "__" ), |
107 | bin_name = bin_name, |
108 | subcommands_and_args = subcommands_of(p) |
109 | ); |
110 | ret.push(parent_text); |
111 | |
112 | // Next we start looping through all the children, grandchildren, etc. |
113 | let mut all_subcommands = utils::all_subcommands(p); |
114 | |
115 | all_subcommands.sort(); |
116 | all_subcommands.dedup(); |
117 | |
118 | for (_, ref bin_name) in &all_subcommands { |
119 | debug!("subcommand_details:iter: bin_name={bin_name}" ); |
120 | |
121 | ret.push(format!( |
122 | "\ |
123 | (( $+functions[_ {bin_name_underscore}_commands] )) || |
124 | _ {bin_name_underscore}_commands() {{ |
125 | local commands; commands=( {subcommands_and_args}) |
126 | _describe -t commands ' {bin_name} commands' commands \"$@ \" |
127 | }}" , |
128 | bin_name_underscore = bin_name.replace(' ' , "__" ), |
129 | bin_name = bin_name, |
130 | subcommands_and_args = |
131 | subcommands_of(parser_of(p, bin_name).expect(INTERNAL_ERROR_MSG)) |
132 | )); |
133 | } |
134 | |
135 | ret.join(" \n" ) |
136 | } |
137 | |
138 | // Generates subcommand completions in form of |
139 | // |
140 | // '[arg_name]:[arg_help]' |
141 | // |
142 | // Where: |
143 | // [arg_name]: the subcommand's name |
144 | // [arg_help]: the help message of the subcommand |
145 | // |
146 | // A snippet from rustup: |
147 | // 'show:Show the active and installed toolchains' |
148 | // 'update:Update Rust toolchains' |
149 | fn subcommands_of(p: &Command) -> String { |
150 | debug!("subcommands_of" ); |
151 | |
152 | let mut segments = vec![]; |
153 | |
154 | fn add_subcommands(subcommand: &Command, name: &str, ret: &mut Vec<String>) { |
155 | debug!("add_subcommands" ); |
156 | |
157 | let text = format!( |
158 | "' {name}: {help}' \\" , |
159 | name = name, |
160 | help = escape_help(&subcommand.get_about().unwrap_or_default().to_string()) |
161 | ); |
162 | |
163 | ret.push(text); |
164 | } |
165 | |
166 | // The subcommands |
167 | for command in p.get_subcommands() { |
168 | debug!("subcommands_of:iter: subcommand={}" , command.get_name()); |
169 | |
170 | add_subcommands(command, command.get_name(), &mut segments); |
171 | |
172 | for alias in command.get_visible_aliases() { |
173 | add_subcommands(command, alias, &mut segments); |
174 | } |
175 | } |
176 | |
177 | // Surround the text with newlines for proper formatting. |
178 | // We need this to prevent weirdly formatted `command=(\n \n)` sections. |
179 | // When there are no (sub-)commands. |
180 | if !segments.is_empty() { |
181 | segments.insert(0, "" .to_string()); |
182 | segments.push(" " .to_string()); |
183 | } |
184 | |
185 | segments.join(" \n" ) |
186 | } |
187 | |
188 | // Get's the subcommand section of a completion file |
189 | // This looks roughly like: |
190 | // |
191 | // case $state in |
192 | // ([bin_name]_args) |
193 | // curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\" |
194 | // case $line[1] in |
195 | // |
196 | // ([name]) |
197 | // _arguments -C -s -S \ |
198 | // [subcommand_args] |
199 | // && ret=0 |
200 | // |
201 | // [RECURSIVE_CALLS] |
202 | // |
203 | // ;;", |
204 | // |
205 | // [repeat] |
206 | // |
207 | // esac |
208 | // ;; |
209 | // esac", |
210 | // |
211 | // Where the following variables are present: |
212 | // [name] = The subcommand name in the form of "install" for "rustup toolchain install" |
213 | // [bin_name] = The full space delineated bin_name such as "rustup toolchain install" |
214 | // [name_hyphen] = The full space delineated bin_name, but replace spaces with hyphens |
215 | // [repeat] = From the same recursive calls, but for all subcommands |
216 | // [subcommand_args] = The same as zsh::get_args_of |
217 | fn get_subcommands_of(parent: &Command) -> String { |
218 | debug!( |
219 | "get_subcommands_of: Has subcommands...{:?}" , |
220 | parent.has_subcommands() |
221 | ); |
222 | |
223 | if !parent.has_subcommands() { |
224 | return String::new(); |
225 | } |
226 | |
227 | let subcommand_names = utils::subcommands(parent); |
228 | let mut all_subcommands = vec![]; |
229 | |
230 | for (ref name, ref bin_name) in &subcommand_names { |
231 | debug!( |
232 | "get_subcommands_of:iter: parent={}, name={name}, bin_name={bin_name}" , |
233 | parent.get_name(), |
234 | ); |
235 | let mut segments = vec![format!("( {name})" )]; |
236 | let subcommand_args = get_args_of( |
237 | parser_of(parent, bin_name).expect(INTERNAL_ERROR_MSG), |
238 | Some(parent), |
239 | ); |
240 | |
241 | if !subcommand_args.is_empty() { |
242 | segments.push(subcommand_args); |
243 | } |
244 | |
245 | // Get the help text of all child subcommands. |
246 | let children = get_subcommands_of(parser_of(parent, bin_name).expect(INTERNAL_ERROR_MSG)); |
247 | |
248 | if !children.is_empty() { |
249 | segments.push(children); |
250 | } |
251 | |
252 | segments.push(String::from(";;" )); |
253 | all_subcommands.push(segments.join(" \n" )); |
254 | } |
255 | |
256 | let parent_bin_name = parent |
257 | .get_bin_name() |
258 | .expect("crate::generate should have set the bin_name" ); |
259 | |
260 | format!( |
261 | " |
262 | case $state in |
263 | ( {name}) |
264 | words=($line[ {pos}] \"$ {{words[@] }}\") |
265 | (( CURRENT += 1 )) |
266 | curcontext= \"$ {{curcontext%:*:* }}: {name_hyphen}-command-$line[ {pos}]: \" |
267 | case $line[ {pos}] in |
268 | {subcommands} |
269 | esac |
270 | ;; |
271 | esac" , |
272 | name = parent.get_name(), |
273 | name_hyphen = parent_bin_name.replace(' ' , "-" ), |
274 | subcommands = all_subcommands.join(" \n" ), |
275 | pos = parent.get_positionals().count() + 1 |
276 | ) |
277 | } |
278 | |
279 | // Get the Command for a given subcommand tree. |
280 | // |
281 | // Given the bin_name "a b c" and the Command for "a" this returns the "c" Command. |
282 | // Given the bin_name "a b c" and the Command for "b" this returns the "c" Command. |
283 | fn parser_of<'cmd>(parent: &'cmd Command, bin_name: &str) -> Option<&'cmd Command> { |
284 | debug!("parser_of: p={}, bin_name={}" , parent.get_name(), bin_name); |
285 | |
286 | if bin_name == parent.get_bin_name().unwrap_or_default() { |
287 | return Some(parent); |
288 | } |
289 | |
290 | for subcommand: &Command in parent.get_subcommands() { |
291 | if let Some(ret: &Command) = parser_of(parent:subcommand, bin_name) { |
292 | return Some(ret); |
293 | } |
294 | } |
295 | |
296 | None |
297 | } |
298 | |
299 | // Writes out the args section, which ends up being the flags, opts and positionals, and a jump to |
300 | // another ZSH function if there are subcommands. |
301 | // The structure works like this: |
302 | // ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)] |
303 | // ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three' |
304 | // |
305 | // An example from the rustup command: |
306 | // |
307 | // _arguments -C -s -S \ |
308 | // '(-h --help --verbose)-v[Enable verbose output]' \ |
309 | // '(-V -v --version --verbose --help)-h[Print help information]' \ |
310 | // # ... snip for brevity |
311 | // ':: :_rustup_commands' \ # <-- displays subcommands |
312 | // '*::: :->rustup' \ # <-- displays subcommand args and child subcommands |
313 | // && ret=0 |
314 | // |
315 | // The args used for _arguments are as follows: |
316 | // -C: modify the $context internal variable |
317 | // -s: Allow stacking of short args (i.e. -a -b -c => -abc) |
318 | // -S: Do not complete anything after '--' and treat those as argument values |
319 | fn get_args_of(parent: &Command, p_global: Option<&Command>) -> String { |
320 | debug!("get_args_of" ); |
321 | |
322 | let mut segments = vec![String::from("_arguments \"${_arguments_options[@]} \" \\" )]; |
323 | let opts = write_opts_of(parent, p_global); |
324 | let flags = write_flags_of(parent, p_global); |
325 | let positionals = write_positionals_of(parent); |
326 | |
327 | if !opts.is_empty() { |
328 | segments.push(opts); |
329 | } |
330 | |
331 | if !flags.is_empty() { |
332 | segments.push(flags); |
333 | } |
334 | |
335 | if !positionals.is_empty() { |
336 | segments.push(positionals); |
337 | } |
338 | |
339 | if parent.has_subcommands() { |
340 | let parent_bin_name = parent |
341 | .get_bin_name() |
342 | .expect("crate::generate should have set the bin_name" ); |
343 | let subcommand_bin_name = format!( |
344 | " \":: :_ {name}_commands \" \\" , |
345 | name = parent_bin_name.replace(' ' , "__" ) |
346 | ); |
347 | segments.push(subcommand_bin_name); |
348 | |
349 | let subcommand_text = format!(" \"*::: :-> {name}\" \\" , name = parent.get_name()); |
350 | segments.push(subcommand_text); |
351 | }; |
352 | |
353 | segments.push(String::from("&& ret=0" )); |
354 | segments.join(" \n" ) |
355 | } |
356 | |
357 | // Uses either `possible_vals` or `value_hint` to give hints about possible argument values |
358 | fn value_completion(arg: &Arg) -> Option<String> { |
359 | if let Some(values) = crate::generator::utils::possible_values(arg) { |
360 | if values |
361 | .iter() |
362 | .any(|value| !value.is_hide_set() && value.get_help().is_some()) |
363 | { |
364 | Some(format!( |
365 | "(( {}))" , |
366 | values |
367 | .iter() |
368 | .filter_map(|value| { |
369 | if value.is_hide_set() { |
370 | None |
371 | } else { |
372 | Some(format!( |
373 | r#" {name}\:" {tooltip}""# , |
374 | name = escape_value(value.get_name()), |
375 | tooltip = |
376 | escape_help(&value.get_help().unwrap_or_default().to_string()), |
377 | )) |
378 | } |
379 | }) |
380 | .collect::<Vec<_>>() |
381 | .join(" \n" ) |
382 | )) |
383 | } else { |
384 | Some(format!( |
385 | "( {})" , |
386 | values |
387 | .iter() |
388 | .filter(|pv| !pv.is_hide_set()) |
389 | .map(|n| n.get_name()) |
390 | .collect::<Vec<_>>() |
391 | .join(" " ) |
392 | )) |
393 | } |
394 | } else { |
395 | // NB! If you change this, please also update the table in `ValueHint` documentation. |
396 | Some( |
397 | match arg.get_value_hint() { |
398 | ValueHint::Unknown => { |
399 | return None; |
400 | } |
401 | ValueHint::Other => "( )" , |
402 | ValueHint::AnyPath => "_files" , |
403 | ValueHint::FilePath => "_files" , |
404 | ValueHint::DirPath => "_files -/" , |
405 | ValueHint::ExecutablePath => "_absolute_command_paths" , |
406 | ValueHint::CommandName => "_command_names -e" , |
407 | ValueHint::CommandString => "_cmdstring" , |
408 | ValueHint::CommandWithArguments => "_cmdambivalent" , |
409 | ValueHint::Username => "_users" , |
410 | ValueHint::Hostname => "_hosts" , |
411 | ValueHint::Url => "_urls" , |
412 | ValueHint::EmailAddress => "_email_addresses" , |
413 | _ => { |
414 | return None; |
415 | } |
416 | } |
417 | .to_string(), |
418 | ) |
419 | } |
420 | } |
421 | |
422 | /// Escape help string inside single quotes and brackets |
423 | fn escape_help(string: &str) -> String { |
424 | string |
425 | .replace(' \\' , " \\\\" ) |
426 | .replace(' \'' , "' \\''" ) |
427 | .replace('[' , " \\[" ) |
428 | .replace(']' , " \\]" ) |
429 | .replace(':' , " \\:" ) |
430 | .replace('$' , " \\$" ) |
431 | .replace(from:'`' , to:" \\`" ) |
432 | } |
433 | |
434 | /// Escape value string inside single quotes and parentheses |
435 | fn escape_value(string: &str) -> String { |
436 | string |
437 | .replace(' \\' , " \\\\" ) |
438 | .replace(' \'' , "' \\''" ) |
439 | .replace('[' , " \\[" ) |
440 | .replace(']' , " \\]" ) |
441 | .replace(':' , " \\:" ) |
442 | .replace('$' , " \\$" ) |
443 | .replace('`' , " \\`" ) |
444 | .replace('(' , " \\(" ) |
445 | .replace(')' , " \\)" ) |
446 | .replace(from:' ' , to:" \\ " ) |
447 | } |
448 | |
449 | fn write_opts_of(p: &Command, p_global: Option<&Command>) -> String { |
450 | debug!("write_opts_of" ); |
451 | |
452 | let mut ret = vec![]; |
453 | |
454 | for o in p.get_opts() { |
455 | debug!("write_opts_of:iter: o={}" , o.get_id()); |
456 | |
457 | let help = escape_help(&o.get_help().unwrap_or_default().to_string()); |
458 | let conflicts = arg_conflicts(p, o, p_global); |
459 | |
460 | let multiple = if let ArgAction::Count | ArgAction::Append = o.get_action() { |
461 | "*" |
462 | } else { |
463 | "" |
464 | }; |
465 | |
466 | let vn = match o.get_value_names() { |
467 | None => " " .to_string(), |
468 | Some(val) => val[0].to_string(), |
469 | }; |
470 | let vc = match value_completion(o) { |
471 | Some(val) => format!(": {vn}: {val}" ), |
472 | None => format!(": {vn}: " ), |
473 | }; |
474 | let vc = vc.repeat(o.get_num_args().expect("built" ).min_values()); |
475 | |
476 | if let Some(shorts) = o.get_short_and_visible_aliases() { |
477 | for short in shorts { |
478 | let s = format!("' {conflicts}{multiple}- {short}+[ {help}] {vc}' \\" ); |
479 | |
480 | debug!("write_opts_of:iter: Wrote...{}" , &*s); |
481 | ret.push(s); |
482 | } |
483 | } |
484 | if let Some(longs) = o.get_long_and_visible_aliases() { |
485 | for long in longs { |
486 | let l = format!("' {conflicts}{multiple}-- {long}=[ {help}] {vc}' \\" ); |
487 | |
488 | debug!("write_opts_of:iter: Wrote...{}" , &*l); |
489 | ret.push(l); |
490 | } |
491 | } |
492 | } |
493 | |
494 | ret.join(" \n" ) |
495 | } |
496 | |
497 | fn arg_conflicts(cmd: &Command, arg: &Arg, app_global: Option<&Command>) -> String { |
498 | fn push_conflicts(conflicts: &[&Arg], res: &mut Vec<String>) { |
499 | for conflict in conflicts { |
500 | if let Some(s) = conflict.get_short() { |
501 | res.push(format!("- {s}" )); |
502 | } |
503 | |
504 | if let Some(l) = conflict.get_long() { |
505 | res.push(format!("-- {l}" )); |
506 | } |
507 | } |
508 | } |
509 | |
510 | let mut res = vec![]; |
511 | match (app_global, arg.is_global_set()) { |
512 | (Some(x), true) => { |
513 | let conflicts = x.get_arg_conflicts_with(arg); |
514 | |
515 | if conflicts.is_empty() { |
516 | return String::new(); |
517 | } |
518 | |
519 | push_conflicts(&conflicts, &mut res); |
520 | } |
521 | (_, _) => { |
522 | let conflicts = cmd.get_arg_conflicts_with(arg); |
523 | |
524 | if conflicts.is_empty() { |
525 | return String::new(); |
526 | } |
527 | |
528 | push_conflicts(&conflicts, &mut res); |
529 | } |
530 | }; |
531 | |
532 | format!("( {})" , res.join(" " )) |
533 | } |
534 | |
535 | fn write_flags_of(p: &Command, p_global: Option<&Command>) -> String { |
536 | debug!("write_flags_of;" ); |
537 | |
538 | let mut ret = vec![]; |
539 | |
540 | for f in utils::flags(p) { |
541 | debug!("write_flags_of:iter: f={}" , f.get_id()); |
542 | |
543 | let help = escape_help(&f.get_help().unwrap_or_default().to_string()); |
544 | let conflicts = arg_conflicts(p, &f, p_global); |
545 | |
546 | let multiple = if let ArgAction::Count | ArgAction::Append = f.get_action() { |
547 | "*" |
548 | } else { |
549 | "" |
550 | }; |
551 | |
552 | if let Some(short) = f.get_short() { |
553 | let s = format!("' {conflicts}{multiple}- {short}[ {help}]' \\" ); |
554 | |
555 | debug!("write_flags_of:iter: Wrote...{}" , &*s); |
556 | |
557 | ret.push(s); |
558 | |
559 | if let Some(short_aliases) = f.get_visible_short_aliases() { |
560 | for alias in short_aliases { |
561 | let s = format!("' {conflicts}{multiple}- {alias}[ {help}]' \\" ,); |
562 | |
563 | debug!("write_flags_of:iter: Wrote...{}" , &*s); |
564 | |
565 | ret.push(s); |
566 | } |
567 | } |
568 | } |
569 | |
570 | if let Some(long) = f.get_long() { |
571 | let l = format!("' {conflicts}{multiple}-- {long}[ {help}]' \\" ); |
572 | |
573 | debug!("write_flags_of:iter: Wrote...{}" , &*l); |
574 | |
575 | ret.push(l); |
576 | |
577 | if let Some(aliases) = f.get_visible_aliases() { |
578 | for alias in aliases { |
579 | let l = format!("' {conflicts}{multiple}-- {alias}[ {help}]' \\" ); |
580 | |
581 | debug!("write_flags_of:iter: Wrote...{}" , &*l); |
582 | |
583 | ret.push(l); |
584 | } |
585 | } |
586 | } |
587 | } |
588 | |
589 | ret.join(" \n" ) |
590 | } |
591 | |
592 | fn write_positionals_of(p: &Command) -> String { |
593 | debug!("write_positionals_of;" ); |
594 | |
595 | let mut ret = vec![]; |
596 | |
597 | // Completions for commands that end with two Vec arguments require special care. |
598 | // - You can have two Vec args separated with a custom value terminator. |
599 | // - You can have two Vec args with the second one set to last (raw sets last) |
600 | // which will require a '--' separator to be used before the second argument |
601 | // on the command-line. |
602 | // |
603 | // We use the '-S' _arguments option to disable completion after '--'. Thus, the |
604 | // completion for the second argument in scenario (B) does not need to be emitted |
605 | // because it is implicitly handled by the '-S' option. |
606 | // We only need to emit the first catch-all. |
607 | // |
608 | // Have we already emitted a catch-all multi-valued positional argument |
609 | // without a custom value terminator? |
610 | let mut catch_all_emitted = false; |
611 | |
612 | for arg in p.get_positionals() { |
613 | debug!("write_positionals_of:iter: arg={}" , arg.get_id()); |
614 | |
615 | let num_args = arg.get_num_args().expect("built" ); |
616 | let is_multi_valued = num_args.max_values() > 1; |
617 | |
618 | if catch_all_emitted && (arg.is_last_set() || is_multi_valued) { |
619 | // This is the final argument and it also takes multiple arguments. |
620 | // We've already emitted a catch-all positional argument so we don't need |
621 | // to emit anything for this argument because it is implicitly handled by |
622 | // the use of the '-S' _arguments option. |
623 | continue; |
624 | } |
625 | |
626 | let cardinality_value; |
627 | // If we have any subcommands, we'll emit a catch-all argument, so we shouldn't |
628 | // emit one here. |
629 | let cardinality = if is_multi_valued && !p.has_subcommands() { |
630 | match arg.get_value_terminator() { |
631 | Some(terminator) => { |
632 | cardinality_value = format!("* {}:" , escape_value(terminator)); |
633 | cardinality_value.as_str() |
634 | } |
635 | None => { |
636 | catch_all_emitted = true; |
637 | "*:" |
638 | } |
639 | } |
640 | } else if !arg.is_required_set() { |
641 | ":" |
642 | } else { |
643 | "" |
644 | }; |
645 | |
646 | let a = format!( |
647 | "' {cardinality}: {name}{help}: {value_completion}' \\" , |
648 | cardinality = cardinality, |
649 | name = arg.get_id(), |
650 | help = arg |
651 | .get_help() |
652 | .map(|s| s.to_string()) |
653 | .map(|v| " -- " .to_owned() + &v) |
654 | .unwrap_or_else(|| "" .to_owned()) |
655 | .replace('[' , " \\[" ) |
656 | .replace(']' , " \\]" ) |
657 | .replace(' \'' , "' \\''" ) |
658 | .replace(':' , " \\:" ), |
659 | value_completion = value_completion(arg).unwrap_or_default() |
660 | ); |
661 | |
662 | debug!("write_positionals_of:iter: Wrote...{a}" ); |
663 | |
664 | ret.push(a); |
665 | } |
666 | |
667 | ret.join(" \n" ) |
668 | } |
669 | |
670 | #[cfg (test)] |
671 | mod tests { |
672 | use crate::shells::zsh::{escape_help, escape_value}; |
673 | |
674 | #[test ] |
675 | fn test_escape_value() { |
676 | let raw_string = " \\ [foo]() `bar https://$PATH" ; |
677 | assert_eq!( |
678 | escape_value(raw_string), |
679 | " \\\\\\ \\[foo \\] \\( \\) \\ \\`bar \\ https \\:// \\$PATH" |
680 | ) |
681 | } |
682 | |
683 | #[test ] |
684 | fn test_escape_help() { |
685 | let raw_string = " \\ [foo]() `bar https://$PATH" ; |
686 | assert_eq!( |
687 | escape_help(raw_string), |
688 | " \\\\ \\[foo \\]() \\`bar https \\:// \\$PATH" |
689 | ) |
690 | } |
691 | } |
692 | |