allow interactive commands
This commit is contained in:
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -25,9 +25,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@@ -42,10 +42,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cr-shell"
|
name = "chg-shell"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"inquire",
|
"inquire",
|
||||||
|
"libc",
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -76,9 +77,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dyn-clone"
|
name = "dyn-clone"
|
||||||
version = "1.0.17"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuzzy-matcher"
|
name = "fuzzy-matcher"
|
||||||
@@ -104,7 +105,7 @@ version = "0.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.9.0",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
@@ -117,9 +118,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.167"
|
version = "0.2.171"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -133,9 +134,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.22"
|
version = "0.4.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
@@ -166,9 +167,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
@@ -195,11 +196,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.7"
|
version = "0.5.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
|
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -269,9 +270,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.2"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cr-shell"
|
name = "chg-shell"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
inquire = "0.7.5"
|
inquire = "0.7.5"
|
||||||
|
libc = "0.2.171"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cr-shell"
|
name = "chg-shell"
|
||||||
path = "main.rs"
|
path = "main.rs"
|
||||||
|
|||||||
128
main.rs
128
main.rs
@@ -1,13 +1,101 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::{Command, Output};
|
|
||||||
use std::io::Result as IoResult;
|
use std::io::Result as IoResult;
|
||||||
use inquire::{Text, validator::Validation};
|
use inquire::{Text, validator::Validation};
|
||||||
use inquire::Select;
|
use inquire::{Select, Confirm};
|
||||||
use inquire::ui::{RenderConfig, Styled};
|
use inquire::ui::{RenderConfig, Styled};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
use std::process::{Command, Output};
|
||||||
|
use std::ffi::CString;
|
||||||
|
use libc::{c_char, execvp, fork, waitpid, WIFEXITED, WEXITSTATUS};
|
||||||
|
|
||||||
|
static AUTHOR_STRING: &str = r#"
|
||||||
|
Author: Spencer
|
||||||
|
Description: A shell to promote proper system changes!
|
||||||
|
Ask for the source code!
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fn cd(path: &str) -> Result<(), std::io::Error> {
|
||||||
|
env::set_current_dir(Path::new(path))
|
||||||
|
}
|
||||||
|
|
||||||
fn execute_command(command: &str) -> IoResult<String> {
|
fn execute_command(command: &str) -> IoResult<String> {
|
||||||
// Use Command to run the bash shell with the -c option to pass the command string
|
// Checking to see if the command is a builtin
|
||||||
|
match command.split_whitespace().next() {
|
||||||
|
Some(first_word) if first_word == "cd" => {
|
||||||
|
let path: String = command.split_whitespace().skip(1).collect::<Vec<&str>>().join(" ");
|
||||||
|
match cd(&path) {
|
||||||
|
Ok(_) => return Ok("".to_string()),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(first_word) if first_word == "author" => {
|
||||||
|
println!("{}", AUTHOR_STRING);
|
||||||
|
return Ok("".to_string())
|
||||||
|
}
|
||||||
|
Some(_) => (),
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Split the command into arguments
|
||||||
|
let args: Vec<&str> = command.split_whitespace().collect();
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err(Error::new(ErrorKind::Other, "No command provided"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert command and arguments to CStrings
|
||||||
|
let c_args: Vec<CString> = args.iter()
|
||||||
|
.map(|s| CString::new(*s).expect("CString::new failed"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Create an array of pointers to the CStrings
|
||||||
|
let mut argv: Vec<*const c_char> = c_args.iter()
|
||||||
|
.map(|s| s.as_ptr())
|
||||||
|
.chain(std::iter::once(std::ptr::null()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Fork and exec
|
||||||
|
unsafe {
|
||||||
|
match fork() {
|
||||||
|
-1 => return Err(Error::last_os_error()),
|
||||||
|
0 => { // Child process
|
||||||
|
if execvp(c_args[0].as_ptr(), argv.as_mut_ptr()) == -1 {
|
||||||
|
let e = Error::last_os_error();
|
||||||
|
std::process::exit(e.raw_os_error().unwrap_or(1));
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pid => { // Parent process
|
||||||
|
let mut status: i32 = 0;
|
||||||
|
if waitpid(pid, &mut status, 0) == -1 {
|
||||||
|
return Err(Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
if WIFEXITED(status) && WEXITSTATUS(status) == 0 {
|
||||||
|
Ok(String::new())
|
||||||
|
} else {
|
||||||
|
let exit_status = WEXITSTATUS(status);
|
||||||
|
Err(Error::new(ErrorKind::Other, format!("Command exited with status {}", exit_status)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt(name: &str) -> String {
|
||||||
|
let mut line = String::new();
|
||||||
|
print!("{}", name);
|
||||||
|
std::io::stdout().flush().unwrap();
|
||||||
|
std::io::stdin().read_line(&mut line).expect("Error: Could not read a line");
|
||||||
|
|
||||||
|
return line.trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_prompt() -> IoResult<String> {
|
||||||
|
let command = r#"echo -n "[${USER}@${HOSTNAME} ${PWD##*/}]$ ""#;
|
||||||
let output: Output = Command::new("bash")
|
let output: Output = Command::new("bash")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(command)
|
.arg(command)
|
||||||
@@ -26,27 +114,19 @@ fn execute_command(command: &str) -> IoResult<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(name:&str) -> String {
|
|
||||||
let mut line = String::new();
|
|
||||||
print!("{}", name);
|
|
||||||
std::io::stdout().flush().unwrap();
|
|
||||||
std::io::stdin().read_line(&mut line).expect("Error: Could not read a line");
|
|
||||||
|
|
||||||
return line.trim().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let prompt_gen_command = "echo -n \"r-[${USER}@${HOSTNAME} ${PWD##*/}]$ \"";
|
// RenderConfig for inquire
|
||||||
|
|
||||||
let mut empty_render_config: RenderConfig = RenderConfig::default();
|
let mut empty_render_config: RenderConfig = RenderConfig::default();
|
||||||
empty_render_config = empty_render_config.with_prompt_prefix(Styled::new(""));
|
empty_render_config = empty_render_config.with_prompt_prefix(Styled::new(""));
|
||||||
empty_render_config = empty_render_config.with_answered_prompt_prefix(Styled::new(""));
|
empty_render_config = empty_render_config.with_answered_prompt_prefix(Styled::new(""));
|
||||||
empty_render_config = empty_render_config.with_canceled_prompt_indicator(Styled::new(""));
|
empty_render_config = empty_render_config.with_canceled_prompt_indicator(Styled::new(""));
|
||||||
|
|
||||||
let re = Regex::new(r"CHG[0-9]{6}$").unwrap();
|
// Change type options
|
||||||
let options: Vec<&str> = vec!["File change", "Package Update", "Package Removal",
|
let options: Vec<&str> = vec!["File change", "Package Update", "Package Removal",
|
||||||
"File Removal", "Misc. Command"];
|
"File Removal", "Misc. Command"];
|
||||||
|
|
||||||
|
// Change request input validator
|
||||||
|
let re = Regex::new(r"CHG[0-9]{6}$").unwrap();
|
||||||
let change_validator = move |input: &str| if input.chars().count() == 0 {
|
let change_validator = move |input: &str| if input.chars().count() == 0 {
|
||||||
Ok(Validation::Invalid("Change request cannot be empty".into()))
|
Ok(Validation::Invalid("Change request cannot be empty".into()))
|
||||||
} else if re.is_match(input) {
|
} else if re.is_match(input) {
|
||||||
@@ -57,7 +137,7 @@ fn main() {
|
|||||||
|
|
||||||
let mut bypass_change = false;
|
let mut bypass_change = false;
|
||||||
loop {
|
loop {
|
||||||
let prompt_command = match execute_command(&prompt_gen_command) {
|
let prompt_command = match generate_prompt() {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
let prompt_output = prompt(&output);
|
let prompt_output = prompt(&output);
|
||||||
if prompt_output.chars().count() == 0 {
|
if prompt_output.chars().count() == 0 {
|
||||||
@@ -80,25 +160,33 @@ fn main() {
|
|||||||
|
|
||||||
if !bypass_change {
|
if !bypass_change {
|
||||||
let change_request = match Text::new("(e.g. CHGXXXXXX) Enter change request: ")
|
let change_request = match Text::new("(e.g. CHGXXXXXX) Enter change request: ")
|
||||||
|
.with_render_config(empty_render_config.clone())
|
||||||
.with_validator(change_validator.clone())
|
.with_validator(change_validator.clone())
|
||||||
.prompt() {
|
.prompt() {
|
||||||
Ok(input) => input,
|
Ok(input) => input,
|
||||||
Err(_) => return,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
println!("Change request validated: {}", change_request);
|
println!("Change request validated: {}", change_request);
|
||||||
|
|
||||||
let ans = Select::new("Change Type?", options.clone())
|
let _change_type = Select::new("Change Type?", options.clone())
|
||||||
.with_render_config(empty_render_config.clone())
|
.with_render_config(empty_render_config.clone())
|
||||||
.prompt();
|
.prompt();
|
||||||
|
|
||||||
|
let ans = Confirm::new("Are you within the change window?")
|
||||||
|
.with_default(false)
|
||||||
|
.with_help_message("Ensure you are running commands within the change time window")
|
||||||
|
.prompt();
|
||||||
|
|
||||||
match ans {
|
match ans {
|
||||||
Ok(selected_option) => {
|
Ok(true) => {
|
||||||
println!("Selected option: {}", selected_option);
|
|
||||||
match execute_command(&prompt_command) {
|
match execute_command(&prompt_command) {
|
||||||
Ok(output) => print!("{}", output),
|
Ok(output) => print!("{}", output),
|
||||||
Err(e) => eprintln!("Command failed: {}", e),
|
Err(e) => eprintln!("Command failed: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
continue
|
||||||
|
}
|
||||||
Err(_) => println!("not running command..."),
|
Err(_) => println!("not running command..."),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user