• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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