Browse Source

feat: do not exit on ctrl-c while executing a not builtin command

main
fdai7451 2 years ago
parent
commit
33a6385db8
  1. 31
      Cargo.lock
  2. 2
      Cargo.toml
  3. 2
      src/error.rs
  4. 38
      src/execute.rs
  5. 10
      src/main.rs

31
Cargo.lock

@ -31,6 +31,35 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "ctrlc"
version = "3.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71"
dependencies = [
"nix",
"windows-sys",
]
[[package]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "2.0.0" version = "2.0.0"
@ -179,6 +208,8 @@ checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
name = "pmuw-project" name = "pmuw-project"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossbeam-channel",
"ctrlc",
"once_cell", "once_cell",
"rustyline", "rustyline",
"shlex", "shlex",

2
Cargo.toml

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
crossbeam-channel = "0.5"
ctrlc = "3.2"
once_cell = "1.17" once_cell = "1.17"
rustyline = "10.1.0" rustyline = "10.1.0"
shlex = "1.1" shlex = "1.1"

2
src/error.rs

@ -6,6 +6,7 @@ pub enum ShellError {
NotFound(Command), NotFound(Command),
ExecuteFailure(String), ExecuteFailure(String),
MalformedArgs(String), MalformedArgs(String),
Interrupt,
} }
impl PartialEq<Self> for ShellError { impl PartialEq<Self> for ShellError {
@ -15,6 +16,7 @@ impl PartialEq<Self> for ShellError {
&ShellError::NotFound(_) => matches!(other, ShellError::NotFound(_)), &ShellError::NotFound(_) => matches!(other, ShellError::NotFound(_)),
&ShellError::ExecuteFailure(_) => matches!(other, ShellError::ExecuteFailure(_)), &ShellError::ExecuteFailure(_) => matches!(other, ShellError::ExecuteFailure(_)),
&ShellError::MalformedArgs(_) => matches!(other, ShellError::MalformedArgs(_)), &ShellError::MalformedArgs(_) => matches!(other, ShellError::MalformedArgs(_)),
&ShellError::Interrupt => matches!(other, ShellError::Interrupt),
} }
} }
} }

38
src/execute.rs

@ -1,11 +1,13 @@
use std::io::ErrorKind::NotFound;
use std::process::Command;
use crossbeam_channel::{select, Receiver};
use std::io::ErrorKind;
use std::process::{Command, ExitStatus};
use std::{io, thread};
use crate::builtins::{execute_builtin, is_builtin}; use crate::builtins::{execute_builtin, is_builtin};
use crate::error::ShellError; use crate::error::ShellError;
use crate::parse::parse_line; use crate::parse::parse_line;
pub fn interpret(line: String) -> Result<(), ShellError> {
pub fn interpret(line: String, ctrlc_recv: Receiver<()>) -> Result<(), ShellError> {
if line.is_empty() { if line.is_empty() {
return Err(ShellError::EmptyLine); return Err(ShellError::EmptyLine);
} }
@ -17,25 +19,33 @@ pub fn interpret(line: String) -> Result<(), ShellError> {
let mut command = Command::new(keyword); let mut command = Command::new(keyword);
command.args(args); command.args(args);
execute(command)?;
execute(command, ctrlc_recv)?;
} }
Ok(()) Ok(())
} }
fn execute(mut command: Command) -> Result<(), ShellError> {
fn execute(mut command: Command, ctrlc_recv: Receiver<()>) -> Result<(), ShellError> {
match command.spawn() { match command.spawn() {
Ok(mut child) => { Ok(mut child) => {
if let Err(err) = child.wait() {
return Err(ShellError::ExecuteFailure(err.to_string()));
}
Ok(())
}
Err(err) => {
if err.kind() == NotFound {
return Err(ShellError::NotFound(command));
let (cmd_send, cmd_recv) = crossbeam_channel::unbounded::<io::Result<ExitStatus>>();
thread::spawn(move || cmd_send.send(child.wait()));
select! {
recv(ctrlc_recv) -> _ => Err(ShellError::Interrupt),
recv(cmd_recv) -> result => {
if let Err(err) = result.unwrap() {
Err(ShellError::ExecuteFailure(err.to_string()))
} else {
Ok(())
}
}
} }
Err(ShellError::ExecuteFailure(err.to_string()))
} }
Err(err) => match err.kind() {
ErrorKind::NotFound => Err(ShellError::NotFound(command)),
ErrorKind::Interrupted => Err(ShellError::Interrupt),
_ => Err(ShellError::ExecuteFailure(err.to_string())),
},
} }
} }

10
src/main.rs

@ -12,6 +12,11 @@ use rustyline::{Editor, Result};
use crate::prompt::Prompt; use crate::prompt::Prompt;
fn main() -> Result<()> { fn main() -> Result<()> {
let (ctrlc_send, ctrlc_recv) = crossbeam_channel::unbounded::<()>();
let _ = ctrlc::set_handler(move || {
let _ = ctrlc_send.send(());
});
let mut rl = Editor::<()>::new()?; let mut rl = Editor::<()>::new()?;
let mut prompt = Prompt::new(); let mut prompt = Prompt::new();
@ -20,7 +25,7 @@ fn main() -> Result<()> {
let readline = rl.readline(&prompt.get_prompt()); let readline = rl.readline(&prompt.get_prompt());
match readline { match readline {
Ok(line) => match interpret(line) {
Ok(line) => match interpret(line, ctrlc_recv.clone()) {
Ok(_) => {} Ok(_) => {}
Err(ShellError::EmptyLine) => { Err(ShellError::EmptyLine) => {
continue; continue;
@ -34,6 +39,9 @@ fn main() -> Result<()> {
Err(ShellError::MalformedArgs(args)) => { Err(ShellError::MalformedArgs(args)) => {
eprintln!("Malformed arguments: {}", args) eprintln!("Malformed arguments: {}", args)
} }
Err(ShellError::Interrupt) => {
continue;
}
}, },
Err(ReadlineError::Interrupted) => { Err(ReadlineError::Interrupted) => {
continue; continue;

Loading…
Cancel
Save