usage/complete/
fish.rs

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    // Build logic to write spec directly to file without storing in shell variables
41    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}