1use crate::complete::CompleteOptions;
2use heck::ToSnakeCase;
3
4pub fn complete_fish(opts: &CompleteOptions) -> String {
5 let usage_bin = &opts.usage_bin;
6 let bin = &opts.bin;
7 let bin_snake = bin.to_snake_case();
8 let spec_variable = if let Some(cache_key) = &opts.cache_key {
9 format!("_usage_spec_{bin_snake}_{}", cache_key.to_snake_case())
10 } else {
11 format!("_usage_spec_{bin_snake}")
12 };
13 let generated_comment = if let Some(source_file) = &opts.source_file {
14 format!("# @generated by usage-cli from {source_file}")
15 } else {
16 "# @generated by usage-cli from usage spec".to_string()
17 };
18 let mut out = vec![
19 generated_comment,
20 format!(
21 r#"
22# if "{usage_bin}" is not installed show an error
23if ! type -p {usage_bin} &> /dev/null
24 echo >&2
25 echo "Error: {usage_bin} CLI not found. This is required for completions to work in {bin}." >&2
26 echo "See https://usage.jdx.dev for more information." >&2
27 return 1
28end"#
29 ),
30 ];
31
32 if let Some(spec) = &opts.spec {
33 let spec_escaped = spec.to_string().replace("'", r"\'");
34 out.push(format!(
35 r#"
36set {spec_variable} '{spec_escaped}'"#
37 ));
38 }
39
40 let file_write_logic = if let Some(usage_cmd) = &opts.usage_cmd {
42 if opts.cache_key.is_some() {
43 format!(
44 r#"if not test -f "$spec_file"
45 {usage_cmd} | string collect > "$spec_file"
46end"#
47 )
48 } else {
49 format!(r#"{usage_cmd} | string collect > "$spec_file""#)
50 }
51 } else if let Some(_spec) = &opts.spec {
52 if opts.cache_key.is_some() {
53 format!(
54 r#"if not test -f "$spec_file"
55 echo ${spec_variable} > "$spec_file"
56end"#
57 )
58 } else {
59 format!(r#"echo ${spec_variable} > "$spec_file""#)
60 }
61 } else {
62 String::new()
63 };
64
65 out.push(format!(
66 r#"
67set -l tmpdir (if set -q TMPDIR; echo $TMPDIR; else; echo /tmp; end)
68set -l spec_file "$tmpdir/usage_{spec_variable}.spec"
69{file_write_logic}
70
71set -l tokens
72if commandline -x >/dev/null 2>&1
73 complete -xc {bin} -a "(command {usage_bin} complete-word --shell fish -f \"$spec_file\" -- (commandline -xpc) (commandline -t))"
74else
75 complete -xc {bin} -a "(command {usage_bin} complete-word --shell fish -f \"$spec_file\" -- (commandline -opc) (commandline -t))"
76end
77"#
78 ).trim().to_string());
79
80 out.join("\n")
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::test::SPEC_KITCHEN_SINK;
87 use insta::assert_snapshot;
88
89 #[test]
90 fn test_complete_fish() {
91 assert_snapshot!(complete_fish(&CompleteOptions {
92 usage_bin: "usage".to_string(),
93 shell: "fish".to_string(),
94 bin: "mycli".to_string(),
95 cache_key: None,
96 spec: None,
97 usage_cmd: Some("mycli complete --usage".to_string()),
98 include_bash_completion_lib: false,
99 source_file: None,
100 }));
101 assert_snapshot!(complete_fish(&CompleteOptions {
102 usage_bin: "usage".to_string(),
103 shell: "fish".to_string(),
104 bin: "mycli".to_string(),
105 cache_key: Some("1.2.3".to_string()),
106 spec: None,
107 usage_cmd: Some("mycli complete --usage".to_string()),
108 include_bash_completion_lib: false,
109 source_file: None,
110 }));
111 assert_snapshot!(complete_fish(&CompleteOptions {
112 usage_bin: "usage".to_string(),
113 shell: "fish".to_string(),
114 bin: "mycli".to_string(),
115 cache_key: None,
116 spec: Some(SPEC_KITCHEN_SINK.clone()),
117 usage_cmd: None,
118 include_bash_completion_lib: false,
119 source_file: None,
120 }));
121 }
122}