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