diff options
author | Jesse Luehrs <doy@tozt.net> | 2021-12-22 20:46:44 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2021-12-22 20:46:44 -0500 |
commit | 9b82f429e3c6a1d4414fbc93db0b5425e3955d5f (patch) | |
tree | a914bec6136cf9d5045d0fd89c97cc2321b14ba0 /src | |
parent | 6e9ba966a1fd097196394f00c43eca15ac571843 (diff) | |
download | nbsh-9b82f429e3c6a1d4414fbc93db0b5425e3955d5f.tar.gz nbsh-9b82f429e3c6a1d4414fbc93db0b5425e3955d5f.zip |
allow running multiple commands separated by semicolons
Diffstat (limited to 'src')
-rw-r--r-- | src/parse.rs | 96 | ||||
-rw-r--r-- | src/shell.pest | 18 | ||||
-rw-r--r-- | src/state/history.rs | 239 | ||||
-rw-r--r-- | src/state/mod.rs | 6 |
4 files changed, 179 insertions, 180 deletions
diff --git a/src/parse.rs b/src/parse.rs index abeefb0..4ddffba 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -30,21 +30,15 @@ impl Word { pub struct Exe { exe: Word, args: Vec<Word>, - original: String, } impl Exe { - fn parse(pair: pest::iterators::Pair<Rule>) -> Self { + fn build_ast(pair: pest::iterators::Pair<Rule>) -> Self { assert!(matches!(pair.as_rule(), Rule::exe)); - let original = pair.as_str().to_string(); let mut iter = pair.into_inner(); let exe = Word::new(iter.next().unwrap().as_str()); let args = iter.map(|word| Word::new(word.as_str())).collect(); - Self { - exe, - args, - original, - } + Self { exe, args } } pub fn exe(&self) -> &str { @@ -54,72 +48,62 @@ impl Exe { pub fn args(&self) -> impl Iterator<Item = &str> { self.args.iter().map(|arg| arg.word.as_ref()) } +} + +#[derive(Debug, Clone)] +pub struct Pipeline { + exes: Vec<Exe>, +} - pub fn input_string(&self) -> String { - self.original.clone() +impl Pipeline { + pub fn exes(&self) -> &[Exe] { + &self.exes + } + + fn build_ast(pipeline: pest::iterators::Pair<Rule>) -> Self { + assert!(matches!(pipeline.as_rule(), Rule::pipeline)); + Self { + exes: pipeline.into_inner().map(Exe::build_ast).collect(), + } } } #[derive(Debug, Clone)] -pub enum Command { - Exe(Exe), - And(Exe, Box<Command>), - Or(Exe, Box<Command>), - Both(Exe, Box<Command>), - Pipe(Exe, Box<Command>), +pub struct Commands { + pipelines: Vec<Pipeline>, + input_string: String, } -impl Command { +impl Commands { pub fn parse(full_cmd: &str) -> Self { Self::build_ast( Shell::parse(Rule::line, full_cmd) .unwrap() .next() .unwrap() - .into_inner(), + .into_inner() + .next() + .unwrap(), ) } - pub fn input_string(&self) -> String { - match self { - Self::Exe(exe) => exe.input_string(), - Self::And(exe, command) => format!( - "{} && {}", - exe.input_string(), - command.input_string() - ), - Self::Or(exe, command) => format!( - "{} || {}", - exe.input_string(), - command.input_string() - ), - Self::Both(exe, command) => { - format!("{}; {}", exe.input_string(), command.input_string()) - } - Self::Pipe(exe, command) => { - format!("{} | {}", exe.input_string(), command.input_string()) - } - } + pub fn pipelines(&self) -> &[Pipeline] { + &self.pipelines } - fn build_ast(mut pairs: pest::iterators::Pairs<Rule>) -> Self { - let command = pairs.next().unwrap(); - assert!(matches!(command.as_rule(), Rule::command)); - let mut inner = command.into_inner(); - let exe = inner.next().unwrap(); - let exe = Exe::parse(exe); - if let Some(rest) = inner.next() { - let rule = rest.as_rule(); - let ast = Self::build_ast(rest.into_inner()); - match rule { - Rule::and => Self::And(exe, Box::new(ast)), - Rule::or => Self::Or(exe, Box::new(ast)), - Rule::both => Self::Both(exe, Box::new(ast)), - Rule::pipe => Self::Pipe(exe, Box::new(ast)), - _ => unreachable!(), - } - } else { - Self::Exe(exe) + pub fn input_string(&self) -> &str { + &self.input_string + } + + fn build_ast(commands: pest::iterators::Pair<Rule>) -> Self { + assert!(matches!(commands.as_rule(), Rule::commands)); + let input_string = commands.as_str().to_string(); + Self { + pipelines: commands + .into_inner() + .map(Pipeline::build_ast) + .collect(), + input_string, } } } diff --git a/src/shell.pest b/src/shell.pest index f1f39ab..49a84b4 100644 --- a/src/shell.pest +++ b/src/shell.pest @@ -1,17 +1,11 @@ -char = { ASCII_ALPHANUMERIC } - +char = @{ !("|" | ";" | WHITESPACE) ~ ANY } word = @{ char+ } -exe = { word+ } - -and = { "&&" ~ command } -or = { "||" ~ command } -both = { ";" ~ command } -pipe = { "|" ~ command } - -command = { exe ~ (and | or | both | pipe)? } +exe = { word+ } +pipeline = { exe ~ ("|" ~ exe)* } +commands = { pipeline ~ (";" ~ pipeline)* } -line = { SOI ~ command ~ EOI } +line = { SOI ~ commands ~ EOI } -WHITESPACE = _{ " " } +WHITESPACE = _{ (" " | "\t" | "\n") } COMMENT = _{ "#" ~ ANY* } diff --git a/src/state/history.rs b/src/state/history.rs index 685650a..b98bf60 100644 --- a/src/state/history.rs +++ b/src/state/history.rs @@ -85,49 +85,24 @@ impl History { pub async fn run( &mut self, - cmd: &crate::parse::Command, + commands: &crate::parse::Commands, event_w: async_std::channel::Sender<crate::event::Event>, ) -> anyhow::Result<usize> { let (input_w, input_r) = async_std::channel::unbounded(); let (resize_w, resize_r) = async_std::channel::unbounded(); let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new( - Entry::new(cmd.clone(), self.size, input_w, resize_w), + Entry::new(commands.clone(), self.size, input_w, resize_w), )); - // for now - let cmd = match cmd { - crate::parse::Command::Exe(exe) => exe, - _ => todo!(), - }; - - if crate::builtins::is(cmd.exe()) { - let code: i32 = - crate::builtins::run(cmd.exe(), cmd.args()).into(); - entry.lock_arc().await.exit_info = Some(ExitInfo::new( - async_std::process::ExitStatus::from_raw(code << 8), - )); - event_w - .send(crate::event::Event::ProcessExit) - .await - .unwrap(); - } else { - let mut process = async_std::process::Command::new(cmd.exe()); - process.args(cmd.args()); - let child = process - .spawn_pty(Some(&pty_process::Size::new( - self.size.0, - self.size.1, - ))) - .unwrap(); - run_process( - child, - async_std::sync::Arc::clone(&entry), - input_r, - resize_r, - event_w, - ); - } + run_commands( + commands.clone(), + async_std::sync::Arc::clone(&entry), + input_r, + resize_r, + event_w, + ); + self.entries.push(entry); Ok(self.entries.len() - 1) } @@ -248,7 +223,7 @@ impl std::iter::DoubleEndedIterator for VisibleEntries { } pub struct Entry { - cmd: crate::parse::Command, + commands: crate::parse::Commands, vt: vt100::Parser, audible_bell_state: usize, visual_bell_state: usize, @@ -262,13 +237,13 @@ pub struct Entry { impl Entry { fn new( - cmd: crate::parse::Command, + commands: crate::parse::Commands, size: (u16, u16), input: async_std::channel::Sender<Vec<u8>>, resize: async_std::channel::Sender<(u16, u16)>, ) -> Self { Self { - cmd, + commands, vt: vt100::Parser::new(size.0, size.1, 0), audible_bell_state: 0, visual_bell_state: 0, @@ -320,7 +295,7 @@ impl Entry { if self.running() { out.set_bgcolor(textmode::Color::Rgb(16, 64, 16)); } - out.write_str(&self.cmd.input_string()); + out.write_str(self.commands.input_string()); out.reset_attributes(); set_bgcolor(out, focused); @@ -429,8 +404,8 @@ impl Entry { } } - pub fn cmd(&self) -> String { - self.cmd.input_string() + pub fn cmd(&self) -> &str { + self.commands.input_string() } pub fn toggle_fullscreen(&mut self) { @@ -500,87 +475,133 @@ impl ExitInfo { } } -fn run_process( - mut child: pty_process::async_std::Child, +fn run_commands( + commands: crate::parse::Commands, entry: async_std::sync::Arc<async_std::sync::Mutex<Entry>>, input_r: async_std::channel::Receiver<Vec<u8>>, resize_r: async_std::channel::Receiver<(u16, u16)>, event_w: async_std::channel::Sender<crate::event::Event>, ) { async_std::task::spawn(async move { - loop { - enum Res { - Read(Result<usize, std::io::Error>), - Write(Result<Vec<u8>, async_std::channel::RecvError>), - Resize(Result<(u16, u16), async_std::channel::RecvError>), + for pipeline in commands.pipelines() { + assert_eq!(pipeline.exes().len(), 1); + for exe in pipeline.exes() { + run_exe( + exe, + async_std::sync::Arc::clone(&entry), + input_r.clone(), + resize_r.clone(), + event_w.clone(), + ) + .await; } - let mut buf = [0_u8; 4096]; - let mut pty = child.pty(); - let read = async { Res::Read(pty.read(&mut buf).await) }; - let write = async { Res::Write(input_r.recv().await) }; - let resize = async { Res::Resize(resize_r.recv().await) }; - match read.race(write).race(resize).await { - Res::Read(res) => match res { - Ok(bytes) => { - let mut entry = entry.lock_arc().await; - let pre_alternate_screen = - entry.vt.screen().alternate_screen(); - entry.vt.process(&buf[..bytes]); - let post_alternate_screen = - entry.vt.screen().alternate_screen(); - if entry.fullscreen.is_none() - && pre_alternate_screen != post_alternate_screen - { - event_w - .send(crate::event::Event::ProcessAlternateScreen) - .await - .unwrap(); - } - event_w - .send(crate::event::Event::ProcessOutput) - .await - .unwrap(); - } - Err(e) => { - if e.raw_os_error() != Some(libc::EIO) { - eprintln!("pty read failed: {:?}", e); - } - // XXX not sure if this is safe - are we sure - // the child exited? - entry.lock_arc().await.exit_info = Some( - ExitInfo::new(child.status().await.unwrap()), - ); + } + }); +} + +async fn run_exe( + exe: &crate::parse::Exe, + entry: async_std::sync::Arc<async_std::sync::Mutex<Entry>>, + input_r: async_std::channel::Receiver<Vec<u8>>, + resize_r: async_std::channel::Receiver<(u16, u16)>, + event_w: async_std::channel::Sender<crate::event::Event>, +) { + if crate::builtins::is(exe.exe()) { + let code: i32 = crate::builtins::run(exe.exe(), exe.args()).into(); + entry.lock_arc().await.exit_info = Some(ExitInfo::new( + async_std::process::ExitStatus::from_raw(code << 8), + )); + event_w + .send(crate::event::Event::ProcessExit) + .await + .unwrap(); + return; + } + + let mut process = async_std::process::Command::new(exe.exe()); + process.args(exe.args()); + let size = entry.lock_arc().await.vt.screen().size(); + let child = process + .spawn_pty(Some(&pty_process::Size::new(size.0, size.1))) + .unwrap(); + loop { + enum Res { + Read(Result<usize, std::io::Error>), + Write(Result<Vec<u8>, async_std::channel::RecvError>), + Resize(Result<(u16, u16), async_std::channel::RecvError>), + Exit( + Result<async_std::process::ExitStatus, async_std::io::Error>, + ), + } + let mut buf = [0_u8; 4096]; + let mut pty = child.pty(); + let read = async { Res::Read(pty.read(&mut buf).await) }; + let write = async { Res::Write(input_r.recv().await) }; + let resize = async { Res::Resize(resize_r.recv().await) }; + let exit = async { Res::Exit(child.status_no_drop().await) }; + match read.race(write).race(resize).or(exit).await { + Res::Read(res) => match res { + Ok(bytes) => { + let mut entry = entry.lock_arc().await; + let pre_alternate_screen = + entry.vt.screen().alternate_screen(); + entry.vt.process(&buf[..bytes]); + let post_alternate_screen = + entry.vt.screen().alternate_screen(); + if entry.fullscreen.is_none() + && pre_alternate_screen != post_alternate_screen + { event_w - .send(crate::event::Event::ProcessExit) + .send(crate::event::Event::ProcessAlternateScreen) .await .unwrap(); - break; - } - }, - Res::Write(res) => match res { - Ok(bytes) => { - pty.write(&bytes).await.unwrap(); - } - Err(e) => { - panic!("failed to read from input channel: {}", e); - } - }, - Res::Resize(res) => match res { - Ok(size) => { - child - .resize_pty(&pty_process::Size::new( - size.0, size.1, - )) - .unwrap(); - entry.lock_arc().await.vt.set_size(size.0, size.1); } - Err(e) => { - panic!("failed to read from resize channel: {}", e); + event_w + .send(crate::event::Event::ProcessOutput) + .await + .unwrap(); + } + Err(e) => { + if e.raw_os_error() != Some(libc::EIO) { + panic!("pty read failed: {:?}", e); } - }, - } + } + }, + Res::Write(res) => match res { + Ok(bytes) => { + pty.write(&bytes).await.unwrap(); + } + Err(e) => { + panic!("failed to read from input channel: {}", e); + } + }, + Res::Resize(res) => match res { + Ok(size) => { + child + .resize_pty(&pty_process::Size::new(size.0, size.1)) + .unwrap(); + entry.lock_arc().await.vt.set_size(size.0, size.1); + } + Err(e) => { + panic!("failed to read from resize channel: {}", e); + } + }, + Res::Exit(res) => match res { + Ok(status) => { + entry.lock_arc().await.exit_info = + Some(ExitInfo::new(status)); + event_w + .send(crate::event::Event::ProcessExit) + .await + .unwrap(); + break; + } + Err(e) => { + panic!("failed to get exit status: {}", e); + } + }, } - }); + } } fn set_bgcolor(out: &mut impl textmode::Textmode, focus: bool) { diff --git a/src/state/mod.rs b/src/state/mod.rs index 8547fc1..5ecfa6e 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -237,7 +237,7 @@ impl State { textmode::Key::Char(' ') => { if let Some(idx) = self.focus_idx() { let entry = self.history.entry(idx).await; - self.readline.set_input(&entry.cmd()); + self.readline.set_input(entry.cmd()); self.set_focus(Focus::Readline, Some(entry)).await; } } @@ -440,8 +440,8 @@ impl State { self.focus_idx().map_or(Focus::Readline, Focus::History) } - fn parse(&self, cmd: &str) -> crate::parse::Command { - let cmd = crate::parse::Command::parse(cmd); + fn parse(&self, cmd: &str) -> crate::parse::Commands { + let cmd = crate::parse::Commands::parse(cmd); // todo: interpolate cmd } |