Repository: kbknapp/clap-rs
Pull request #1189 (2018-02-21 02:05:11)

Use short help as tooltip in PowerShell completion
Also fixed some small issues with subcommand detection.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/kbknapp/clap-rs/1189)
<!-- Reviewable:end -->



Patch:
diff --git a/src/completions/powershell.rs b/src/completions/powershell.rs
index 19b14969..b06d4d19 100644
--- a/src/completions/powershell.rs
+++ b/src/completions/powershell.rs
@@ -19,55 +19,37 @@ impl<'a, 'b> PowerShellGen<'a, 'b> {
         let bin_name = self.p.meta.bin_name.as_ref().unwrap();
 
         let mut names = vec![];
-        let (subcommands_detection_cases, subcommands_cases) =
+        let subcommands_cases =
             generate_inner(self.p, "", &mut names);
 
-        let mut bin_names = vec![bin_name.to_string(), format!("./{0}", bin_name)];
-        if cfg!(windows) {
-            bin_names.push(format!("{0}.exe", bin_name));
-            bin_names.push(format!(r".\{0}", bin_name));
-            bin_names.push(format!(r".\{0}.exe", bin_name));
-            bin_names.push(format!(r"./{0}.exe", bin_name));
-        }
-
-        let bin_names = bin_names.iter().fold(String::new(), |previous, current| {
-            format!("{0}, '{1}'", previous, current)
-        });
-        let bin_names = bin_names.trim_left_matches(", ");
-
         let result = format!(r#"
-@({bin_names}) | %{{
-    Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {{
-        param($wordToComplete, $commandAst, $cursorPosition)
-
-        $command = '_{bin_name}'
-        $commandAst.CommandElements |
-            Select-Object -Skip 1 |
-            %{{
-                switch ($_.ToString()) {{
-{subcommands_detection_cases}
-                    default {{ 
-                        break
-                    }}
-                }}
-            }}
-
-        $completions = @()
-
-        switch ($command) {{
-{subcommands_cases}
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        '{bin_name}'
+        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('-')) {{
+                break
         }}
+        $element.Value
+    }}) -join ';'
 
-        $completions |
-            ?{{ $_ -like "$wordToComplete*" }} |
-            Sort-Object |
-            %{{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }}
-    }}
+    $completions = @(switch ($command) {{{subcommands_cases}
+    }})
+
+    $completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} |
+        Sort-Object -Property ListItemText
 }}
 "#,
-            bin_names = bin_names,
             bin_name = bin_name,
-            subcommands_detection_cases = subcommands_detection_cases,
             subcommands_cases = subcommands_cases
         );
 
@@ -75,64 +57,83 @@ impl<'a, 'b> PowerShellGen<'a, 'b> {
     }
 }
 
+// Escape string inside single quotes
+fn escape_string(string: &str) -> String { string.replace("'", "''") }
+
+fn get_tooltip<T : ToString>(help: Option<&str>, data: T) -> String {
+    match help {
+        Some(help) => escape_string(&help),
+        _ => data.to_string()
+    }
+}
+
 fn generate_inner<'a, 'b, 'p>(
     p: &'p Parser<'a, 'b>,
     previous_command_name: &str,
     names: &mut Vec<&'p str>,
-) -> (String, String) {
+) -> String {
     debugln!("PowerShellGen::generate_inner;");
     let command_name = if previous_command_name.is_empty() {
-        format!(
-            "{}_{}",
-            previous_command_name,
-            &p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG)
-        )
+        p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
     } else {
-        format!("{}_{}", previous_command_name, &p.meta.name)
-    };
-
-    let mut subcommands_detection_cases = if !names.contains(&&*p.meta.name) {
-        names.push(&*p.meta.name);
-        format!(
-            r"
-                    '{0}' {{
-                        $command += '_{0}'
-                        break
-                    }}
-",
-            &p.meta.name
-        )
-    } else {
-        String::new()
+        format!("{};{}", previous_command_name, &p.meta.name)
     };
 
     let mut completions = String::new();
