hedl_cli/commands/
completion.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License in the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Shell completion generation - Tab completion for various shells
19
20use clap::Command;
21use clap_complete::{generate, Generator};
22use std::io;
23
24/// Generate shell completion script to stdout for a given command.
25///
26/// Generates shell-specific completion scripts that enable tab completion
27/// for HEDL CLI commands, arguments, and file paths.
28///
29/// # Arguments
30///
31/// * `generator` - The shell generator (Bash, Zsh, Fish, PowerShell, or Elvish)
32/// * `cmd` - The clap Command to generate completions for
33///
34/// # Returns
35///
36/// Returns `Ok(())` on success.
37///
38/// # Errors
39///
40/// This function does not typically return errors, but uses `Result` for
41/// consistency with other command functions.
42///
43/// # Examples
44///
45/// ```no_run
46/// use clap::Command;
47/// use clap_complete::shells::Bash;
48/// use hedl_cli::commands::generate_completion_for_command;
49///
50/// # fn main() -> Result<(), String> {
51/// let mut cmd = Command::new("hedl");
52/// generate_completion_for_command(Bash, &mut cmd)?;
53/// # Ok(())
54/// # }
55/// ```
56///
57/// # Output
58///
59/// Writes the completion script to stdout. Users typically redirect this to
60/// a file or evaluate it in their shell configuration.
61pub fn generate_completion_for_command<G: Generator>(
62    generator: G,
63    cmd: &mut Command,
64) -> Result<(), String> {
65    generate(generator, cmd, cmd.get_name().to_string(), &mut io::stdout());
66    Ok(())
67}
68
69/// Print installation instructions for shell completions.
70///
71/// Returns detailed, shell-specific instructions for installing and enabling
72/// HEDL CLI tab completions. Instructions cover both temporary (current session)
73/// and persistent (profile-based) installation methods.
74///
75/// # Arguments
76///
77/// * `shell` - The target shell name (bash, zsh, fish, powershell/pwsh, elvish)
78///
79/// # Returns
80///
81/// Returns a formatted string with installation instructions.
82///
83/// # Examples
84///
85/// ```
86/// use hedl_cli::commands::print_installation_instructions;
87///
88/// // Get bash installation instructions
89/// let instructions = print_installation_instructions("bash");
90/// assert!(instructions.contains("bash"));
91///
92/// // Get zsh installation instructions
93/// let instructions = print_installation_instructions("zsh");
94/// assert!(instructions.contains("zsh"));
95///
96/// // Unsupported shells return a generic message
97/// let instructions = print_installation_instructions("unknown");
98/// assert_eq!(instructions, "Unsupported shell");
99/// ```
100///
101/// # Supported Shells
102///
103/// - **bash**: Instructions for ~/.bashrc and bash-completion directories
104/// - **zsh**: Instructions for ~/.zshrc and $fpath completion directories
105/// - **fish**: Instructions for ~/.config/fish/completions/
106/// - **powershell/pwsh**: Instructions for PowerShell profile
107/// - **elvish**: Instructions for ~/.elvish/rc.elv
108///
109/// # Case Sensitivity
110///
111/// Shell names are case-insensitive (e.g., "BASH", "Bash", "bash" all work).
112pub fn print_installation_instructions(shell: &str) -> String {
113    match shell.to_lowercase().as_str() {
114        "bash" => {
115            r#"# Bash completion installation:
116
117# For current session only:
118eval "$(hedl completion bash)"
119
120# For persistent installation, add to your ~/.bashrc:
121echo 'eval "$(hedl completion bash)"' >> ~/.bashrc
122
123# Or save to completions directory:
124hedl completion bash > ~/.local/share/bash-completion/completions/hedl
125"#
126        }
127        "zsh" => {
128            r#"# Zsh completion installation:
129
130# For current session only:
131eval "$(hedl completion zsh)"
132
133# For persistent installation, add to your ~/.zshrc:
134echo 'eval "$(hedl completion zsh)"' >> ~/.zshrc
135
136# Or save to completions directory (ensure directory is in $fpath):
137hedl completion zsh > ~/.zsh/completions/_hedl
138"#
139        }
140        "fish" => {
141            r#"# Fish completion installation:
142
143# Save to fish completions directory:
144hedl completion fish > ~/.config/fish/completions/hedl.fish
145
146# Completions will be available in new fish sessions
147"#
148        }
149        "powershell" | "pwsh" => {
150            r#"# PowerShell completion installation:
151
152# For current session only:
153hedl completion powershell | Out-String | Invoke-Expression
154
155# For persistent installation, add to your PowerShell profile:
156# Find profile location with: $PROFILE
157# Then add this line:
158hedl completion powershell | Out-String | Invoke-Expression
159"#
160        }
161        "elvish" => {
162            r#"# Elvish completion installation:
163
164# For current session only:
165eval (hedl completion elvish)
166
167# For persistent installation, add to your ~/.elvish/rc.elv:
168eval (hedl completion elvish)
169"#
170        }
171        _ => "Unsupported shell",
172    }
173    .to_string()
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_installation_instructions_bash() {
182        let instructions = print_installation_instructions("bash");
183        assert!(!instructions.is_empty());
184        assert!(instructions.to_lowercase().contains("bash"));
185    }
186
187    #[test]
188    fn test_installation_instructions_zsh() {
189        let instructions = print_installation_instructions("zsh");
190        assert!(!instructions.is_empty());
191        assert!(instructions.contains("zsh"));
192    }
193
194    #[test]
195    fn test_installation_instructions_fish() {
196        let instructions = print_installation_instructions("fish");
197        assert!(!instructions.is_empty());
198        assert!(instructions.contains("fish"));
199    }
200
201    #[test]
202    fn test_installation_instructions_powershell() {
203        let instructions = print_installation_instructions("powershell");
204        assert!(!instructions.is_empty());
205        assert!(instructions.to_lowercase().contains("powershell"));
206    }
207
208    #[test]
209    fn test_installation_instructions_elvish() {
210        let instructions = print_installation_instructions("elvish");
211        assert!(!instructions.is_empty());
212        assert!(instructions.contains("elvish"));
213    }
214
215    #[test]
216    fn test_installation_instructions_case_insensitive() {
217        let lower = print_installation_instructions("bash");
218        let upper = print_installation_instructions("BASH");
219        let mixed = print_installation_instructions("Bash");
220        assert_eq!(lower, upper);
221        assert_eq!(lower, mixed);
222    }
223
224    #[test]
225    fn test_installation_instructions_unsupported() {
226        let instructions = print_installation_instructions("invalid");
227        assert_eq!(instructions, "Unsupported shell");
228    }
229}