clap_complete/aot/shells/
powershell.rs
1use std::io::{Error, Write};
2
3use clap::builder::StyledStr;
4use clap::{Arg, Command};
5
6use crate::generator::{utils, Generator};
7use crate::INTERNAL_ERROR_MSG;
8
9#[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 self.try_generate(cmd, buf)
20 .expect("failed to write completion file");
21 }
22
23 fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
24 let bin_name = cmd
25 .get_bin_name()
26 .expect("crate::generate should have set the bin_name");
27
28 let subcommands_cases = generate_inner(cmd, "");
29
30 write!(
31 buf,
32 r#"
33using namespace System.Management.Automation
34using namespace System.Management.Automation.Language
35
36Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
37 param($wordToComplete, $commandAst, $cursorPosition)
38
39 $commandElements = $commandAst.CommandElements
40 $command = @(
41 '{bin_name}'
42 for ($i = 1; $i -lt $commandElements.Count; $i++) {{
43 $element = $commandElements[$i]
44 if ($element -isnot [StringConstantExpressionAst] -or
45 $element.StringConstantType -ne [StringConstantType]::BareWord -or
46 $element.Value.StartsWith('-') -or
47 $element.Value -eq $wordToComplete) {{
48 break
49 }}
50 $element.Value
51 }}) -join ';'
52
53 $completions = @(switch ($command) {{{subcommands_cases}
54 }})
55
56 $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} |
57 Sort-Object -Property ListItemText
58}}
59"#
60 )
61 }
62}
63
64fn escape_string(string: &str) -> String {
66 string.replace('\'', "''").replace('’', "'’")
67}
68
69fn escape_help<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
70 if let Some(help) = help {
71 let help_str = help.to_string();
72 if !help_str.is_empty() {
73 return escape_string(&help_str.replace('\n', " "));
74 }
75 }
76 data.to_string()
77}
78
79fn generate_inner(p: &Command, previous_command_name: &str) -> String {
80 debug!("generate_inner");
81
82 let command_names = if previous_command_name.is_empty() {
83 vec![p.get_bin_name().expect(INTERNAL_ERROR_MSG).to_string()]
84 } else {
85 p.get_name_and_visible_aliases()
86 .into_iter()
87 .map(|name| format!("{previous_command_name};{name}"))
88 .collect()
89 };
90
91 let mut completions = String::new();
92 let preamble = String::from("\n [CompletionResult]::new(");
93
94 for option in p.get_opts() {
95 generate_aliases(&mut completions, &preamble, option);
96 }
97
98 for flag in utils::flags(p) {
99 generate_aliases(&mut completions, &preamble, &flag);
100 }
101
102 for subcommand in p.get_subcommands() {
103 for name in subcommand.get_name_and_visible_aliases() {
104 let tooltip = escape_help(subcommand.get_about(), name);
105 completions.push_str(&preamble);
106 completions.push_str(&format!(
107 "'{name}', '{name}', [CompletionResultType]::ParameterValue, '{tooltip}')"
108 ));
109 }
110 }
111
112 let mut subcommands_cases = String::new();
113 for command_name in &command_names {
114 subcommands_cases.push_str(&format!(
115 r"
116 '{command_name}' {{{completions}
117 break
118 }}"
119 ));
120 }
121
122 for subcommand in p.get_subcommands() {
123 for command_name in &command_names {
124 let subcommand_subcommands_cases = generate_inner(subcommand, command_name);
125 subcommands_cases.push_str(&subcommand_subcommands_cases);
126 }
127 }
128
129 subcommands_cases
130}
131
132fn generate_aliases(completions: &mut String, preamble: &String, arg: &Arg) {
133 use std::fmt::Write as _;
134
135 if let Some(aliases) = arg.get_short_and_visible_aliases() {
136 let tooltip = escape_help(arg.get_help(), aliases[0]);
137 for alias in aliases {
138 let _ = write!(
139 completions,
140 "{preamble}'-{alias}', '-{alias}{}', [CompletionResultType]::ParameterName, '{tooltip}')",
141 if alias.is_uppercase() { " " } else { "" },
143 );
144 }
145 }
146 if let Some(aliases) = arg.get_long_and_visible_aliases() {
147 let tooltip = escape_help(arg.get_help(), aliases[0]);
148 for alias in aliases {
149 let _ = write!(
150 completions,
151 "{preamble}'--{alias}', '--{alias}', [CompletionResultType]::ParameterName, '{tooltip}')"
152 );
153 }
154 }
155}