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