1 // Std
2 use std::io::Write;
3 
4 // Internal
5 use app::parser::Parser;
6 use INTERNAL_ERROR_MSG;
7 
8 pub struct PowerShellGen<'a, 'b>
9 where
10     'a: 'b,
11 {
12     p: &'b Parser<'a, 'b>,
13 }
14 
15 impl<'a, 'b> PowerShellGen<'a, 'b> {
new(p: &'b Parser<'a, 'b>) -> Self16     pub fn new(p: &'b Parser<'a, 'b>) -> Self {
17         PowerShellGen { p: p }
18     }
19 
generate_to<W: Write>(&self, buf: &mut W)20     pub fn generate_to<W: Write>(&self, buf: &mut W) {
21         let bin_name = self.p.meta.bin_name.as_ref().unwrap();
22 
23         let mut names = vec![];
24         let subcommands_cases = generate_inner(self.p, "", &mut names);
25 
26         let result = format!(
27             r#"
28 using namespace System.Management.Automation
29 using namespace System.Management.Automation.Language
30 
31 Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
32     param($wordToComplete, $commandAst, $cursorPosition)
33 
34     $commandElements = $commandAst.CommandElements
35     $command = @(
36         '{bin_name}'
37         for ($i = 1; $i -lt $commandElements.Count; $i++) {{
38             $element = $commandElements[$i]
39             if ($element -isnot [StringConstantExpressionAst] -or
40                 $element.StringConstantType -ne [StringConstantType]::BareWord -or
41                 $element.Value.StartsWith('-')) {{
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<&str>, data: T) -> String67 fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
68     match help {
69         Some(help) => escape_string(help),
70         _ => data.to_string(),
71     }
72 }
73 
generate_inner<'a, 'b, 'p>( p: &'p Parser<'a, 'b>, previous_command_name: &str, names: &mut Vec<&'p str>, ) -> String74 fn generate_inner<'a, 'b, 'p>(
75     p: &'p Parser<'a, 'b>,
76     previous_command_name: &str,
77     names: &mut Vec<&'p str>,
78 ) -> String {
79     debugln!("PowerShellGen::generate_inner;");
80     let command_name = if previous_command_name.is_empty() {
81         p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
82     } else {
83         format!("{};{}", previous_command_name, &p.meta.name)
84     };
85 
86     let mut completions = String::new();
87     let preamble = String::from("\n            [CompletionResult]::new(");
88 
89     for option in p.opts() {
90         if let Some(data) = option.s.short {
91             let tooltip = get_tooltip(option.b.help, data);
92             completions.push_str(&preamble);
93             completions.push_str(
94                 format!(
95                     "'-{}', '{}', {}, '{}')",
96                     data, data, "[CompletionResultType]::ParameterName", tooltip
97                 )
98                 .as_str(),
99             );
100         }
101         if let Some(data) = option.s.long {
102             let tooltip = get_tooltip(option.b.help, data);
103             completions.push_str(&preamble);
104             completions.push_str(
105                 format!(
106                     "'--{}', '{}', {}, '{}')",
107                     data, data, "[CompletionResultType]::ParameterName", tooltip
108                 )
109                 .as_str(),
110             );
111         }
112     }
113 
114     for flag in p.flags() {
115         if let Some(data) = flag.s.short {
116             let tooltip = get_tooltip(flag.b.help, data);
117             completions.push_str(&preamble);
118             completions.push_str(
119                 format!(
120                     "'-{}', '{}', {}, '{}')",
121                     data, data, "[CompletionResultType]::ParameterName", tooltip
122                 )
123                 .as_str(),
124             );
125         }
126         if let Some(data) = flag.s.long {
127             let tooltip = get_tooltip(flag.b.help, data);
128             completions.push_str(&preamble);
129             completions.push_str(
130                 format!(
131                     "'--{}', '{}', {}, '{}')",
132                     data, data, "[CompletionResultType]::ParameterName", tooltip
133                 )
134                 .as_str(),
135             );
136         }
137     }
138 
139     for subcommand in &p.subcommands {
140         let data = &subcommand.p.meta.name;
141         let tooltip = get_tooltip(subcommand.p.meta.about, data);
142         completions.push_str(&preamble);
143         completions.push_str(
144             format!(
145                 "'{}', '{}', {}, '{}')",
146                 data, data, "[CompletionResultType]::ParameterValue", tooltip
147             )
148             .as_str(),
149         );
150     }
151 
152     let mut subcommands_cases = format!(
153         r"
154         '{}' {{{}
155             break
156         }}",
157         &command_name, completions
158     );
159 
160     for subcommand in &p.subcommands {
161         let subcommand_subcommands_cases = generate_inner(&subcommand.p, &command_name, names);
162         subcommands_cases.push_str(&subcommand_subcommands_cases);
163     }
164 
165     subcommands_cases
166 }
167