mirror of
https://github.com/TECHNOFAB11/pay-respects.git
synced 2025-12-12 06:20:09 +01:00
feat: prompt to install missing command
This commit is contained in:
parent
ab3534f0ef
commit
c99c736ad5
7 changed files with 351 additions and 23 deletions
|
|
@ -130,7 +130,7 @@ pub fn get_best_match_file(input: &str) -> Option<String> {
|
|||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let best_match = find_similar(&exit_dir, &dir_files);
|
||||
let best_match = find_similar(&exit_dir, &dir_files, Some(1));
|
||||
best_match.as_ref()?;
|
||||
|
||||
input = format!("{}/{}", input, best_match.unwrap());
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ mod rules;
|
|||
mod shell;
|
||||
mod style;
|
||||
mod suggestions;
|
||||
mod system;
|
||||
|
||||
#[cfg(feature = "runtime-rules")]
|
||||
mod replaces;
|
||||
|
|
|
|||
64
src/modes.rs
64
src/modes.rs
|
|
@ -1,8 +1,10 @@
|
|||
use crate::shell::{command_output, get_shell, PRIVILEGE_LIST};
|
||||
use crate::style::highlight_difference;
|
||||
use crate::suggestions::{split_command, suggest_typo};
|
||||
use crate::suggestions::{best_match_path, split_command};
|
||||
use crate::system;
|
||||
use crate::{shell, suggestions};
|
||||
use colored::Colorize;
|
||||
use inquire::*;
|
||||
|
||||
pub fn suggestion() {
|
||||
let shell = get_shell();
|
||||
|
|
@ -78,21 +80,51 @@ pub fn cnf() {
|
|||
false => split_command.first().expect(&t!("no-command")).as_str(),
|
||||
};
|
||||
|
||||
let best_match = suggest_typo(&[executable.to_owned()], vec!["path".to_string()]);
|
||||
if best_match == executable {
|
||||
eprintln!("{}: command not found: {}", shell, executable);
|
||||
return;
|
||||
}
|
||||
match PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
|
||||
true => {
|
||||
split_command[1] = best_match;
|
||||
let best_match = best_match_path(executable);
|
||||
if best_match.is_some() {
|
||||
let best_match = best_match.unwrap();
|
||||
match PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
|
||||
true => {
|
||||
split_command[1] = best_match;
|
||||
}
|
||||
false => {
|
||||
split_command[0] = best_match;
|
||||
}
|
||||
}
|
||||
false => {
|
||||
split_command[0] = best_match;
|
||||
}
|
||||
}
|
||||
let suggestion = split_command.join(" ");
|
||||
let suggestion = split_command.join(" ");
|
||||
|
||||
let highlighted_suggestion = highlight_difference(&shell, &suggestion, &last_command).unwrap();
|
||||
let _ = suggestions::confirm_suggestion(&shell, &suggestion, &highlighted_suggestion);
|
||||
let highlighted_suggestion =
|
||||
highlight_difference(&shell, &suggestion, &last_command).unwrap();
|
||||
let _ = suggestions::confirm_suggestion(&shell, &suggestion, &highlighted_suggestion);
|
||||
} else {
|
||||
let package_manager = match system::get_package_manager(&shell) {
|
||||
Some(package_manager) => package_manager,
|
||||
None => {
|
||||
eprintln!("no package manager found");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let packages = match system::get_packages(&shell, &package_manager, executable) {
|
||||
Some(packages) => packages,
|
||||
None => {
|
||||
eprintln!("no package found");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let ans = Select::new("Select a package to install", packages).prompt();
|
||||
let package = match ans {
|
||||
Ok(package) => package,
|
||||
Err(_) => {
|
||||
eprintln!("no package selected");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// retry after installing package
|
||||
if system::install_package(&shell, &package_manager, &package) {
|
||||
let _ = suggestions::confirm_suggestion(&shell, &last_command, &last_command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ pub fn suggest_command(shell: &str, last_command: &str, error_msg: &str) -> Opti
|
|||
// skip for commands with no arguments,
|
||||
// very likely to be an error showing the usage
|
||||
if PRIVILEGE_LIST.contains(&split_command[0].as_str()) && split_command.len() > 2
|
||||
|| !PRIVILEGE_LIST.contains(&split_command[0].as_str()) && split_command.len() > 1 {
|
||||
|| !PRIVILEGE_LIST.contains(&split_command[0].as_str()) && split_command.len() > 1
|
||||
{
|
||||
let suggest = ai_suggestion(last_command, error_msg);
|
||||
if let Some(suggest) = suggest {
|
||||
let warn = format!("{}:", t!("ai-suggestion")).bold().blue();
|
||||
|
|
@ -184,7 +185,7 @@ pub fn suggest_typo(typos: &[String], candidates: Vec<String>) -> String {
|
|||
if path_files.is_empty() {
|
||||
path_files = get_path_files();
|
||||
};
|
||||
if let Some(suggest) = find_similar(typo, &path_files) {
|
||||
if let Some(suggest) = find_similar(typo, &path_files, Some(2)) {
|
||||
suggestions.push(suggest);
|
||||
} else {
|
||||
suggestions.push(typo.to_string());
|
||||
|
|
@ -199,7 +200,7 @@ pub fn suggest_typo(typos: &[String], candidates: Vec<String>) -> String {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if let Some(suggest) = find_similar(typo, &candidates) {
|
||||
} else if let Some(suggest) = find_similar(typo, &candidates, Some(2)) {
|
||||
suggestions.push(suggest);
|
||||
} else {
|
||||
suggestions.push(typo.to_string());
|
||||
|
|
@ -208,8 +209,19 @@ pub fn suggest_typo(typos: &[String], candidates: Vec<String>) -> String {
|
|||
suggestions.join(" ")
|
||||
}
|
||||
|
||||
pub fn find_similar(typo: &str, candidates: &[String]) -> Option<String> {
|
||||
let mut min_distance = { std::cmp::max(2, typo.chars().count() / 2 + 1) };
|
||||
pub fn best_match_path(typo: &str) -> Option<String> {
|
||||
let path_files = get_path_files();
|
||||
find_similar(typo, &path_files, Some(3))
|
||||
}
|
||||
|
||||
// higher the threshold, the stricter the comparison
|
||||
// 1: anything
|
||||
// 2: 50% similarity
|
||||
// 3: 33% similarity
|
||||
// ... etc
|
||||
pub fn find_similar(typo: &str, candidates: &[String], threshold: Option<usize>) -> Option<String> {
|
||||
let threshold = threshold.unwrap_or(2);
|
||||
let mut min_distance = typo.chars().count() / threshold + 1;
|
||||
let mut min_distance_index = None;
|
||||
for (i, candidate) in candidates.iter().enumerate() {
|
||||
if candidate.is_empty() {
|
||||
|
|
@ -344,4 +356,3 @@ fn run_suggestion(shell: &str, command: &str) -> std::process::ExitStatus {
|
|||
.wait()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
|
|
|||
60
src/system.rs
Normal file
60
src/system.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use std::io::stderr;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
|
||||
pub fn get_package_manager(shell: &str) -> Option<String> {
|
||||
let package_managers = vec!["pacman", "apt", "dnf"];
|
||||
|
||||
for package_manager in package_managers {
|
||||
let success = Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(format!("command -v {}", package_manager))
|
||||
.output()
|
||||
.expect("failed to execute process")
|
||||
.status
|
||||
.success();
|
||||
|
||||
if success {
|
||||
return Some(package_manager.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_packages(shell: &str, package_manager: &str, executable: &str) -> Option<Vec<String>> {
|
||||
match package_manager {
|
||||
"pacman" => {
|
||||
let result = Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(format!("pacman -Fq /usr/bin/{}", executable))
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
if result.status.success() {
|
||||
let output = String::from_utf8_lossy(&result.stdout)
|
||||
.lines()
|
||||
.map(|line| line.split_whitespace().next().unwrap().to_string())
|
||||
.collect();
|
||||
Some(output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Unsupported package manager"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install_package(shell: &str, package_manager: &str, package: &str) -> bool {
|
||||
match package_manager {
|
||||
"pacman" => Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(format!("sudo pacman -S {}", package))
|
||||
.stdout(stderr())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.expect("failed to execute process")
|
||||
.wait()
|
||||
.unwrap()
|
||||
.success(),
|
||||
_ => unreachable!("Unsupported package manager"),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue