Repository: clap-rs/clap (language: rust)

Issue:
clap_complete generates invalid powershell code when docs have RIGHT_SINGLE_QUOTATION_MARK
### Please complete the following tasks

- [X] I have searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [X] I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues

### Rust Version

rustc 1.81.0 (eeb90cda1 2024-09-04)

### Clap Version

4.5.21

### Minimal reproducible code

```rust
use std::io;

use clap::{CommandFactory, Parser};
use clap_complete::{generate, Shell};

fn main() {
    let cmd = Cmd::parse();
    cmd.run();
}

#[derive(Parser, Debug)]
struct Cmd {
    /// This doesn’t work on powershell.
    #[arg(long)]
    shell: Shell,
}

impl Cmd {
    fn run(&self) {
        let cmd = &mut Cmd::command();
        generate(self.shell, cmd, "sample", &mut io::stdout());
    }
}
```


### Steps to reproduce the bug with the above code

On Windows (PowerShell), run:

```sh
cargo run -- --shell powershell > bug.ps1
```

Then try to load this file:

```console
$ . .\bug.ps1
ParserError: C:\Users\fnando\Downloads\bug.ps1:24
Line |
  24 |  … --shell', [CompletionResultType]::ParameterName, 'This doesn’t work o …
     |                                                                 ~
     | Missing ')' in method call.
```

### Actual Behaviour

When trying to load the generated completion code, I get an exception because it's not properly escaping `’`.

### Expected Behaviour

clap_complete escapes `’`

### Additional Context

_No response_

### Debug Output

```console
$ cargo run -- --shell powershell
[clap_builder::builder::command]Command::_do_parse
[clap_builder::builder::command]Command::_build: name="clap_complete_bug"
[clap_builder::builder::command]Command::_propagate:clap_complete_bug
[clap_builder::builder::command]Command::_check_help_and_version:clap_complete_bug expand_help_tree=false
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_check_help_and_version: Building default --help
[clap_builder::builder::command]Command::_propagate_global_args:clap_complete_bug
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:shell
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::parser::parser]Parser::get_matches_with
[clap_builder::parser::parser]Parser::parse
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"--shell"'
[clap_builder::parser::parser]Parser::possible_subcommand: arg=Ok("--shell")
[clap_builder::parser::parser]Parser::get_matches_with: sc=None
[clap_builder::parser::parser]Parser::parse_long_arg
[clap_builder::parser::parser]Parser::parse_long_arg: Does it contain '='...
[clap_builder::parser::parser]Parser::parse_long_arg: Found valid arg or flag '--shell <SHELL>'
[clap_builder::parser::parser]Parser::parse_long_arg("shell"): Found an arg with value 'None'
[clap_builder::parser::parser]Parser::parse_opt_value; arg=shell, val=None, has_eq=false
[clap_builder::parser::parser]Parser::parse_opt_value; arg.settings=ArgFlags(1)
[clap_builder::parser::parser]Parser::parse_opt_value; Checking for val...
[clap_builder::parser::parser]Parser::parse_opt_value: More arg vals required...
[clap_builder::parser::parser]Parser::get_matches_with: After parse_long_arg Opt("shell")
[clap_builder::parser::parser]Parser::get_matches_with: Begin parsing '"powershell"'
[clap_builder::parser::arg_matcher]ArgMatcher::needs_more_vals: o=shell, pending=1
[clap_builder::parser::arg_matcher]ArgMatcher::needs_more_vals: expected=1, actual=1
[clap_builder::parser::parser]Parser::resolve_pending: id="shell"
[clap_builder::parser::parser]Parser::react action=Set, identifier=Some(Long), source=CommandLine
[clap_builder::parser::parser]Parser::react: cur_idx:=1
[clap_builder::parser::parser]Parser::remove_overrides: id="shell"
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="shell", source=CommandLine
[clap_builder::builder::command]Command::groups_for_arg: id="shell"
[clap_builder::parser::arg_matcher]ArgMatcher::start_custom_arg: id="Cmd", source=CommandLine
[clap_builder::parser::parser]Parser::push_arg_values: ["powershell"]
[clap_builder::parser::parser]Parser::add_single_val_to_arg: cur_idx:=2
[clap_builder::parser::arg_matcher]ArgMatcher::needs_more_vals: o=shell, pending=0
[clap_builder::parser::arg_matcher]ArgMatcher::needs_more_vals: expected=1, actual=0
[clap_builder::parser::parser]Parser::react not enough values passed in, leaving it to the validator to complain
[clap_builder::parser::parser]Parser::add_defaults
[clap_builder::parser::parser]Parser::add_defaults:iter:shell:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:shell: doesn't have default vals
[clap_builder::parser::parser]Parser::add_defaults:iter:help:
[clap_builder::parser::parser]Parser::add_default_value: doesn't have conditional defaults
[clap_builder::parser::parser]Parser::add_default_value:iter:help: doesn't have default vals
[clap_builder::parser::validator]Validator::validate
[clap_builder::builder::command]Command::groups_for_arg: id="shell"
[clap_builder::parser::validator]Conflicts::gather_direct_conflicts id="shell", conflicts=[]
[clap_builder::parser::validator]Conflicts::gather_direct_conflicts id="Cmd", conflicts=[]
[clap_builder::parser::validator]Validator::validate_conflicts
[clap_builder::parser::validator]Validator::validate_exclusive
[clap_builder::parser::validator]Validator::validate_conflicts::iter: id="shell"
[clap_builder::parser::validator]Conflicts::gather_conflicts: arg="shell"
[clap_builder::parser::validator]Conflicts::gather_conflicts: conflicts=[]
[clap_builder::parser::validator]Validator::validate_required: required=ChildGraph([Child { id: "shell", children: [] }])
[clap_builder::parser::validator]Validator::gather_requires
[clap_builder::parser::validator]Validator::gather_requires:iter:"shell"
[clap_builder::parser::validator]Validator::gather_requires:iter:"Cmd"
[clap_builder::parser::validator]Validator::gather_requires:iter:"Cmd":group
[clap_builder::parser::validator]Validator::validate_required: is_exclusive_present=false
[clap_builder::parser::arg_matcher]ArgMatcher::get_global_values: global_arg_vec=[]
[clap_builder::builder::command]Command::_build: name="clap_complete_bug"
[clap_builder::builder::command]Command::_propagate:clap_complete_bug
[clap_builder::builder::command]Command::_check_help_and_version:clap_complete_bug expand_help_tree=true
[clap_builder::builder::command]Command::long_help_exists
[clap_builder::builder::command]Command::_check_help_and_version: Building default --help
[clap_builder::builder::command]Command::_propagate_global_args:clap_complete_bug
[clap_builder::builder::debug_asserts]Command::_debug_asserts
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:shell
[clap_builder::builder::debug_asserts]Arg::_debug_asserts:help
[clap_builder::builder::debug_asserts]Command::_verify_positionals
[clap_builder::builder::command]Command::_build_bin_names
[ clap_builder::output::usage]Usage::get_required_usage_from: incls=[], matcher=false, incl_last=true
[ clap_builder::output::usage]Usage::get_required_usage_from: unrolled_reqs=["shell"]
[ clap_builder::output::usage]Usage::get_required_usage_from:iter:"shell" arg is_present=false
[ clap_builder::output::usage]Usage::get_required_usage_from: ret_val=[StyledStr("\u{1b}[1m--shell\u{1b}[0m <SHELL>")]

using namespace System.Management.Automation
using namespace System.Management.Automation.Language

Register-ArgumentCompleter -Native -CommandName 'sample' -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    $commandElements = $commandAst.CommandElements
    $command = @(
        'sample'
        for ($i = 1; $i -lt $commandElements.Count; $i++) {
            $element = $commandElements[$i]
            if ($element -isnot [StringConstantExpressionAst] -or
                $element.StringConstantType -ne [StringConstantType]::BareWord -or
                $element.Value.StartsWith('-') -or
                $element.Value -eq $wordToComplete) {
                break
        }
        $element.Value
    }) -join ';'

    $completions = @(switch ($command) {
        'sample' {
            [CompletionResult]::new('--shell', '--shell', [CompletionResultType]::ParameterName, 'This doesn’t work on powershell')
            [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
            [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
            break
        }
    })

    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
        Sort-Object -Property ListItemText
}
```


Discussion / hints:

Fix patch:
diff --git a/clap_complete/src/aot/shells/powershell.rs b/clap_complete/src/aot/shells/powershell.rs
index 13e4a61f351..e99c9eac734 100644
--- a/clap_complete/src/aot/shells/powershell.rs
+++ b/clap_complete/src/aot/shells/powershell.rs
@@ -59,7 +59,7 @@ Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
 
 // Escape string inside single quotes
 fn escape_string(string: &str) -> String {
-    string.replace('\'', "''")
+    string.replace('\'', "''").replace('’', "'’")
 }
 
 fn escape_help<T: ToString>(help: Option<&StyledStr>, data: T) -> String {


Test patch: