1 use std::io::Write;
2
3 use clap::builder::StyledStr;
4 use clap::*;
5
6 use crate::generator::{utils, Generator};
7 use crate::INTERNAL_ERROR_MSG;
8
9 /// Generate powershell completion file
10 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
11 pub struct PowerShell;
12
13 impl Generator for PowerShell {
file_name(&self, name: &str) -> String14 fn file_name(&self, name: &str) -> String {
15 format!("_{}.ps1", name)
16 }
17
generate(&self, cmd: &Command, buf: &mut dyn Write)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#"
27 using namespace System.Management.Automation
28 using namespace System.Management.Automation.Language
29
30 Register-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
escape_string(string: &str) -> String63 fn escape_string(string: &str) -> String {
64 string.replace('\'', "''")
65 }
66
get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String67 fn 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
generate_inner(p: &Command, previous_command_name: &str) -> String74 fn 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