refactor: data passing

This commit is contained in:
iff 2024-12-06 17:35:48 +01:00
parent 94d011cdd1
commit dae09adb76
5 changed files with 313 additions and 241 deletions

View file

@ -1,9 +1,10 @@
use crate::shell::initialization;
pub fn handle_args() {
// returns true if should exit
pub fn handle_args() -> bool {
let args = std::env::args().collect::<Vec<String>>();
if args.len() <= 1 {
return;
return false;
}
let mut auto_aliasing = String::new();
let mut shell = String::new();
@ -13,9 +14,11 @@ pub fn handle_args() {
match args[index].as_str() {
"-h" | "--help" => {
print_help();
return true;
}
"-v" | "--version" => {
print_version();
return true;
}
"-a" | "--alias" => {
if args.len() > index + 1 {
@ -43,12 +46,13 @@ pub fn handle_args() {
if shell.is_empty() {
eprintln!("{}", t!("no-shell"));
std::process::exit(1);
return true;
}
let binary_path = &args[0];
initialization(&shell, binary_path, &auto_aliasing, cnf);
return true;
}
fn print_help() {
@ -63,7 +67,6 @@ fn print_help() {
auto_example_fish = "pay-respects fish --alias | source",
)
);
std::process::exit(0);
}
fn print_version() {
@ -84,5 +87,4 @@ fn print_version() {
{
println!(" - libcurl");
}
std::process::exit(0);
}

View file

@ -37,9 +37,28 @@ mod requests;
extern crate rust_i18n;
i18n!("i18n", fallback = "en", minify_key = true);
fn main() {
fn main() -> Result<(), std::io::Error>{
colored::control::set_override(true);
// let locale = std::env::var("LANG").unwrap_or("en_US".to_string());
let mut data = {
let init = init();
if init.is_none() {
return Ok(());
} else {
init.unwrap()
}
};
data.expand_command();
use shell::Mode;
match data.mode {
Mode::Suggestion => modes::suggestion(&mut data),
Mode::CNF => modes::cnf(&mut data),
}
Ok(())
}
fn init() -> Option<shell::Data> {
let locale = {
let sys_locale = get_locale().unwrap_or("en-US".to_string());
if sys_locale.len() < 2 {
@ -50,6 +69,11 @@ fn main() {
};
rust_i18n::set_locale(&locale[0..2]);
let exit = args::handle_args();
if exit {
return None;
}
#[cfg(feature = "request-ai")]
{
if std::env::var("_PR_AI_LOCALE").is_err() {
@ -57,16 +81,5 @@ fn main() {
}
}
args::handle_args();
let mode = match std::env::var("_PR_MODE") {
Ok(mode) => mode,
Err(_) => "suggestion".to_string(),
};
match mode.as_str() {
"suggestion" => modes::suggestion(),
"cnf" => modes::cnf(),
_ => {}
}
Some(shell::Data::init())
}

View file

@ -1,32 +1,18 @@
use crate::shell::{command_output, get_shell, PRIVILEGE_LIST};
use crate::shell::Data;
use crate::style::highlight_difference;
use crate::suggestions::{best_match_path, split_command};
use crate::suggestions::{best_match_path, suggest_command};
use crate::system;
use crate::{shell, suggestions};
use colored::Colorize;
use inquire::*;
pub fn suggestion() {
let shell = get_shell();
let mut last_command = shell::last_command(&shell).trim().to_string();
last_command = shell::expand_alias(&shell, &last_command);
let mut error_msg = {
let error_msg = std::env::var("_PR_ERROR_MSG");
if let Ok(error_msg) = error_msg {
error_msg
} else {
command_output(&shell, &last_command)
}
};
error_msg = error_msg
.split_whitespace()
.collect::<Vec<&str>>()
.join(" ");
pub fn suggestion(data: &mut Data) {
let shell = data.shell.clone();
let last_command = data.command.clone();
loop {
let suggestion = {
let command = suggestions::suggest_command(&shell, &last_command, &error_msg);
let command = suggest_command(&data);
if command.is_none() {
break;
};
@ -35,6 +21,8 @@ pub fn suggestion() {
shell::shell_syntax(&shell, &mut command);
command
};
data.update_suggest(&suggestion);
data.expand_suggest();
let highlighted_suggestion = {
let difference = highlight_difference(&shell, &suggestion, &last_command);
@ -45,16 +33,13 @@ pub fn suggestion() {
};
let execution =
suggestions::confirm_suggestion(&shell, &suggestion, &highlighted_suggestion);
suggestions::confirm_suggestion(&data, &highlighted_suggestion);
if execution.is_ok() {
return;
} else {
last_command = suggestion;
error_msg = execution.err().unwrap();
error_msg = error_msg
.split_whitespace()
.collect::<Vec<&str>>()
.join(" ");
data.update_command(&suggestion);
let msg = Some(execution.err().unwrap().split_whitespace().collect::<Vec<&str>>().join(" "));
data.update_error(msg);
let retry_message = format!("{}...", t!("retry"));
@ -69,33 +54,24 @@ pub fn suggestion() {
);
}
pub fn cnf() {
let shell = get_shell();
let mut last_command = shell::last_command(&shell).trim().to_string();
last_command = shell::expand_alias(&shell, &last_command);
pub fn cnf(data: &mut Data) {
let shell = data.shell.clone();
let last_command = data.command.clone();
let mut split_command = data.split.clone();
let mut split_command = split_command(&last_command);
let executable = match PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
true => split_command.get(1).expect(&t!("no-command")).as_str(),
false => split_command.first().expect(&t!("no-command")).as_str(),
};
let executable = split_command[0].as_str();
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;
}
}
let suggestion = split_command.join(" ");
data.update_suggest(&suggestion);
data.expand_suggest();
let highlighted_suggestion =
highlight_difference(&shell, &suggestion, &last_command).unwrap();
let _ = suggestions::confirm_suggestion(&shell, &suggestion, &highlighted_suggestion);
let _ = suggestions::confirm_suggestion(&data, &highlighted_suggestion);
} else {
let package_manager = match system::get_package_manager(&shell) {
Some(package_manager) => package_manager,
@ -124,7 +100,7 @@ pub fn cnf() {
// retry after installing package
if system::install_package(&shell, &package_manager, &package) {
let _ = suggestions::confirm_suggestion(&shell, &last_command, &last_command);
let _ = suggestions::confirm_suggestion(&data, &last_command);
}
}
}

View file

@ -3,9 +3,137 @@ use std::process::exit;
use std::sync::mpsc::channel;
use std::thread;
use std::time::Duration;
use std::collections::HashMap;
use regex_lite::Regex;
pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
pub enum Mode {
Suggestion,
CNF,
}
pub struct Data {
pub shell: String,
pub command: String,
pub suggest: Option<String>,
pub split: Vec<String>,
pub alias: Option<HashMap<String, String>>,
pub privilege: Option<String>,
pub error: String,
pub mode: Mode,
}
pub struct Init {
pub shell: String,
pub binary_path: String,
pub auto_alias: String,
pub cnf: bool,
}
impl Data {
pub fn init() -> Data {
let shell = get_shell();
let command = last_command(&shell).trim().to_string();
let alias = alias_map(&shell);
let mode = run_mode();
let mut init = Data {
shell,
command,
suggest: None,
alias,
split: vec![],
privilege: None,
error: "".to_string(),
mode,
};
init.split();
init.update_error(None);
init
}
pub fn expand_command(&mut self) {
if self.alias.is_none() {
return;
}
let alias = self.alias.as_ref().unwrap();
if let Some(command) = expand_alias_multiline(alias, &self.command) {
self.update_command(&command);
}
}
pub fn expand_suggest(&mut self) {
if self.alias.is_none() {
return;
}
let alias = self.alias.as_ref().unwrap();
if let Some(suggest) = expand_alias_multiline(alias, &self.suggest.as_ref().unwrap()) {
self.update_suggest(&suggest);
}
}
pub fn split(&mut self) {
let mut split = split_command(&self.command);
if PRIVILEGE_LIST.contains(&split[0].as_str()) {
self.command = self.command.replacen(&split[0], "", 1);
self.privilege = Some(split.remove(0))
}
self.split = split;
}
pub fn update_error(&mut self, error: Option<String>) {
if let Some(error) = error {
self.error = error;
} else {
self.error = get_error(&self.shell, &self.command);
}
}
pub fn update_command(&mut self, command: &str) {
self.command = command.to_string();
self.split();
}
pub fn update_suggest(&mut self, suggest: &str) {
let split = split_command(&suggest);
if PRIVILEGE_LIST.contains(&split[0].as_str()) {
self.suggest = Some(suggest.replacen(&split[0], "", 1));
self.privilege = Some(split[0].clone())
} else {
self.suggest = Some(suggest.to_string());
};
}
}
pub fn split_command(command: &str) -> Vec<String> {
if cfg!(debug_assertions) {
eprintln!("command: {command}")
}
// this regex splits the command separated by spaces, except when the space
// is escaped by a backslash or surrounded by quotes
let regex = r#"([^\s"'\\]+|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\\ )+|\\|\n"#;
let regex = Regex::new(regex).unwrap();
let split_command = regex
.find_iter(command)
.map(|cap| cap.as_str().to_owned())
.collect::<Vec<String>>();
split_command
}
pub fn get_error(shell: &str, command: &str) -> String {
let error_msg = std::env::var("_PR_ERROR_MSG");
let error = if let Ok(error_msg) = error_msg {
std::env::remove_var("_PR_ERROR_MSG");
error_msg
} else {
command_output(shell, command)
};
error.split_whitespace().collect::<Vec<&str>>().join(" ")
}
pub fn command_output(shell: &str, command: &str) -> String {
let (sender, receiver) = channel();
@ -65,85 +193,93 @@ pub fn last_command(shell: &str) -> String {
}
}
pub fn expand_alias(shell: &str, full_command: &str) -> String {
let alias_env = std::env::var("_PR_ALIAS");
if alias_env.is_err() {
return full_command.to_string();
pub fn run_mode() -> Mode {
match std::env::var("_PR_MODE") {
Ok(mode) => {
match mode.as_str() {
"suggestion" => Mode::Suggestion,
"cnf" => Mode::CNF,
_ => Mode::Suggestion,
}
}
Err(_) => Mode::Suggestion,
}
let alias = alias_env.unwrap();
if alias.is_empty() {
return full_command.to_string();
}
let split_command = full_command.split_whitespace().collect::<Vec<&str>>();
let (command, pure_command) = if PRIVILEGE_LIST.contains(&split_command[0]) && split_command.len() > 1 {
(split_command[1], Some(split_command[1..].join(" ")))
} else {
(split_command[0], None)
};
let mut expanded_command = Option::None;
pub fn alias_map(shell: &str) -> Option<HashMap<String, String>> {
let env = std::env::var("_PR_ALIAS");
if env.is_err() {
return None;
}
let env = env.unwrap();
if env.is_empty() {
return None;
}
let mut alias_map = HashMap::new();
match shell {
"bash" => {
for line in alias.lines() {
if line.starts_with(format!("alias {}=", command).as_str()) {
let alias = line.replace(format!("alias {}='", command).as_str(), "");
let alias = alias.trim_end_matches('\'').trim_start_matches('\'');
expanded_command = Some(alias.to_string());
}
for line in env.lines() {
let alias = line.replace("alias ", "");
let (alias, command) = alias.split_once('=').unwrap();
let command = command.trim().trim_matches('\'');
alias_map.insert(alias.to_string(), command.to_string());
}
}
"zsh" => {
for line in alias.lines() {
if line.starts_with(format!("{}=", command).as_str()) {
let alias = line.replace(format!("{}=", command).as_str(), "");
let alias = alias.trim_start_matches('\'').trim_end_matches('\'');
expanded_command = Some(alias.to_string());
}
for line in env.lines() {
let (alias, command) = line.split_once('=').unwrap();
let command = command.trim().trim_matches('\'');
alias_map.insert(alias.to_string(), command.to_string());
}
}
"fish" => {
for line in alias.lines() {
if line.starts_with(format!("alias {} ", command).as_str()) {
let alias = line.replace(format!("alias {} ", command).as_str(), "");
let alias = alias.trim_start_matches('\'').trim_end_matches('\'');
expanded_command = Some(alias.to_string());
}
for line in env.lines() {
let alias = line.replace("alias ", "");
let (alias, command) = alias.split_once(' ').unwrap();
let command = command.trim().trim_matches('\'');
alias_map.insert(alias.to_string(), command.to_string());
}
}
_ => {
eprintln!("Unsupported shell: {}", shell);
exit(1);
unreachable!("Unsupported shell: {}", shell);
}
}
Some(alias_map)
}
pub fn expand_alias(map: &HashMap<String, String>, command: &str) -> Option<String> {
#[cfg(debug_assertions)]
eprintln!("expand_alias: command: {}", command);
let command = if let Some(split) = command.split_once(' ') {
split.0
} else {
command
};
if expanded_command.is_none() {
return full_command.to_string();
};
let expanded_command = expanded_command.unwrap();
if pure_command.is_some() {
let pure_command = pure_command.unwrap();
if pure_command.starts_with(&expanded_command) {
return full_command.to_string();
if let Some(expand) = map.get(command) {
Some(command.replacen(&command, expand, 1))
} else {
None
}
}
full_command.replacen(command, &expanded_command, 1)
}
pub fn expand_alias_multiline(shell: &str, full_command: &str) -> String {
let lines = full_command.lines().collect::<Vec<&str>>();
pub fn expand_alias_multiline(map: &HashMap<String, String>, command: &str) -> Option<String> {
let lines = command.lines().collect::<Vec<&str>>();
let mut expanded = String::new();
let mut expansion = false;
for line in lines {
expanded = format!("{}\n{}", expanded, expand_alias(shell, line));
if let Some(expand) = expand_alias(&map, line){
expanded = format!("{}\n{}", expanded, expand);
expansion = true;
} else {
expanded = format!("{}\n{}", expanded, line);
}
}
if expansion {
Some(expanded)
} else {
None
}
expanded
}
pub fn initialization(shell: &str, binary_path: &str, auto_alias: &str, cnf: bool) {
@ -173,7 +309,7 @@ pub fn initialization(shell: &str, binary_path: &str, auto_alias: &str, cnf: boo
}
_ => {
println!("Unknown shell: {}", shell);
std::process::exit(1);
return;
}
}
@ -194,7 +330,7 @@ def --env {} [] {{
pr_alias, last_command, binary_path
);
println!("{}", init);
std::process::exit(0);
return;
}
let mut init = match shell {
@ -233,12 +369,12 @@ def --env {} [] {{
),
_ => {
println!("Unsupported shell: {}", shell);
exit(1);
return;
}
};
if auto_alias.is_empty() {
println!("{}", init);
std::process::exit(0);
return;
}
match shell {
@ -264,7 +400,7 @@ end
}
_ => {
println!("Unsupported shell: {}", shell);
exit(1);
return;
}
}
@ -332,14 +468,12 @@ $ExecutionContext.InvokeCommand.CommandNotFoundAction =
}
_ => {
println!("Unsupported shell: {}", shell);
exit(1);
return;
}
}
}
println!("{}", init);
std::process::exit(0);
}
pub fn get_shell() -> String {

View file

@ -7,52 +7,39 @@ use regex_lite::Regex;
use crate::files::{get_best_match_file, get_path_files};
use crate::rules::match_pattern;
use crate::shell::{expand_alias_multiline, shell_evaluated_commands, PRIVILEGE_LIST};
use crate::shell::{Data, shell_evaluated_commands};
pub fn suggest_command(shell: &str, last_command: &str, error_msg: &str) -> Option<String> {
let split_command = split_command(last_command);
let executable = match PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
true => split_command.get(1).expect(&t!("no-command")).as_str(),
false => split_command.first().expect(&t!("no-command")).as_str(),
};
pub fn suggest_command(data: &Data) -> Option<String> {
let shell = &data.shell;
let command = &data.command;
let split_command = &data.split;
let executable = data.split[0].as_str();
let error = &data.error;
let privilege = &data.privilege;
if !PRIVILEGE_LIST.contains(&executable) {
let suggest = match_pattern("_PR_privilege", last_command, error_msg, shell);
if privilege.is_none() {
let suggest = match_pattern("_PR_privilege", command, error, shell);
if suggest.is_some() {
return suggest;
}
}
let last_command = match PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
true => &last_command[split_command[0].len() + 1..],
false => last_command,
};
let suggest = match_pattern(executable, last_command, error_msg, shell);
if let Some(suggest) = suggest {
if PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
return Some(format!("{} {}", split_command[0], suggest));
}
return Some(suggest);
let suggest = match_pattern(executable, command, error, shell);
if suggest.is_some() {
return suggest;
}
let suggest = match_pattern("_PR_general", last_command, error_msg, shell);
if let Some(suggest) = suggest {
if PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
return Some(format!("{} {}", split_command[0], suggest));
}
return Some(suggest);
let suggest = match_pattern("_PR_general", command, error, shell);
if suggest.is_some() {
return suggest;
}
#[cfg(feature = "runtime-rules")]
{
use crate::runtime_rules::runtime_match;
let suggest = runtime_match(executable, last_command, error_msg, shell);
if let Some(suggest) = suggest {
if PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
return Some(format!("{} {}", split_command[0], suggest));
}
return Some(suggest);
let suggest = runtime_match(executable, command, error, shell);
if suggest.is_some() {
return suggest;
}
}
@ -63,25 +50,20 @@ 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
if privilege.is_some() && split_command.len() > 2 ||
privilege.is_none() && split_command.len() > 1
{
let suggest = ai_suggestion(last_command, error_msg);
let suggest = ai_suggestion(command, error);
if let Some(suggest) = suggest {
let warn = format!("{}:", t!("ai-suggestion")).bold().blue();
let note = fill(&suggest.note, termwidth());
eprintln!("{}\n{}\n", warn, note);
let command = suggest.command;
if command != "None" {
if PRIVILEGE_LIST.contains(&split_command[0].as_str()) {
return Some(format!("{} {}", split_command[0], command));
}
return Some(command);
}
}
}
}
None
}
@ -262,56 +244,17 @@ pub fn compare_string(a: &str, b: &str) -> usize {
matrix[a.chars().count()][b.chars().count()]
}
pub fn confirm_suggestion(shell: &str, command: &str, highlighted: &str) -> Result<(), String> {
pub fn confirm_suggestion(data: &Data, highlighted: &str) -> Result<(), String> {
eprintln!("{}\n", highlighted);
let confirm = format!("[{}]", t!("confirm-yes")).green();
eprintln!("{}: {} {}", t!("confirm"), confirm, "[Ctrl+C]".red());
std::io::stdin().read_line(&mut String::new()).unwrap();
for p in PRIVILEGE_LIST {
let _p = p.to_owned() + " ";
if !command.starts_with(&_p) {
continue;
}
let command = {
let mut command = command.replacen(&_p, "", 1);
if command != " " {
command = expand_alias_multiline(shell, &command);
}
command
};
let shell = &data.shell;
let command = &data.suggest.clone().unwrap();
let now = Instant::now();
let process = run_suggestion_p(shell, p, &command);
if process.success() {
let cd = shell_evaluated_commands(shell, &command);
if let Some(cd) = cd {
println!("{}", cd);
}
} else {
if now.elapsed() > Duration::from_secs(3) {
exit(1);
}
let process = std::process::Command::new(p)
.arg(shell)
.arg("-c")
.arg(command)
.env("LC_ALL", "C")
.output()
.expect("failed to execute process");
let error_msg = match process.stderr.is_empty() {
true => String::from_utf8_lossy(&process.stdout),
false => String::from_utf8_lossy(&process.stderr),
};
return Err(error_msg.to_string());
}
}
let command = expand_alias_multiline(shell, command);
let now = Instant::now();
let process = run_suggestion(shell, &command);
let process = run_suggestion(data, &command);
if process.success() {
let cd = shell_evaluated_commands(shell, &command);
@ -337,8 +280,12 @@ pub fn confirm_suggestion(shell: &str, command: &str, highlighted: &str) -> Resu
}
}
fn run_suggestion_p(shell: &str, p: &str, command: &str) -> std::process::ExitStatus {
std::process::Command::new(p)
fn run_suggestion(data: &Data, command: &str) -> std::process::ExitStatus {
let shell = &data.shell;
let privilege = &data.privilege;
match privilege {
Some(sudo) => {
std::process::Command::new(sudo)
.arg(shell)
.arg("-c")
.arg(command)
@ -349,12 +296,10 @@ fn run_suggestion_p(shell: &str, p: &str, command: &str) -> std::process::ExitSt
.wait()
.unwrap()
}
fn run_suggestion(shell: &str, command: &str) -> std::process::ExitStatus {
None => {
std::process::Command::new(shell)
.arg("-c")
.arg(command)
// .stdout(Stdio::inherit())
.stdout(stderr())
.stderr(Stdio::inherit())
.spawn()
@ -362,3 +307,5 @@ fn run_suggestion(shell: &str, command: &str) -> std::process::ExitStatus {
.wait()
.unwrap()
}
}
}