1use std::io::Write;
2
3use clap::builder::StyledStr;
4use clap::*;
5
6use crate::generator::{utils, Generator};
7use crate::INTERNAL_ERROR_MSG;
8
9/// Generate powershell completion file
10#[derive(Copy, Clone, PartialEq, Eq, Debug)]
11pub struct PowerShell;
12
13impl Generator for PowerShell {
14    fn file_name(&self, name: &str) -> String {
15        format!("_{name}.ps1")
16    }
17
18    fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
19        let bin_name = cmd
20            .get_bin_name()
21            .expect("crate::generate should have set the bin_name");
22
23        let subcommands_cases = generate_inner(cmd, "");
24
25        let result = format!(
26            r#"
27using namespace System.Management.Automation
28using namespace System.Management.Automation.Language
29
30Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
31    param($wordToComplete, $commandAst, $cursorPosition)
32
33    $commandElements = $commandAst.CommandElements
34    $command = @(
35        '{bin_name}'
36        for ($i = 1; $i -lt $commandElements.Count; $i++) {{
37            $element = $commandElements[$i]
38            if ($element -isnot [StringConstantExpressionAst] -or
39                $element.StringConstantType -ne [StringConstantType]::BareWord -or
40                $element.Value.StartsWith('-') -or
41                $element.Value -eq $wordToComplete) {{
42                break
43        }}
44        $element.Value
45    }}) -join ';'
46
47    $completions = @(switch ($command) {{{subcommands_cases}
48    }})
49
50    $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} |
51        Sort-Object -Property ListItemText
52}}
53"#,
54            bin_name = bin_name,
55            subcommands_cases = subcommands_cases
56        );
57
58        w!(buf, result.as_bytes());
59    }
60}
61
62// Escape string inside single quotes
63fn escape_string(string: &str) -> String {
64    string.replace('\'', "''")
65}
66
67fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
68    match help {
69        Some(help) => escape_string(&help.to_string()),
70        _ => data.to_string(),
71    }
72}
73
74fn generate_inner(p: &Command, previous_command_name: &str) -> String {
75    debug!("generate_inner");
76
77    let command_name = if previous_command_name.is_empty() {
78        p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()
79    } else {
80        format!("{};{}", previous_command_name, &p.get_name())
81    };
82
83    let mut completions = String::new();
84    let preamble = String::from("\n            [CompletionResult]::new(");
85
86    for option in p.get_opts() {
87        if let Some(shorts) = option.get_short_and_visible_aliases() {
88            let tooltip = get_tooltip(option.get_help(), shorts[0]);
89            for short in shorts {
90                completions.push_str(&preamble);
91                completions.push_str(
92                    format!(
93                        "'-{}', '{}', {}, '{}')",
94                        short, short, "[CompletionResultType]::ParameterName", tooltip
95                    )
96                    .as_str(),
97                );
98            }
99        }
100
101        if let Some(longs) = option.get_long_and_visible_aliases() {
102            let tooltip = get_tooltip(option.get_help(), longs[0]);
103            for long in longs {
104                completions.push_str(&preamble);
105                completions.push_str(
106                    format!(
107                        "'--{}', '{}', {}, '{}')",
108                        long, long, "[CompletionResultType]::ParameterName", tooltip
109                    )
110                    .as_str(),
111                );
112            }
113        }
114    }
115
116    for flag in utils::flags(p) {
117        if let Some(shorts) = flag.get_short_and_visible_aliases() {
118            let tooltip = get_tooltip(flag.get_help(), shorts[0]);
119            for short in shorts {
120                completions.push_str(&preamble);
121                completions.push_str(
122                    format!(
123                        "'-{}', '{}', {}, '{}')",
124                        short, short, "[CompletionResultType]::ParameterName", tooltip
125                    )
126                    .as_str(),
127                );
128            }
129        }
130
131        if let Some(longs) = flag.get_long_and_visible_aliases() {
132            let tooltip = get_tooltip(flag.get_help(), longs[0]);
133            for long in longs {
134                completions.push_str(&preamble);
135                completions.push_str(
136                    format!(
137                        "'--{}', '{}', {}, '{}')",
138                        long, long, "[CompletionResultType]::ParameterName", tooltip
139                    )
140                    .as_str(),
141                );
142            }
143        }
144    }
145
146    for subcommand in p.get_subcommands() {
147        let data = &subcommand.get_name();
148        let tooltip = get_tooltip(subcommand.get_about(), data);
149
150        completions.push_str(&preamble);
151        completions.push_str(
152            format!(
153                "'{}', '{}', {}, '{}')",
154                data, data, "[CompletionResultType]::ParameterValue", tooltip
155            )
156            .as_str(),
157        );
158    }
159
160    let mut subcommands_cases = format!(
161        r"
162        '{}' {{{}
163            break
164        }}",
165        &command_name, completions
166    );
167
168    for subcommand in p.get_subcommands() {
169        let subcommand_subcommands_cases = generate_inner(subcommand, &command_name);
170        subcommands_cases.push_str(&subcommand_subcommands_cases);
171    }
172
173    subcommands_cases
174}
175