Skip to main content

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}