-    for subcommand in &p.subcommands {
-        completions.push_str(&format!("'{}', ", &subcommand.p.meta.name));
+    let preamble = String::from("\n            [CompletionResult]::new(");
+
+    for option in p.opts() {
+        if let Some(data) = option.s.short {
+            let tooltip = get_tooltip(option.b.help, data);
+            completions.push_str(&preamble);
+            completions.push_str(format!("'-{}', '{}', {}, '{}')",
+                                         data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+        }
+        if let Some(data) = option.s.long {
+            let tooltip = get_tooltip(option.b.help, data);
+            completions.push_str(&preamble);
+            completions.push_str(format!("'--{}', '{}', {}, '{}')",
+                                         data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+        }
     }
-    for short in shorts!(p) {
-        completions.push_str(&format!("'-{}', ", short));
+
+    for flag in p.flags() {
+        if let Some(data) = flag.s.short {
+            let tooltip = get_tooltip(flag.b.help, data);
+            completions.push_str(&preamble);
+            completions.push_str(format!("'-{}', '{}', {}, '{}')",
+                                         data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+        }
+        if let Some(data) = flag.s.long {
+            let tooltip = get_tooltip(flag.b.help, data);
+            completions.push_str(&preamble);
+            completions.push_str(format!("'--{}', '{}', {}, '{}')",
+                                         data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
+        }
     }
-    for long in longs!(p) {
-        completions.push_str(&format!("'--{}', ", long));
+
+    for subcommand in &p.subcommands {
+        let data = &subcommand.p.meta.name;
+        let tooltip = get_tooltip(subcommand.p.meta.about, data);
+        completions.push_str(&preamble);
+        completions.push_str(format!("'{}', '{}', {}, '{}')",
+                                     data, data, "[CompletionResultType]::ParameterValue", tooltip).as_str());
     }
 
     let mut subcommands_cases = format!(
         r"
-            '{}' {{
-                $completions = @({})
-            }}
-",
+        '{}' {{{}
+            break
+        }}",
         &command_name,
-        completions.trim_right_matches(", ")
+        completions
     );
 
     for subcommand in &p.subcommands {
-        let (subcommand_subcommands_detection_cases, subcommand_subcommands_cases) =
+        let subcommand_subcommands_cases =
             generate_inner(&subcommand.p, &command_name, names);
-        subcommands_detection_cases.push_str(&subcommand_subcommands_detection_cases);
         subcommands_cases.push_str(&subcommand_subcommands_cases);
     }
 
-    (subcommands_detection_cases, subcommands_cases)
+    subcommands_cases
 }


Test patch:
diff --git a/tests/completions.rs b/tests/completions.rs
index 8770b122..efb001e8 100644
--- a/tests/completions.rs
+++ b/tests/completions.rs
@@ -189,205 +189,124 @@ complete -c myapp -n "__fish_using_command myapp help" -s h -l help -d 'Prints h
 complete -c myapp -n "__fish_using_command myapp help" -s V -l version -d 'Prints version information'
 "#;
 
-#[allow(dead_code)]
-#[cfg(not(target_os="windows"))]
 static POWERSHELL: &'static str = r#"
-@('myapp', './myapp') | %{
-    Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
-        param($wordToComplete, $commandAst, $cursorPosition)
-
-        $command = '_myapp'
-        $commandAst.CommandElements |
-            Select-Object -Skip 1 |
-            %{
-                switch ($_.ToString()) {
-
-                    'test' {
-                        $command += '_test'
-                        break
-                    }
-
-                    'help' {
-                        $command += '_help'
-                        break
-                    }
-
-                }
-            }
-
-        $completions = @()
-
-        switch ($command) {
-
-            '_myapp' {
-                $completions = @('test', 'help', '-h', '-V', '--help', '--version')
-            }
-
-            '_myapp_test' {
-                $completions = @('-h', '-V', '--case', '--help', '--version')
-            }
-
-            '_myapp_help' {
-                $completions = @('-h', '-V', '--help', '--version')
-            }
-
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        'my_app'
+        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('-')) {
+                break
         }
-
-        $completions |
-            ?{ $_ -like "$wordToComplete*" } |
-            Sort-Object |
-            %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
-    }
-}
-"#;
-
-#[allow(dead_code)]
-#[cfg(target_os="windows")]
-static POWERSHELL: &'static str = r#"
-@('myapp', './myapp', 'myapp.exe', '.\myapp', '.\myapp.exe', './myapp.exe') | %{
-    Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
-        param($wordToComplete, $commandAst, $cursorPosition)
-        $command = '_myapp'
-        $commandAst.CommandElements |
-            Select-Object -Skip 1 |
-            %{
-                switch ($_.ToString()) {
-                    'test' {
-                        $command += '_test'
-                        break
-                    }
-                    'help' {
-                        $command += '_help'
-                        break
-                    }
-                }
-            }
-        $completions = @()
-        switch ($command) {
-            '_myapp' {
-                $completions = @('test', 'help', '-h', '-V', '--help', '--version')
-            }
-            '_myapp_test' {
-                $completions = @('-h', '-V', '--case', '--help', '--version')
-            }
-            '_myapp_help' {
-                $completions = @('-h', '-V', '--help', '--version')
-            }
+        $element.Value
+    }) -join ';'
+
+    $completions = @(switch ($command) {
+        'my_app' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things')
+            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)')
+            break
         }
-        $completions |
-            ?{ $_ -like "$wordToComplete*" } |
-            Sort-Object |
-            %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
-    }
-}
-"#;
-
-#[allow(dead_code)]
-#[cfg(not(target_os="windows"))]
-static POWERSHELL_SPECIAL_CMDS: &'static str = r#"
-@('my_app', './my_app') | %{
-    Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
-        param($wordToComplete, $commandAst, $cursorPosition)
-
-        $command = '_my_app'
-        $commandAst.CommandElements |
-            Select-Object -Skip 1 |
-            %{
-                switch ($_.ToString()) {
-
-                    'test' {
-                        $command += '_test'
-                        break
-                    }
-
-                    'some_cmd' {
-                        $command += '_some_cmd'
-                        break
-                    }
-
-                    'help' {
-                        $command += '_help'
-                        break
-                    }
-
-                }
-            }
-
-        $completions = @()
-
-        switch ($command) {
-
-            '_my_app' {
-                $completions = @('test', 'some_cmd', 'help', '-h', '-V', '--help', '--version')
-            }
-
-            '_my_app_test' {
-                $completions = @('-h', '-V', '--case', '--help', '--version')
-            }
-
-            '_my_app_some_cmd' {
-                $completions = @('-h', '-V', '--config', '--help', '--version')
-            }
-
-            '_my_app_help' {
-                $completions = @('-h', '-V', '--help', '--version')
-            }
-
+        'my_app;test' {
+            [CompletionResult]::new('--case', 'case', [CompletionResultType]::ParameterName, 'the case to test')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;help' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
         }
+    })
 
-        $completions |
-            ?{ $_ -like "$wordToComplete*" } |
-            Sort-Object |
-            %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
-    }
+    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
+        Sort-Object -Property ListItemText
 }
 "#;
 
-#[allow(dead_code)]
-#[cfg(target_os="windows")]
 static POWERSHELL_SPECIAL_CMDS: &'static str = r#"
-@('my_app', './my_app', 'my_app.exe', '.\my_app', '.\my_app.exe', './my_app.exe') | %{
-    Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {
-        param($wordToComplete, $commandAst, $cursorPosition)
-        $command = '_my_app'
-        $commandAst.CommandElements |
-            Select-Object -Skip 1 |
-            %{
-                switch ($_.ToString()) {
-                    'test' {
-                        $command += '_test'
-                        break
-                    }
-                    'some_cmd' {
-                        $command += '_some_cmd'
-                        break
-                    }
-                    'help' {
-                        $command += '_help'
-                        break
-                    }
-                }
-            }
-        $completions = @()
-        switch ($command) {
-            '_my_app' {
-                $completions = @('test', 'some_cmd', 'help', '-h', '-V', '--help', '--version')
-            }
-            '_my_app_test' {
-                $completions = @('-h', '-V', '--case', '--help', '--version')
-            }
-            '_my_app_some_cmd' {
-                $completions = @('-h', '-V', '--config', '--help', '--version')
-            }
-            '_my_app_help' {
-                $completions = @('-h', '-V', '--help', '--version')
-            }
+using namespace System.Management.Automation
+using namespace System.Management.Automation.Language
+
+Register-ArgumentCompleter -Native -CommandName 'my_app' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+
+    $commandElements = $commandAst.CommandElements
+    $command = @(
+        'my_app'
+        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('-')) {
+                break
         }
-        $completions |
-            ?{ $_ -like "$wordToComplete*" } |
-            Sort-Object |
-            %{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
-    }
+        $element.Value
+    }) -join ';'
+
+    $completions = @(switch ($command) {
+        'my_app' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('test', 'test', [CompletionResultType]::ParameterValue, 'tests things')
+            [CompletionResult]::new('some_cmd', 'some_cmd', [CompletionResultType]::ParameterValue, 'tests other things')
+            [CompletionResult]::new('some-cmd-with-hypens', 'some-cmd-with-hypens', [CompletionResultType]::ParameterValue, 'some-cmd-with-hypens')
+            [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)')
+            break
+        }
+        'my_app;test' {
+            [CompletionResult]::new('--case', 'case', [CompletionResultType]::ParameterName, 'the case to test')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;some_cmd' {
+            [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'the other case to test')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;some-cmd-with-hypens' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+        'my_app;help' {
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
+            [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
+            [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
+            break
+        }
+    })
+
+    $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
+        Sort-Object -Property ListItemText
 }
 "#;
 
@@ -813,27 +732,25 @@ fn fish() {
     assert!(compare(&*string, FISH));
 }
 
-// Disabled until I figure out this windows line ending and AppVeyor issues
-//#[test]
-// fn powershell() {
-//     let mut app = build_app();
-//     let mut buf = vec![];
-//     app.gen_completions_to("myapp", Shell::PowerShell, &mut buf);
-//     let string = String::from_utf8(buf).unwrap();
-//
-//     assert!(compare(&*string, POWERSHELL));
-// }
-
-// Disabled until I figure out this windows line ending and AppVeyor issues
-//#[test]
-// fn powershell_with_special_commands() {
-//     let mut app = build_app_special_commands();
-//     let mut buf = vec![];
-//     app.gen_completions_to("my_app", Shell::PowerShell, &mut buf);
-//     let string = String::from_utf8(buf).unwrap();
-//
-//     assert!(compare(&*string, POWERSHELL_SPECIAL_CMDS));
-// }
+#[test]
+fn powershell() {
+    let mut app = build_app();
+    let mut buf = vec![];
+    app.gen_completions_to("my_app", Shell::PowerShell, &mut buf);
+    let string = String::from_utf8(buf).unwrap();
+
+    assert!(compare(&*string, POWERSHELL));
+}
+
+#[test]
+fn powershell_with_special_commands() {
+    let mut app = build_app_special_commands();
+    let mut buf = vec![];
+    app.gen_completions_to("my_app", Shell::PowerShell, &mut buf);
+    let string = String::from_utf8(buf).unwrap();
+
+    assert!(compare(&*string, POWERSHELL_SPECIAL_CMDS));
+}
 
 #[test]
 fn bash_with_special_commands() {