cli/completion.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
use log::{error, info};
use std::io::BufRead;
use std::path::Path;
use std::{fs, io};
/// # Completions Module
///
/// This module handles the generation of shell completion scripts for the `cargo-make` tool.
///
/// ## Functionality
/// - `generate_completion_zsh`: Generates a Zsh completion script, creates the necessary directory,
/// and prompts for overwriting existing files.
///
/// ## Improvements to Consider
/// 1. **Modularity**: Separate the completion logic into different modules for different shells
/// (e.g., Zsh, Bash, Fish) to improve code organization.
/// 2. **Cross-Platform Support**: Abstract the completion generation into a trait or interface
/// to facilitate adding support for other shell types.
/// 3. **Enhanced Error Handling**: Provide more informative error messages for file operations.
/// 4. **User Input Handling**: Ensure user input is trimmed and handled correctly.
/// 5. **Testing**: Implement unit tests to verify the correct behavior of completion generation functions.
#[cfg(test)]
#[path = "completion_test.rs"]
mod completion_test;
pub fn generate_completions(shell: &str) -> Result<(), Box<dyn std::error::Error>> {
match shell {
"zsh" => {
generate_completion_zsh(None)?; // Use the `?` operator to propagate errors
Ok(()) // Return Ok if no error occurred
}
_ => {
// Return an error for unsupported shell
Err(Box::from(format!(
"Unsupported shell for completion: {}",
shell
)))
}
}
}
// Modify the function to accept an optional input stream
fn generate_completion_zsh(
input: Option<&mut dyn io::Read>,
) -> Result<(), Box<dyn std::error::Error>> {
let home_dir = std::env::var("HOME")?;
let zfunc_dir = format!("{}/.zfunc", home_dir);
let completion_file = format!("{}/_cargo-make", zfunc_dir);
if !Path::new(&zfunc_dir).exists() {
if let Err(e) = fs::create_dir_all(&zfunc_dir) {
error!("Failed to create directory {}: {}", zfunc_dir, e);
return Err(Box::new(e));
}
info!("Created directory: {}", zfunc_dir);
}
if Path::new(&completion_file).exists() {
let mut input_str = String::new();
let reader: Box<dyn io::Read> = match input {
Some(input) => Box::new(input),
None => Box::new(io::stdin()),
};
// Create a BufReader to read from the provided input or stdin
let mut buf_reader = io::BufReader::new(reader);
println!(
"File {} already exists. Overwrite? (y/n): ",
completion_file
);
buf_reader.read_line(&mut input_str)?;
if input_str.trim().to_lowercase() != "y" {
println!("Aborted overwriting the file.");
return Ok(());
}
}
let completion_script = r#"
#compdef cargo make cargo-make
_cargo_make() {
local tasks
local makefile="Makefile.toml"
if [[ ! -f $makefile ]]; then
return 1
fi
tasks=($(awk -F'[\\[\\.\\]]' '/^\[tasks/ {print $3}' "$makefile"))
if [[ ${#tasks[@]} -eq 0 ]]; then
return 1
fi
_describe -t tasks 'cargo-make tasks' tasks
}
_cargo_make "$@"
"#;
fs::write(&completion_file, completion_script)?;
println!("\nWrote tasks completion script to: {}", completion_file);
println!("To enable Zsh completion, add the following lines to your ~/.zshrc:\n");
println!(" fpath=(~/.zfunc $fpath)");
println!(" autoload -Uz compinit && compinit");
println!("\nThen, restart your terminal or run 'source ~/.zshrc'.");
Ok(())
}