mirror of
https://github.com/TECHNOFAB11/pay-respects.git
synced 2025-12-11 22:10:09 +01:00
feat: config file
This commit is contained in:
parent
6ba4591db9
commit
2699238bb1
16 changed files with 452 additions and 286 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -912,7 +912,9 @@ dependencies = [
|
||||||
"pay-respects-utils",
|
"pay-respects-utils",
|
||||||
"regex-lite",
|
"regex-lite",
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
|
"serde",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
24
config.md
Normal file
24
config.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Configuration File
|
||||||
|
|
||||||
|
Configuration file for `pay-respects` is located at:
|
||||||
|
|
||||||
|
- `$HOME/.config/pay-respects/config.toml` (*nix)
|
||||||
|
- `%APPDATA%/pay-respects/config.toml` (Windows)
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
All available options are listed in the following example file:
|
||||||
|
```toml
|
||||||
|
# maximum time in milliseconds for getting previous output
|
||||||
|
timeout = 3000
|
||||||
|
# your preferred command for privileges
|
||||||
|
sudo = "run0"
|
||||||
|
|
||||||
|
[package_manager]
|
||||||
|
# preferred package manager
|
||||||
|
package_manager = "pacman"
|
||||||
|
|
||||||
|
# preferred installation method, can be limited with the package manager
|
||||||
|
# available options are: System, User, Temp
|
||||||
|
install_method = "System"
|
||||||
|
```
|
||||||
|
|
@ -22,6 +22,10 @@ askama = "0.13"
|
||||||
|
|
||||||
inquire = "0.7"
|
inquire = "0.7"
|
||||||
|
|
||||||
|
# config file
|
||||||
|
toml = { version = "0.8" }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
pay-respects-parser = { version = "0.3", path = "../parser" }
|
pay-respects-parser = { version = "0.3", path = "../parser" }
|
||||||
pay-respects-utils = { version ="0.1", path = "../utils"}
|
pay-respects-utils = { version ="0.1", path = "../utils"}
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::shell::{initialization, Init};
|
use crate::{init::Init, shell::initialization};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
|
|
|
||||||
67
core/src/config.rs
Normal file
67
core/src/config.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub sudo: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub timeout: Timeout,
|
||||||
|
#[serde(default)]
|
||||||
|
pub package_manager: PackageManagerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PackageManagerConfig {
|
||||||
|
pub package_manager: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub install_method: InstallMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Timeout(pub u64);
|
||||||
|
impl Default for Timeout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Timeout(3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum InstallMethod {
|
||||||
|
#[default]
|
||||||
|
System,
|
||||||
|
User,
|
||||||
|
Temp,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config() -> Config {
|
||||||
|
let path = config_path();
|
||||||
|
let exists = std::path::Path::new(&path).exists();
|
||||||
|
if exists {
|
||||||
|
let content = std::fs::read_to_string(&path).expect("Failed to read config file");
|
||||||
|
let config: Config = toml::from_str(&content).unwrap_or_else(|_| {
|
||||||
|
eprintln!(
|
||||||
|
"Failed to parse config file at {}. Using default configuration.",
|
||||||
|
path
|
||||||
|
);
|
||||||
|
Config::default()
|
||||||
|
});
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_path() -> String {
|
||||||
|
#[cfg(windows)]
|
||||||
|
let xdg_config_home = std::env::var("APPDATA").unwrap();
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let xdg_config_home = std::env::var("XDG_CONFIG_HOME")
|
||||||
|
.unwrap_or_else(|_| std::env::var("HOME").unwrap() + "/.config");
|
||||||
|
|
||||||
|
format!("{}/pay-respects/config.toml", xdg_config_home)
|
||||||
|
}
|
||||||
288
core/src/data.rs
Normal file
288
core/src/data.rs
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
use pay_respects_utils::evals::split_command;
|
||||||
|
use pay_respects_utils::files::get_path_files;
|
||||||
|
use pay_respects_utils::files::path_env_sep;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use pay_respects_utils::files::path_convert;
|
||||||
|
|
||||||
|
use crate::config::load_config;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::shell::alias_map;
|
||||||
|
use crate::shell::builtin_commands;
|
||||||
|
use crate::shell::expand_alias_multiline;
|
||||||
|
use crate::shell::get_error;
|
||||||
|
use crate::shell::get_shell;
|
||||||
|
use crate::shell::last_command;
|
||||||
|
use crate::shell::run_mode;
|
||||||
|
|
||||||
|
pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum Mode {
|
||||||
|
Suggestion,
|
||||||
|
Echo,
|
||||||
|
NoConfirm,
|
||||||
|
Cnf,
|
||||||
|
}
|
||||||
|
pub struct Data {
|
||||||
|
pub shell: String,
|
||||||
|
pub env: Option<String>,
|
||||||
|
pub command: String,
|
||||||
|
pub suggest: Option<String>,
|
||||||
|
pub candidates: Vec<String>,
|
||||||
|
pub split: Vec<String>,
|
||||||
|
pub alias: Option<HashMap<String, String>>,
|
||||||
|
pub privilege: Option<String>,
|
||||||
|
pub error: String,
|
||||||
|
pub executables: Vec<String>,
|
||||||
|
pub modules: Vec<String>,
|
||||||
|
pub fallbacks: Vec<String>,
|
||||||
|
pub config: Config,
|
||||||
|
pub mode: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 executables, modules, fallbacks);
|
||||||
|
let lib_dir = {
|
||||||
|
if let Ok(lib_dir) = std::env::var("_PR_LIB") {
|
||||||
|
Some(lib_dir)
|
||||||
|
} else {
|
||||||
|
option_env!("_DEF_PR_LIB").map(|dir| dir.to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
eprintln!("lib_dir: {:?}", lib_dir);
|
||||||
|
|
||||||
|
if lib_dir.is_none() {
|
||||||
|
(executables, modules, fallbacks) = {
|
||||||
|
let path_executables = get_path_files();
|
||||||
|
let mut executables = vec![];
|
||||||
|
let mut modules = vec![];
|
||||||
|
let mut fallbacks = vec![];
|
||||||
|
for exe in path_executables {
|
||||||
|
if exe.starts_with("_pay-respects-module-") {
|
||||||
|
modules.push(exe.to_string());
|
||||||
|
} else if exe.starts_with("_pay-respects-fallback-") {
|
||||||
|
fallbacks.push(exe.to_string());
|
||||||
|
} else {
|
||||||
|
executables.push(exe.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modules.sort_unstable();
|
||||||
|
fallbacks.sort_unstable();
|
||||||
|
if alias.is_some() {
|
||||||
|
let alias = alias.as_ref().unwrap();
|
||||||
|
for command in alias.keys() {
|
||||||
|
if executables.contains(command) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
executables.push(command.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(executables, modules, fallbacks)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
(executables, modules, fallbacks) = {
|
||||||
|
let mut modules = vec![];
|
||||||
|
let mut fallbacks = vec![];
|
||||||
|
let lib_dir = lib_dir.unwrap();
|
||||||
|
let mut executables = get_path_files();
|
||||||
|
if alias.is_some() {
|
||||||
|
let alias = alias.as_ref().unwrap();
|
||||||
|
for command in alias.keys() {
|
||||||
|
if executables.contains(command) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
executables.push(command.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = lib_dir.split(path_env_sep()).collect::<Vec<&str>>();
|
||||||
|
|
||||||
|
for p in path {
|
||||||
|
#[cfg(windows)]
|
||||||
|
let p = path_convert(p);
|
||||||
|
|
||||||
|
let files = match std::fs::read_dir(p) {
|
||||||
|
Ok(files) => files,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
for file in files {
|
||||||
|
let file = file.unwrap();
|
||||||
|
let file_name = file.file_name().into_string().unwrap();
|
||||||
|
let file_path = file.path();
|
||||||
|
|
||||||
|
if file_name.starts_with("_pay-respects-module-") {
|
||||||
|
modules.push(file_path.to_string_lossy().to_string());
|
||||||
|
} else if file_name.starts_with("_pay-respects-fallback-") {
|
||||||
|
fallbacks.push(file_path.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modules.sort_unstable();
|
||||||
|
fallbacks.sort_unstable();
|
||||||
|
|
||||||
|
(executables, modules, fallbacks)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let builtins = builtin_commands(&shell);
|
||||||
|
executables.extend(builtins.clone());
|
||||||
|
executables = executables.iter().unique().cloned().collect();
|
||||||
|
let config = load_config();
|
||||||
|
|
||||||
|
let mut init = Data {
|
||||||
|
shell,
|
||||||
|
env: None,
|
||||||
|
command,
|
||||||
|
suggest: None,
|
||||||
|
candidates: vec![],
|
||||||
|
alias,
|
||||||
|
split: vec![],
|
||||||
|
privilege: None,
|
||||||
|
error: "".to_string(),
|
||||||
|
executables,
|
||||||
|
modules,
|
||||||
|
fallbacks,
|
||||||
|
config,
|
||||||
|
mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
init.split();
|
||||||
|
init.extract_env();
|
||||||
|
init.expand_command();
|
||||||
|
if init.mode != Mode::Cnf {
|
||||||
|
init.update_error(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
eprintln!("/// data initialization");
|
||||||
|
eprintln!("shell: {}", init.shell);
|
||||||
|
eprintln!("env: {:?}", init.env);
|
||||||
|
eprintln!("command: {}", init.command);
|
||||||
|
eprintln!("error: {}", init.error);
|
||||||
|
eprintln!("modules: {:?}", init.modules);
|
||||||
|
eprintln!("fallbacks: {:?}", init.fallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
eprintln!("expand_command: {}", 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()) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
eprintln!("expand_suggest: {}", suggest);
|
||||||
|
self.update_suggest(&suggest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split(&mut self) {
|
||||||
|
self.extract_privilege();
|
||||||
|
let split = split_command(&self.command);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
eprintln!("split: {:?}", split);
|
||||||
|
if split.is_empty() {
|
||||||
|
eprintln!("{}", t!("empty-command"));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
self.split = split;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_privilege(&mut self) {
|
||||||
|
let command = {
|
||||||
|
let first = self.command.split_whitespace().next();
|
||||||
|
if let Some(first) = first {
|
||||||
|
first.to_string()
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(sudo) = self.config.sudo.as_ref() {
|
||||||
|
if command == *sudo {
|
||||||
|
self.privilege = Some(command.to_string());
|
||||||
|
self.command = self.command.replacen(sudo, "", 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if PRIVILEGE_LIST.contains(&command.as_str()) {
|
||||||
|
self.privilege = Some(command.to_string());
|
||||||
|
self.command = self.command.replacen(&self.split[0], "", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_env(&mut self) {
|
||||||
|
let mut envs = vec![];
|
||||||
|
loop {
|
||||||
|
let mut char = self.split[0].char_indices();
|
||||||
|
char.next();
|
||||||
|
let offset = char.offset();
|
||||||
|
if self.split[0][offset..].contains("=") {
|
||||||
|
envs.push(self.split.remove(0));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !envs.is_empty() {
|
||||||
|
self.env = Some(envs.join(" "));
|
||||||
|
self.command = self.split.join(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_error(&mut self, error: Option<String>) {
|
||||||
|
if let Some(error) = error {
|
||||||
|
self.error = error
|
||||||
|
.to_lowercase()
|
||||||
|
.split_whitespace()
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(" ");
|
||||||
|
} else {
|
||||||
|
self.error = get_error(&self.shell, &self.command, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
17
core/src/init.rs
Normal file
17
core/src/init.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
pub struct Init {
|
||||||
|
pub shell: String,
|
||||||
|
pub binary_path: String,
|
||||||
|
pub alias: String,
|
||||||
|
pub cnf: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Init {
|
||||||
|
pub fn new() -> Init {
|
||||||
|
Init {
|
||||||
|
shell: String::from(""),
|
||||||
|
binary_path: String::from(""),
|
||||||
|
alias: String::from("f"),
|
||||||
|
cnf: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,9 @@ use std::env;
|
||||||
use sys_locale::get_locale;
|
use sys_locale::get_locale;
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
mod config;
|
||||||
|
mod data;
|
||||||
|
mod init;
|
||||||
mod modes;
|
mod modes;
|
||||||
mod rules;
|
mod rules;
|
||||||
mod shell;
|
mod shell;
|
||||||
|
|
@ -51,7 +54,7 @@ fn main() -> Result<(), std::io::Error> {
|
||||||
init.ok().unwrap()
|
init.ok().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
use shell::Mode::*;
|
use data::Mode::*;
|
||||||
match data.mode {
|
match data.mode {
|
||||||
Suggestion => modes::suggestion(&mut data),
|
Suggestion => modes::suggestion(&mut data),
|
||||||
Echo => modes::echo(&mut data),
|
Echo => modes::echo(&mut data),
|
||||||
|
|
@ -62,7 +65,7 @@ fn main() -> Result<(), std::io::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init() -> Result<shell::Data, args::Status> {
|
fn init() -> Result<data::Data, args::Status> {
|
||||||
let locale = {
|
let locale = {
|
||||||
let sys_locale = {
|
let sys_locale = {
|
||||||
// use terminal locale if available
|
// use terminal locale if available
|
||||||
|
|
@ -95,5 +98,5 @@ fn init() -> Result<shell::Data, args::Status> {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(shell::Data::init())
|
Ok(data::Data::init())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ use ui::Color;
|
||||||
use pay_respects_utils::evals::best_matches_path;
|
use pay_respects_utils::evals::best_matches_path;
|
||||||
use pay_respects_utils::files::best_match_file;
|
use pay_respects_utils::files::best_match_file;
|
||||||
|
|
||||||
use crate::shell::{shell_evaluated_commands, Data};
|
use crate::data::Data;
|
||||||
|
use crate::shell::shell_evaluated_commands;
|
||||||
use crate::style::highlight_difference;
|
use crate::style::highlight_difference;
|
||||||
use crate::suggestions;
|
use crate::suggestions;
|
||||||
use crate::suggestions::suggest_candidates;
|
use crate::suggestions::suggest_candidates;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::shell::Data;
|
use crate::data::Data;
|
||||||
use pay_respects_parser::parse_rules;
|
use pay_respects_parser::parse_rules;
|
||||||
use pay_respects_utils::evals::*;
|
use pay_respects_utils::evals::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
use pay_respects_utils::evals::split_command;
|
|
||||||
use pay_respects_utils::files::get_path_files;
|
|
||||||
use pay_respects_utils::files::path_env_sep;
|
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use std::process::{exit, Stdio};
|
use std::process::{exit, Stdio};
|
||||||
|
|
||||||
|
|
@ -15,269 +10,19 @@ use std::time::Duration;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use pay_respects_utils::files::path_convert;
|
use pay_respects_utils::files::path_convert;
|
||||||
|
|
||||||
pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
|
use crate::data::{Data, Mode};
|
||||||
|
use crate::init::Init;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
|
||||||
pub enum Mode {
|
|
||||||
Suggestion,
|
|
||||||
Echo,
|
|
||||||
NoConfirm,
|
|
||||||
Cnf,
|
|
||||||
}
|
|
||||||
pub struct Init {
|
|
||||||
pub shell: String,
|
|
||||||
pub binary_path: String,
|
|
||||||
pub alias: String,
|
|
||||||
pub cnf: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Init {
|
|
||||||
pub fn new() -> Init {
|
|
||||||
Init {
|
|
||||||
shell: String::from(""),
|
|
||||||
binary_path: String::from(""),
|
|
||||||
alias: String::from("f"),
|
|
||||||
cnf: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Data {
|
|
||||||
pub shell: String,
|
|
||||||
pub env: Option<String>,
|
|
||||||
pub command: String,
|
|
||||||
pub suggest: Option<String>,
|
|
||||||
pub candidates: Vec<String>,
|
|
||||||
pub split: Vec<String>,
|
|
||||||
pub alias: Option<HashMap<String, String>>,
|
|
||||||
pub privilege: Option<String>,
|
|
||||||
pub error: String,
|
|
||||||
pub executables: Vec<String>,
|
|
||||||
pub modules: Vec<String>,
|
|
||||||
pub fallbacks: Vec<String>,
|
|
||||||
pub mode: Mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 executables, modules, fallbacks);
|
|
||||||
let lib_dir = {
|
|
||||||
if let Ok(lib_dir) = std::env::var("_PR_LIB") {
|
|
||||||
Some(lib_dir)
|
|
||||||
} else {
|
|
||||||
option_env!("_DEF_PR_LIB").map(|dir| dir.to_string())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
eprintln!("lib_dir: {:?}", lib_dir);
|
|
||||||
|
|
||||||
if lib_dir.is_none() {
|
|
||||||
(executables, modules, fallbacks) = {
|
|
||||||
let path_executables = get_path_files();
|
|
||||||
let mut executables = vec![];
|
|
||||||
let mut modules = vec![];
|
|
||||||
let mut fallbacks = vec![];
|
|
||||||
for exe in path_executables {
|
|
||||||
if exe.starts_with("_pay-respects-module-") {
|
|
||||||
modules.push(exe.to_string());
|
|
||||||
} else if exe.starts_with("_pay-respects-fallback-") {
|
|
||||||
fallbacks.push(exe.to_string());
|
|
||||||
} else {
|
|
||||||
executables.push(exe.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modules.sort_unstable();
|
|
||||||
fallbacks.sort_unstable();
|
|
||||||
if alias.is_some() {
|
|
||||||
let alias = alias.as_ref().unwrap();
|
|
||||||
for command in alias.keys() {
|
|
||||||
if executables.contains(command) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
executables.push(command.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(executables, modules, fallbacks)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
(executables, modules, fallbacks) = {
|
|
||||||
let mut modules = vec![];
|
|
||||||
let mut fallbacks = vec![];
|
|
||||||
let lib_dir = lib_dir.unwrap();
|
|
||||||
let mut executables = get_path_files();
|
|
||||||
if alias.is_some() {
|
|
||||||
let alias = alias.as_ref().unwrap();
|
|
||||||
for command in alias.keys() {
|
|
||||||
if executables.contains(command) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
executables.push(command.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = lib_dir.split(path_env_sep()).collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
for p in path {
|
|
||||||
#[cfg(windows)]
|
|
||||||
let p = path_convert(p);
|
|
||||||
|
|
||||||
let files = match std::fs::read_dir(p) {
|
|
||||||
Ok(files) => files,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
for file in files {
|
|
||||||
let file = file.unwrap();
|
|
||||||
let file_name = file.file_name().into_string().unwrap();
|
|
||||||
let file_path = file.path();
|
|
||||||
|
|
||||||
if file_name.starts_with("_pay-respects-module-") {
|
|
||||||
modules.push(file_path.to_string_lossy().to_string());
|
|
||||||
} else if file_name.starts_with("_pay-respects-fallback-") {
|
|
||||||
fallbacks.push(file_path.to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modules.sort_unstable();
|
|
||||||
fallbacks.sort_unstable();
|
|
||||||
|
|
||||||
(executables, modules, fallbacks)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let builtins = builtin_commands(&shell);
|
|
||||||
executables.extend(builtins.clone());
|
|
||||||
executables = executables.iter().unique().cloned().collect();
|
|
||||||
|
|
||||||
let mut init = Data {
|
|
||||||
shell,
|
|
||||||
env: None,
|
|
||||||
command,
|
|
||||||
suggest: None,
|
|
||||||
candidates: vec![],
|
|
||||||
alias,
|
|
||||||
split: vec![],
|
|
||||||
privilege: None,
|
|
||||||
error: "".to_string(),
|
|
||||||
executables,
|
|
||||||
modules,
|
|
||||||
fallbacks,
|
|
||||||
mode,
|
|
||||||
};
|
|
||||||
|
|
||||||
init.split();
|
|
||||||
init.extract_env();
|
|
||||||
init.expand_command();
|
|
||||||
if init.mode != Mode::Cnf {
|
|
||||||
init.update_error(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
eprintln!("/// data initialization");
|
|
||||||
eprintln!("shell: {}", init.shell);
|
|
||||||
eprintln!("env: {:?}", init.env);
|
|
||||||
eprintln!("command: {}", init.command);
|
|
||||||
eprintln!("error: {}", init.error);
|
|
||||||
eprintln!("modules: {:?}", init.modules);
|
|
||||||
eprintln!("fallbacks: {:?}", init.fallbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
eprintln!("expand_command: {}", 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()) {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
eprintln!("expand_suggest: {}", suggest);
|
|
||||||
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).trim().to_string();
|
|
||||||
self.privilege = Some(split.remove(0))
|
|
||||||
}
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
eprintln!("split: {:?}", split);
|
|
||||||
if split.is_empty() {
|
|
||||||
eprintln!("{}", t!("empty-command"));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
self.split = split;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_env(&mut self) {
|
|
||||||
let mut envs = vec![];
|
|
||||||
loop {
|
|
||||||
let mut char = self.split[0].char_indices();
|
|
||||||
char.next();
|
|
||||||
let offset = char.offset();
|
|
||||||
if self.split[0][offset..].contains("=") {
|
|
||||||
envs.push(self.split.remove(0));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !envs.is_empty() {
|
|
||||||
self.env = Some(envs.join(" "));
|
|
||||||
self.command = self.split.join(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_error(&mut self, error: Option<String>) {
|
|
||||||
if let Some(error) = error {
|
|
||||||
self.error = error
|
|
||||||
.to_lowercase()
|
|
||||||
.split_whitespace()
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(" ");
|
|
||||||
} 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 elevate(data: &mut Data, command: &mut String) {
|
pub fn elevate(data: &mut Data, command: &mut String) {
|
||||||
|
if is_privileged(command, data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if data.config.sudo.is_some() {
|
||||||
|
*command = format!("{} {}", data.config.sudo.as_ref().unwrap(), command);
|
||||||
|
return;
|
||||||
|
}
|
||||||
for privilege in PRIVILEGE_LIST.iter() {
|
for privilege in PRIVILEGE_LIST.iter() {
|
||||||
if data.executables.contains(&privilege.to_string()) {
|
if data.executables.contains(&privilege.to_string()) {
|
||||||
*command = format!("{} {}", privilege, command);
|
*command = format!("{} {}", privilege, command);
|
||||||
|
|
@ -286,6 +31,13 @@ pub fn elevate(data: &mut Data, command: &mut String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_privileged(command: &str, data: &Data) -> bool {
|
||||||
|
if data.config.sudo.is_some() {
|
||||||
|
return command == data.config.sudo.as_ref().unwrap();
|
||||||
|
}
|
||||||
|
PRIVILEGE_LIST.contains(&command)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_candidates_no_dup(
|
pub fn add_candidates_no_dup(
|
||||||
command: &str,
|
command: &str,
|
||||||
candidates: &mut Vec<String>,
|
candidates: &mut Vec<String>,
|
||||||
|
|
@ -302,13 +54,15 @@ pub fn add_candidates_no_dup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_error(shell: &str, command: &str) -> String {
|
pub fn get_error(shell: &str, command: &str, data: &Data) -> String {
|
||||||
let error_msg = std::env::var("_PR_ERROR_MSG");
|
let error_msg = std::env::var("_PR_ERROR_MSG");
|
||||||
let error = if let Ok(error_msg) = error_msg {
|
let error = if let Ok(error_msg) = error_msg {
|
||||||
std::env::remove_var("_PR_ERROR_MSG");
|
std::env::remove_var("_PR_ERROR_MSG");
|
||||||
error_msg
|
error_msg
|
||||||
} else {
|
} else {
|
||||||
error_output_threaded(shell, command)
|
let timeout = data.config.timeout.0;
|
||||||
|
eprintln!("time out is: {}", timeout);
|
||||||
|
error_output_threaded(shell, command, timeout)
|
||||||
};
|
};
|
||||||
error
|
error
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
|
|
@ -317,7 +71,7 @@ pub fn get_error(shell: &str, command: &str) -> String {
|
||||||
.join(" ")
|
.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error_output_threaded(shell: &str, command: &str) -> String {
|
pub fn error_output_threaded(shell: &str, command: &str, timeout: u64) -> String {
|
||||||
let (sender, receiver) = channel();
|
let (sender, receiver) = channel();
|
||||||
|
|
||||||
thread::scope(|s| {
|
thread::scope(|s| {
|
||||||
|
|
@ -334,7 +88,7 @@ pub fn error_output_threaded(shell: &str, command: &str) -> String {
|
||||||
.expect("failed to send output");
|
.expect("failed to send output");
|
||||||
});
|
});
|
||||||
|
|
||||||
match receiver.recv_timeout(Duration::from_secs(3)) {
|
match receiver.recv_timeout(Duration::from_millis(timeout)) {
|
||||||
Ok(output) => match output.stderr.is_empty() {
|
Ok(output) => match output.stderr.is_empty() {
|
||||||
true => String::from_utf8_lossy(&output.stdout).to_string(),
|
true => String::from_utf8_lossy(&output.stdout).to_string(),
|
||||||
false => String::from_utf8_lossy(&output.stderr).to_string(),
|
false => String::from_utf8_lossy(&output.stderr).to_string(),
|
||||||
|
|
@ -630,7 +384,8 @@ pub fn get_shell() -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builtin_commands(shell: &str) -> Vec<String> {
|
#[allow(unused_variables)]
|
||||||
|
pub fn builtin_commands(shell: &str) -> Vec<String> {
|
||||||
// TODO: add the commands for each shell
|
// TODO: add the commands for each shell
|
||||||
// these should cover most of the builtin commands
|
// these should cover most of the builtin commands
|
||||||
// (maybe with false positives)
|
// (maybe with false positives)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::shell::Data;
|
use crate::data::Data;
|
||||||
use crate::shell::PRIVILEGE_LIST;
|
use crate::shell::is_privileged;
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use pay_respects_utils::evals::split_command;
|
use pay_respects_utils::evals::split_command;
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub fn highlight_difference(data: &Data, suggested_command: &str) -> Option<Stri
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let privileged = PRIVILEGE_LIST.contains(&split_suggested_command[0].as_str());
|
let privileged = is_privileged(&split_suggested_command[0], data);
|
||||||
|
|
||||||
let mut old_entries = Vec::new();
|
let mut old_entries = Vec::new();
|
||||||
for command in &split_suggested_command {
|
for command in &split_suggested_command {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ use colored::Colorize;
|
||||||
use inquire::*;
|
use inquire::*;
|
||||||
use ui::Color;
|
use ui::Color;
|
||||||
|
|
||||||
|
use crate::data::Data;
|
||||||
use crate::rules::match_pattern;
|
use crate::rules::match_pattern;
|
||||||
use crate::shell::{
|
use crate::shell::{add_candidates_no_dup, module_output, shell_evaluated_commands, shell_syntax};
|
||||||
add_candidates_no_dup, module_output, shell_evaluated_commands, shell_syntax, Data,
|
|
||||||
};
|
|
||||||
use crate::style::highlight_difference;
|
use crate::style::highlight_difference;
|
||||||
|
|
||||||
pub fn suggest_candidates(data: &mut Data) {
|
pub fn suggest_candidates(data: &mut Data) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
use crate::data::Data;
|
||||||
use crate::shell::command_output_or_error;
|
use crate::shell::command_output_or_error;
|
||||||
use crate::shell::{command_output, elevate, Data};
|
use crate::shell::{command_output, elevate};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use std::io::stderr;
|
use std::io::stderr;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,7 @@ pub async fn ai_suggestion(last_command: &str, error_msg: &str, locale: &str) {
|
||||||
map.insert("error_msg", error_msg);
|
map.insert("error_msg", error_msg);
|
||||||
|
|
||||||
let user_locale = {
|
let user_locale = {
|
||||||
let locale = std::env::var("_PR_AI_LOCALE")
|
let locale = std::env::var("_PR_AI_LOCALE").unwrap_or_else(|_| locale.to_string());
|
||||||
.unwrap_or_else(|_| locale.to_string());
|
|
||||||
if locale.len() < 2 {
|
if locale.len() < 2 {
|
||||||
"en-US".to_string()
|
"en-US".to_string()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ fn main() -> Result<(), std::io::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
rules::runtime_match(&executable, &shell, &last_command, &error_msg, &executables);
|
rules::runtime_match(&executable, &shell, &last_command, &error_msg, &executables);
|
||||||
rules::runtime_match("_PR_GENERAL", &shell, &last_command, &error_msg, &executables);
|
rules::runtime_match(
|
||||||
|
"_PR_GENERAL",
|
||||||
|
&shell,
|
||||||
|
&last_command,
|
||||||
|
&error_msg,
|
||||||
|
&executables,
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue