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}