summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/builtins.rs426
-rw-r--r--src/builtins/command.rs235
-rw-r--r--src/builtins/mod.rs194
-rw-r--r--src/env.rs48
-rw-r--r--src/info.rs40
-rw-r--r--src/main.rs5
-rw-r--r--src/pipeline/command.rs100
-rw-r--r--src/pipeline/mod.rs (renamed from src/command.rs)128
-rw-r--r--src/state/history/mod.rs6
-rw-r--r--src/state/readline.rs10
10 files changed, 600 insertions, 592 deletions
diff --git a/src/builtins.rs b/src/builtins.rs
deleted file mode 100644
index 638496b..0000000
--- a/src/builtins.rs
+++ /dev/null
@@ -1,426 +0,0 @@
-use async_std::io::{ReadExt as _, WriteExt as _};
-use std::os::unix::io::{AsRawFd as _, FromRawFd as _, IntoRawFd as _};
-use std::os::unix::process::ExitStatusExt as _;
-
-struct Io {
- fds: std::collections::HashMap<
- std::os::unix::io::RawFd,
- std::os::unix::io::RawFd,
- >,
- pre_exec: Option<
- Box<dyn 'static + FnMut() -> std::io::Result<()> + Send + Sync>,
- >,
-}
-
-impl Io {
- fn new() -> Self {
- let mut fds = std::collections::HashMap::new();
- fds.insert(0.as_raw_fd(), 0.as_raw_fd());
- fds.insert(1.as_raw_fd(), 1.as_raw_fd());
- fds.insert(2.as_raw_fd(), 2.as_raw_fd());
- Self {
- fds,
- pre_exec: None,
- }
- }
-
- fn stdin(&self) -> Option<async_std::fs::File> {
- self.fds
- .get(&0.as_raw_fd())
- .copied()
- .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) })
- }
-
- fn set_stdin<T: std::os::unix::io::IntoRawFd>(&mut self, stdin: T) {
- if let Some(fd) = self.fds.get(&0.as_raw_fd()) {
- if *fd > 2 {
- drop(unsafe { async_std::fs::File::from_raw_fd(*fd) });
- }
- }
- self.fds.insert(0.as_raw_fd(), stdin.into_raw_fd());
- }
-
- fn stdout(&self) -> Option<async_std::fs::File> {
- self.fds
- .get(&1.as_raw_fd())
- .copied()
- .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) })
- }
-
- fn set_stdout<T: std::os::unix::io::IntoRawFd>(&mut self, stdout: T) {
- if let Some(fd) = self.fds.get(&1.as_raw_fd()) {
- if *fd > 2 {
- drop(unsafe { async_std::fs::File::from_raw_fd(*fd) });
- }
- }
- self.fds.insert(1.as_raw_fd(), stdout.into_raw_fd());
- }
-
- fn stderr(&self) -> Option<async_std::fs::File> {
- self.fds
- .get(&2.as_raw_fd())
- .copied()
- .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) })
- }
-
- fn set_stderr<T: std::os::unix::io::IntoRawFd>(&mut self, stderr: T) {
- if let Some(fd) = self.fds.get(&2.as_raw_fd()) {
- if *fd > 2 {
- drop(unsafe { async_std::fs::File::from_raw_fd(*fd) });
- }
- }
- self.fds.insert(2.as_raw_fd(), stderr.into_raw_fd());
- }
-
- pub unsafe fn pre_exec<F>(&mut self, f: F)
- where
- F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
- {
- self.pre_exec = Some(Box::new(f));
- }
-
- async fn read_stdin(&self, buf: &mut [u8]) -> anyhow::Result<usize> {
- if let Some(mut fh) = self.stdin() {
- let res = fh.read(buf).await;
- let _ = fh.into_raw_fd();
- Ok(res?)
- } else {
- Ok(0)
- }
- }
-
- async fn write_stdout(&self, buf: &[u8]) -> anyhow::Result<()> {
- if let Some(mut fh) = self.stdout() {
- let res = fh.write_all(buf).await;
- let _ = fh.into_raw_fd();
- Ok(res.map(|_| ())?)
- } else {
- Ok(())
- }
- }
-
- async fn write_stderr(&self, buf: &[u8]) -> anyhow::Result<()> {
- if let Some(mut fh) = self.stderr() {
- let res = fh.write_all(buf).await;
- let _ = fh.into_raw_fd();
- Ok(res.map(|_| ())?)
- } else {
- Ok(())
- }
- }
-
- fn setup_command(mut self, cmd: &mut crate::command::Command) {
- if let Some(stdin) = self.stdin() {
- let stdin = stdin.into_raw_fd();
- if stdin != 0 {
- cmd.stdin(unsafe { std::fs::File::from_raw_fd(stdin) });
- self.fds.remove(&0.as_raw_fd());
- }
- }
- if let Some(stdout) = self.stdout() {
- let stdout = stdout.into_raw_fd();
- if stdout != 1 {
- cmd.stdout(unsafe { std::fs::File::from_raw_fd(stdout) });
- self.fds.remove(&1.as_raw_fd());
- }
- }
- if let Some(stderr) = self.stderr() {
- let stderr = stderr.into_raw_fd();
- if stderr != 2 {
- cmd.stderr(unsafe { std::fs::File::from_raw_fd(stderr) });
- self.fds.remove(&2.as_raw_fd());
- }
- }
- if let Some(pre_exec) = self.pre_exec.take() {
- unsafe { cmd.pre_exec(pre_exec) };
- }
- }
-}
-
-impl Drop for Io {
- fn drop(&mut self) {
- for fd in self.fds.values() {
- if *fd > 2 {
- drop(unsafe { std::fs::File::from_raw_fd(*fd) });
- }
- }
- }
-}
-
-type Builtin = &'static (dyn Fn(
- &crate::parse::Exe,
- &crate::command::Env,
- Io,
-) -> anyhow::Result<Child>
- + Sync
- + Send);
-
-#[allow(clippy::as_conversions)]
-static BUILTINS: once_cell::sync::Lazy<
- std::collections::HashMap<&'static str, Builtin>,
-> = once_cell::sync::Lazy::new(|| {
- let mut builtins = std::collections::HashMap::new();
- builtins.insert("cd", &cd as Builtin);
- builtins.insert("echo", &echo);
- builtins.insert("and", &and);
- builtins.insert("or", &or);
- builtins.insert("command", &command);
- builtins.insert("builtin", &builtin);
- builtins
-});
-
-pub struct Command {
- exe: crate::parse::Exe,
- f: Builtin,
- io: Io,
-}
-
-impl Command {
- pub fn new(exe: &crate::parse::Exe) -> Option<Self> {
- BUILTINS.get(exe.exe()).map(|f| Self {
- exe: exe.clone(),
- f,
- io: Io::new(),
- })
- }
-
- pub fn stdin(&mut self, fh: std::fs::File) {
- self.io.set_stdin(fh);
- }
-
- pub fn stdout(&mut self, fh: std::fs::File) {
- self.io.set_stdout(fh);
- }
-
- pub fn stderr(&mut self, fh: std::fs::File) {
- self.io.set_stderr(fh);
- }
-
- pub unsafe fn pre_exec<F>(&mut self, f: F)
- where
- F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
- {
- self.io.pre_exec(f);
- }
-
- pub fn spawn(self, env: &crate::command::Env) -> anyhow::Result<Child> {
- let Self { f, exe, io } = self;
- (f)(&exe, env, io)
- }
-}
-
-pub struct Child {
- fut: std::pin::Pin<
- Box<
- dyn std::future::Future<Output = std::process::ExitStatus>
- + Sync
- + Send,
- >,
- >,
- wrapped_child: Option<Box<crate::command::Child>>,
-}
-
-impl Child {
- pub fn id(&self) -> Option<u32> {
- self.wrapped_child.as_ref().and_then(|cmd| cmd.id())
- }
-
- #[async_recursion::async_recursion]
- pub async fn status(
- self,
- ) -> anyhow::Result<async_std::process::ExitStatus> {
- if let Some(child) = self.wrapped_child {
- child.status().await
- } else {
- Ok(self.fut.await)
- }
- }
-}
-
-// clippy can't tell that the type is necessary
-#[allow(clippy::unnecessary_wraps)]
-fn cd(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
- io: Io,
-) -> anyhow::Result<Child> {
- async fn async_cd(
- exe: &crate::parse::Exe,
- _env: &crate::command::Env,
- io: Io,
- ) -> std::process::ExitStatus {
- let dir = exe
- .args()
- .into_iter()
- .map(std::convert::AsRef::as_ref)
- .next()
- .unwrap_or("");
-
- let dir = if dir.is_empty() {
- home()
- } else if dir.starts_with('~') {
- let path: std::path::PathBuf = dir.into();
- if let std::path::Component::Normal(prefix) =
- path.components().next().unwrap()
- {
- if prefix.to_str() == Some("~") {
- home().join(path.strip_prefix(prefix).unwrap())
- } else {
- // TODO
- io.write_stderr(b"unimplemented\n").await.unwrap();
- return async_std::process::ExitStatus::from_raw(1 << 8);
- }
- } else {
- unreachable!()
- }
- } else {
- dir.into()
- };
- let code = match std::env::set_current_dir(&dir) {
- Ok(()) => 0,
- Err(e) => {
- io.write_stderr(
- format!(
- "{}: {}: {}\n",
- exe.exe(),
- crate::format::io_error(&e),
- dir.display()
- )
- .as_bytes(),
- )
- .await
- .unwrap();
- 1
- }
- };
- async_std::process::ExitStatus::from_raw(code << 8)
- }
-
- let exe = exe.clone();
- let env = env.clone();
- Ok(Child {
- fut: Box::pin(async move { async_cd(&exe, &env, io).await }),
- wrapped_child: None,
- })
-}
-
-// clippy can't tell that the type is necessary
-#[allow(clippy::unnecessary_wraps)]
-// mostly just for testing and ensuring that builtins work, i'll likely remove
-// this later, since the binary seems totally fine
-fn echo(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
- io: Io,
-) -> anyhow::Result<Child> {
- async fn async_echo(
- exe: &crate::parse::Exe,
- _env: &crate::command::Env,
- io: Io,
- ) -> std::process::ExitStatus {
- macro_rules! write_stdout {
- ($bytes:expr) => {
- if let Err(e) = io.write_stdout($bytes).await {
- io.write_stderr(format!("echo: {}", e).as_bytes())
- .await
- .unwrap();
- return async_std::process::ExitStatus::from_raw(1 << 8);
- }
- };
- }
- let count = exe.args().count();
- for (i, arg) in exe.args().enumerate() {
- write_stdout!(arg.as_bytes());
- if i == count - 1 {
- write_stdout!(b"\n");
- } else {
- write_stdout!(b" ");
- }
- }
-
- async_std::process::ExitStatus::from_raw(0)
- }
-
- let exe = exe.clone();
- let env = env.clone();
- Ok(Child {
- fut: Box::pin(async move { async_echo(&exe, &env, io).await }),
- wrapped_child: None,
- })
-}
-
-fn and(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
- io: Io,
-) -> anyhow::Result<Child> {
- let exe = exe.shift();
- if env.latest_status().success() {
- let mut cmd = crate::command::Command::new(&exe);
- io.setup_command(&mut cmd);
- Ok(Child {
- fut: Box::pin(async move { unreachable!() }),
- wrapped_child: Some(Box::new(cmd.spawn(env)?)),
- })
- } else {
- let env = env.clone();
- Ok(Child {
- fut: Box::pin(async move { *env.latest_status() }),
- wrapped_child: None,
- })
- }
-}
-
-fn or(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
- io: Io,
-) -> anyhow::Result<Child> {
- let exe = exe.shift();
- if env.latest_status().success() {
- let env = env.clone();
- Ok(Child {
- fut: Box::pin(async move { *env.latest_status() }),
- wrapped_child: None,
- })
- } else {
- let mut cmd = crate::command::Command::new(&exe);
- io.setup_command(&mut cmd);
- Ok(Child {
- fut: Box::pin(async move { unreachable!() }),
- wrapped_child: Some(Box::new(cmd.spawn(env)?)),
- })
- }
-}
-
-fn command(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
- io: Io,
-) -> anyhow::Result<Child> {
- let exe = exe.shift();
- let mut cmd = crate::command::Command::new_binary(&exe);
- io.setup_command(&mut cmd);
- Ok(Child {
- fut: Box::pin(async move { unreachable!() }),
- wrapped_child: Some(Box::new(cmd.spawn(env)?)),
- })
-}
-
-fn builtin(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
- io: Io,
-) -> anyhow::Result<Child> {
- let exe = exe.shift();
- let mut cmd = crate::command::Command::new_builtin(&exe);
- io.setup_command(&mut cmd);
- Ok(Child {
- fut: Box::pin(async move { unreachable!() }),
- wrapped_child: Some(Box::new(cmd.spawn(env)?)),
- })
-}
-
-fn home() -> std::path::PathBuf {
- std::env::var_os("HOME").unwrap().into()
-}
diff --git a/src/builtins/command.rs b/src/builtins/command.rs
new file mode 100644
index 0000000..6dfa56c
--- /dev/null
+++ b/src/builtins/command.rs
@@ -0,0 +1,235 @@
+use async_std::io::{ReadExt as _, WriteExt as _};
+use std::os::unix::io::{AsRawFd as _, FromRawFd as _, IntoRawFd as _};
+
+pub struct Command {
+ exe: crate::parse::Exe,
+ f: super::Builtin,
+ io: Io,
+}
+
+impl Command {
+ pub fn new(exe: &crate::parse::Exe) -> Option<Self> {
+ super::BUILTINS.get(exe.exe()).map(|f| Self {
+ exe: exe.clone(),
+ f,
+ io: Io::new(),
+ })
+ }
+
+ pub fn stdin(&mut self, fh: std::fs::File) {
+ self.io.set_stdin(fh);
+ }
+
+ pub fn stdout(&mut self, fh: std::fs::File) {
+ self.io.set_stdout(fh);
+ }
+
+ pub fn stderr(&mut self, fh: std::fs::File) {
+ self.io.set_stderr(fh);
+ }
+
+ pub unsafe fn pre_exec<F>(&mut self, f: F)
+ where
+ F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
+ {
+ self.io.pre_exec(f);
+ }
+
+ pub fn spawn(self, env: &crate::env::Env) -> anyhow::Result<Child> {
+ let Self { f, exe, io } = self;
+ (f)(&exe, env, io)
+ }
+}
+
+pub struct Io {
+ fds: std::collections::HashMap<
+ std::os::unix::io::RawFd,
+ std::os::unix::io::RawFd,
+ >,
+ pre_exec: Option<
+ Box<dyn 'static + FnMut() -> std::io::Result<()> + Send + Sync>,
+ >,
+}
+
+impl Io {
+ fn new() -> Self {
+ let mut fds = std::collections::HashMap::new();
+ fds.insert(0.as_raw_fd(), 0.as_raw_fd());
+ fds.insert(1.as_raw_fd(), 1.as_raw_fd());
+ fds.insert(2.as_raw_fd(), 2.as_raw_fd());
+ Self {
+ fds,
+ pre_exec: None,
+ }
+ }
+
+ fn stdin(&self) -> Option<async_std::fs::File> {
+ self.fds
+ .get(&0.as_raw_fd())
+ .copied()
+ .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) })
+ }
+
+ fn set_stdin<T: std::os::unix::io::IntoRawFd>(&mut self, stdin: T) {
+ if let Some(fd) = self.fds.get(&0.as_raw_fd()) {
+ if *fd > 2 {
+ drop(unsafe { async_std::fs::File::from_raw_fd(*fd) });
+ }
+ }
+ self.fds.insert(0.as_raw_fd(), stdin.into_raw_fd());
+ }
+
+ fn stdout(&self) -> Option<async_std::fs::File> {
+ self.fds
+ .get(&1.as_raw_fd())
+ .copied()
+ .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) })
+ }
+
+ fn set_stdout<T: std::os::unix::io::IntoRawFd>(&mut self, stdout: T) {
+ if let Some(fd) = self.fds.get(&1.as_raw_fd()) {
+ if *fd > 2 {
+ drop(unsafe { async_std::fs::File::from_raw_fd(*fd) });
+ }
+ }
+ self.fds.insert(1.as_raw_fd(), stdout.into_raw_fd());
+ }
+
+ fn stderr(&self) -> Option<async_std::fs::File> {
+ self.fds
+ .get(&2.as_raw_fd())
+ .copied()
+ .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) })
+ }
+
+ fn set_stderr<T: std::os::unix::io::IntoRawFd>(&mut self, stderr: T) {
+ if let Some(fd) = self.fds.get(&2.as_raw_fd()) {
+ if *fd > 2 {
+ drop(unsafe { async_std::fs::File::from_raw_fd(*fd) });
+ }
+ }
+ self.fds.insert(2.as_raw_fd(), stderr.into_raw_fd());
+ }
+
+ pub unsafe fn pre_exec<F>(&mut self, f: F)
+ where
+ F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
+ {
+ self.pre_exec = Some(Box::new(f));
+ }
+
+ pub async fn read_stdin(&self, buf: &mut [u8]) -> anyhow::Result<usize> {
+ if let Some(mut fh) = self.stdin() {
+ let res = fh.read(buf).await;
+ let _ = fh.into_raw_fd();
+ Ok(res?)
+ } else {
+ Ok(0)
+ }
+ }
+
+ pub async fn write_stdout(&self, buf: &[u8]) -> anyhow::Result<()> {
+ if let Some(mut fh) = self.stdout() {
+ let res = fh.write_all(buf).await;
+ let _ = fh.into_raw_fd();
+ Ok(res.map(|_| ())?)
+ } else {
+ Ok(())
+ }
+ }
+
+ pub async fn write_stderr(&self, buf: &[u8]) -> anyhow::Result<()> {
+ if let Some(mut fh) = self.stderr() {
+ let res = fh.write_all(buf).await;
+ let _ = fh.into_raw_fd();
+ Ok(res.map(|_| ())?)
+ } else {
+ Ok(())
+ }
+ }
+
+ pub fn setup_command(mut self, cmd: &mut crate::pipeline::Command) {
+ if let Some(stdin) = self.stdin() {
+ let stdin = stdin.into_raw_fd();
+ if stdin != 0 {
+ cmd.stdin(unsafe { std::fs::File::from_raw_fd(stdin) });
+ self.fds.remove(&0.as_raw_fd());
+ }
+ }
+ if let Some(stdout) = self.stdout() {
+ let stdout = stdout.into_raw_fd();
+ if stdout != 1 {
+ cmd.stdout(unsafe { std::fs::File::from_raw_fd(stdout) });
+ self.fds.remove(&1.as_raw_fd());
+ }
+ }
+ if let Some(stderr) = self.stderr() {
+ let stderr = stderr.into_raw_fd();
+ if stderr != 2 {
+ cmd.stderr(unsafe { std::fs::File::from_raw_fd(stderr) });
+ self.fds.remove(&2.as_raw_fd());
+ }
+ }
+ if let Some(pre_exec) = self.pre_exec.take() {
+ unsafe { cmd.pre_exec(pre_exec) };
+ }
+ }
+}
+
+impl Drop for Io {
+ fn drop(&mut self) {
+ for fd in self.fds.values() {
+ if *fd > 2 {
+ drop(unsafe { std::fs::File::from_raw_fd(*fd) });
+ }
+ }
+ }
+}
+
+pub struct Child {
+ fut: std::pin::Pin<
+ Box<
+ dyn std::future::Future<Output = std::process::ExitStatus>
+ + Sync
+ + Send,
+ >,
+ >,
+ wrapped_child: Option<Box<crate::pipeline::Child>>,
+}
+
+impl Child {
+ pub fn new_fut<F>(fut: F) -> Self
+ where
+ F: std::future::Future<Output = std::process::ExitStatus>
+ + Sync
+ + Send
+ + 'static,
+ {
+ Self {
+ fut: Box::pin(fut),
+ wrapped_child: None,
+ }
+ }
+
+ pub fn new_wrapped(child: crate::pipeline::Child) -> Self {
+ Self {
+ fut: Box::pin(async move { unreachable!() }),
+ wrapped_child: Some(Box::new(child)),
+ }
+ }
+
+ pub fn id(&self) -> Option<u32> {
+ self.wrapped_child.as_ref().and_then(|cmd| cmd.id())
+ }
+
+ #[async_recursion::async_recursion]
+ pub async fn status(
+ self,
+ ) -> anyhow::Result<async_std::process::ExitStatus> {
+ if let Some(child) = self.wrapped_child {
+ child.status().await
+ } else {
+ Ok(self.fut.await)
+ }
+ }
+}
diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs
new file mode 100644
index 0000000..5c7d4b6
--- /dev/null
+++ b/src/builtins/mod.rs
@@ -0,0 +1,194 @@
+use std::os::unix::process::ExitStatusExt as _;
+
+pub mod command;
+pub use command::{Child, Command};
+
+type Builtin = &'static (dyn Fn(
+ &crate::parse::Exe,
+ &crate::env::Env,
+ command::Io,
+) -> anyhow::Result<command::Child>
+ + Sync
+ + Send);
+
+#[allow(clippy::as_conversions)]
+static BUILTINS: once_cell::sync::Lazy<
+ std::collections::HashMap<&'static str, Builtin>,
+> = once_cell::sync::Lazy::new(|| {
+ let mut builtins = std::collections::HashMap::new();
+ builtins.insert("cd", &cd as Builtin);
+ builtins.insert("echo", &echo);
+ builtins.insert("and", &and);
+ builtins.insert("or", &or);
+ builtins.insert("command", &command);
+ builtins.insert("builtin", &builtin);
+ builtins
+});
+
+// clippy can't tell that the type is necessary
+#[allow(clippy::unnecessary_wraps)]
+fn cd(
+ exe: &crate::parse::Exe,
+ env: &crate::env::Env,
+ io: command::Io,
+) -> anyhow::Result<command::Child> {
+ async fn async_cd(
+ exe: &crate::parse::Exe,
+ _env: &crate::env::Env,
+ io: command::Io,
+ ) -> std::process::ExitStatus {
+ let dir = exe
+ .args()
+ .into_iter()
+ .map(std::convert::AsRef::as_ref)
+ .next()
+ .unwrap_or("");
+
+ let dir = if dir.is_empty() {
+ home()
+ } else if dir.starts_with('~') {
+ let path: std::path::PathBuf = dir.into();
+ if let std::path::Component::Normal(prefix) =
+ path.components().next().unwrap()
+ {
+ if prefix.to_str() == Some("~") {
+ home().join(path.strip_prefix(prefix).unwrap())
+ } else {
+ // TODO
+ io.write_stderr(b"unimplemented\n").await.unwrap();
+ return async_std::process::ExitStatus::from_raw(1 << 8);
+ }
+ } else {
+ unreachable!()
+ }
+ } else {
+ dir.into()
+ };
+ let code = match std::env::set_current_dir(&dir) {
+ Ok(()) => 0,
+ Err(e) => {
+ io.write_stderr(
+ format!(
+ "{}: {}: {}\n",
+ exe.exe(),
+ crate::format::io_error(&e),
+ dir.display()
+ )
+ .as_bytes(),
+ )
+ .await
+ .unwrap();
+ 1
+ }
+ };
+ async_std::process::ExitStatus::from_raw(code << 8)
+ }
+
+ let exe = exe.clone();
+ let env = env.clone();
+ Ok(command::Child::new_fut(async move {
+ async_cd(&exe, &env, io).await
+ }))
+}
+
+// clippy can't tell that the type is necessary
+#[allow(clippy::unnecessary_wraps)]
+// mostly just for testing and ensuring that builtins work, i'll likely remove
+// this later, since the binary seems totally fine
+fn echo(
+ exe: &crate::parse::Exe,
+ env: &crate::env::Env,
+ io: command::Io,
+) -> anyhow::Result<command::Child> {
+ async fn async_echo(
+ exe: &crate::parse::Exe,
+ _env: &crate::env::Env,
+ io: command::Io,
+ ) -> std::process::ExitStatus {
+ macro_rules! write_stdout {
+ ($bytes:expr) => {
+ if let Err(e) = io.write_stdout($bytes).await {
+ io.write_stderr(format!("echo: {}", e).as_bytes())
+ .await
+ .unwrap();
+ return async_std::process::ExitStatus::from_raw(1 << 8);
+ }
+ };
+ }
+ let count = exe.args().count();
+ for (i, arg) in exe.args().enumerate() {
+ write_stdout!(arg.as_bytes());
+ if i == count - 1 {
+ write_stdout!(b"\n");
+ } else {
+ write_stdout!(b" ");
+ }
+ }
+
+ async_std::process::ExitStatus::from_raw(0)
+ }
+
+ let exe = exe.clone();
+ let env = env.clone();
+ Ok(command::Child::new_fut(async move {
+ async_echo(&exe, &env, io).await
+ }))
+}
+
+fn and(
+ exe: &crate::parse::Exe,
+ env: &crate::env::Env,
+ io: command::Io,
+) -> anyhow::Result<command::Child> {
+ let exe = exe.shift();
+ if env.latest_status().success() {
+ let mut cmd = crate::pipeline::Command::new(&exe);
+ io.setup_command(&mut cmd);
+ Ok(command::Child::new_wrapped(cmd.spawn(env)?))
+ } else {
+ let env = env.clone();
+ Ok(command::Child::new_fut(async move { *env.latest_status() }))
+ }
+}
+
+fn or(
+ exe: &crate::parse::Exe,
+ env: &crate::env::Env,
+ io: command::Io,
+) -> anyhow::Result<command::Child> {
+ let exe = exe.shift();
+ if env.latest_status().success() {
+ let env = env.clone();
+ Ok(command::Child::new_fut(async move { *env.latest_status() }))
+ } else {
+ let mut cmd = crate::pipeline::Command::new(&exe);
+ io.setup_command(&mut cmd);
+ Ok(command::Child::new_wrapped(cmd.spawn(env)?))
+ }
+}
+
+fn command(
+ exe: &crate::parse::Exe,
+ env: &crate::env::Env,
+ io: command::Io,
+) -> anyhow::Result<command::Child> {
+ let exe = exe.shift();
+ let mut cmd = crate::pipeline::Command::new_binary(&exe);
+ io.setup_command(&mut cmd);
+ Ok(command::Child::new_wrapped(cmd.spawn(env)?))
+}
+
+fn builtin(
+ exe: &crate::parse::Exe,
+ env: &crate::env::Env,
+ io: command::Io,
+) -> anyhow::Result<command::Child> {
+ let exe = exe.shift();
+ let mut cmd = crate::pipeline::Command::new_builtin(&exe);
+ io.setup_command(&mut cmd);
+ Ok(command::Child::new_wrapped(cmd.spawn(env)?))
+}
+
+fn home() -> std::path::PathBuf {
+ std::env::var_os("HOME").unwrap().into()
+}
diff --git a/src/env.rs b/src/env.rs
index 3ff2576..e88ec7c 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -1,40 +1,24 @@
-pub fn pwd() -> anyhow::Result<String> {
- let mut pwd = std::env::current_dir()?.display().to_string();
- if let Ok(home) = std::env::var("HOME") {
- if pwd.starts_with(&home) {
- pwd.replace_range(..home.len(), "~");
- }
- }
- Ok(pwd)
-}
+use std::os::unix::process::ExitStatusExt as _;
-pub fn user() -> anyhow::Result<String> {
- Ok(users::get_current_username()
- .ok_or_else(|| anyhow::anyhow!("couldn't get username"))?
- .to_string_lossy()
- .into_owned())
+#[derive(Clone)]
+pub struct Env {
+ latest_status: async_std::process::ExitStatus,
}
-#[allow(clippy::unnecessary_wraps)]
-pub fn prompt_char() -> anyhow::Result<String> {
- if users::get_current_uid() == 0 {
- Ok("#".into())
- } else {
- Ok("$".into())
+impl Env {
+ pub fn new(code: i32) -> Self {
+ Self {
+ latest_status: async_std::process::ExitStatus::from_raw(
+ code << 8,
+ ),
+ }
}
-}
-pub fn hostname() -> anyhow::Result<String> {
- let mut hostname = hostname::get()?.to_string_lossy().into_owned();
- if let Some(idx) = hostname.find('.') {
- hostname.truncate(idx);
+ pub fn set_status(&mut self, status: async_std::process::ExitStatus) {
+ self.latest_status = status;
}
- Ok(hostname)
-}
-#[allow(clippy::unnecessary_wraps)]
-pub fn time(offset: time::UtcOffset) -> anyhow::Result<String> {
- Ok(crate::format::time(
- time::OffsetDateTime::now_utc().to_offset(offset),
- ))
+ pub fn latest_status(&self) -> &async_std::process::ExitStatus {
+ &self.latest_status
+ }
}
diff --git a/src/info.rs b/src/info.rs
new file mode 100644
index 0000000..3ff2576
--- /dev/null
+++ b/src/info.rs
@@ -0,0 +1,40 @@
+pub fn pwd() -> anyhow::Result<String> {
+ let mut pwd = std::env::current_dir()?.display().to_string();
+ if let Ok(home) = std::env::var("HOME") {
+ if pwd.starts_with(&home) {
+ pwd.replace_range(..home.len(), "~");
+ }
+ }
+ Ok(pwd)
+}
+
+pub fn user() -> anyhow::Result<String> {
+ Ok(users::get_current_username()
+ .ok_or_else(|| anyhow::anyhow!("couldn't get username"))?
+ .to_string_lossy()
+ .into_owned())
+}
+
+#[allow(clippy::unnecessary_wraps)]
+pub fn prompt_char() -> anyhow::Result<String> {
+ if users::get_current_uid() == 0 {
+ Ok("#".into())
+ } else {
+ Ok("$".into())
+ }
+}
+
+pub fn hostname() -> anyhow::Result<String> {
+ let mut hostname = hostname::get()?.to_string_lossy().into_owned();
+ if let Some(idx) = hostname.find('.') {
+ hostname.truncate(idx);
+ }
+ Ok(hostname)
+}
+
+#[allow(clippy::unnecessary_wraps)]
+pub fn time(offset: time::UtcOffset) -> anyhow::Result<String> {
+ Ok(crate::format::time(
+ time::OffsetDateTime::now_utc().to_offset(offset),
+ ))
+}
diff --git a/src/main.rs b/src/main.rs
index 91a3526..9709a32 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,11 +13,12 @@
#![allow(clippy::type_complexity)]
mod builtins;
-mod command;
mod env;
mod event;
mod format;
+mod info;
mod parse;
+mod pipeline;
mod state;
use async_std::stream::StreamExt as _;
@@ -45,7 +46,7 @@ fn get_offset() -> time::UtcOffset {
async fn async_main() -> anyhow::Result<i32> {
if std::env::args().nth(1).as_deref() == Some("--internal-cmd-runner") {
- return command::run().await;
+ return pipeline::run().await;
}
let mut input = textmode::Input::new().await?;
diff --git a/src/pipeline/command.rs b/src/pipeline/command.rs
new file mode 100644
index 0000000..5a7798c
--- /dev/null
+++ b/src/pipeline/command.rs
@@ -0,0 +1,100 @@
+use async_std::os::unix::process::CommandExt as _;
+
+pub enum Command {
+ Binary(async_std::process::Command),
+ Builtin(crate::builtins::Command),
+}
+
+impl Command {
+ pub fn new(exe: &crate::parse::Exe) -> Self {
+ crate::builtins::Command::new(exe)
+ .map_or_else(|| Self::new_binary(exe), Self::Builtin)
+ }
+
+ pub fn new_binary(exe: &crate::parse::Exe) -> Self {
+ let mut cmd = async_std::process::Command::new(exe.exe());
+ cmd.args(exe.args());
+ Self::Binary(cmd)
+ }
+
+ pub fn new_builtin(exe: &crate::parse::Exe) -> Self {
+ crate::builtins::Command::new(exe)
+ .map_or_else(|| todo!(), Self::Builtin)
+ }
+
+ pub fn stdin(&mut self, fh: std::fs::File) {
+ match self {
+ Self::Binary(cmd) => {
+ cmd.stdin(fh);
+ }
+ Self::Builtin(cmd) => {
+ cmd.stdin(fh);
+ }
+ }
+ }
+
+ pub fn stdout(&mut self, fh: std::fs::File) {
+ match self {
+ Self::Binary(cmd) => {
+ cmd.stdout(fh);
+ }
+ Self::Builtin(cmd) => {
+ cmd.stdout(fh);
+ }
+ }
+ }
+
+ pub fn stderr(&mut self, fh: std::fs::File) {
+ match self {
+ Self::Binary(cmd) => {
+ cmd.stderr(fh);
+ }
+ Self::Builtin(cmd) => {
+ cmd.stderr(fh);
+ }
+ }
+ }
+
+ pub unsafe fn pre_exec<F>(&mut self, f: F)
+ where
+ F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
+ {
+ match self {
+ Self::Binary(cmd) => {
+ cmd.pre_exec(f);
+ }
+ Self::Builtin(cmd) => {
+ cmd.pre_exec(f);
+ }
+ }
+ }
+
+ pub fn spawn(self, env: &crate::env::Env) -> anyhow::Result<Child> {
+ match self {
+ Self::Binary(mut cmd) => Ok(Child::Binary(cmd.spawn()?)),
+ Self::Builtin(cmd) => Ok(Child::Builtin(cmd.spawn(env)?)),
+ }
+ }
+}
+
+pub enum Child {
+ Binary(async_std::process::Child),
+ Builtin(crate::builtins::Child),
+}
+
+impl Child {
+ pub fn id(&self) -> Option<u32> {
+ match self {
+ Self::Binary(child) => Some(child.id()),
+ Self::Builtin(child) => child.id(),
+ }
+ }
+
+ #[async_recursion::async_recursion]
+ pub async fn status(self) -> anyhow::Result<std::process::ExitStatus> {
+ match self {
+ Self::Binary(child) => Ok(child.status_no_drop().await?),
+ Self::Builtin(child) => Ok(child.status().await?),
+ }
+ }
+}
diff --git a/src/command.rs b/src/pipeline/mod.rs
index 8f19a26..b1e4c21 100644
--- a/src/command.rs
+++ b/src/pipeline/mod.rs
@@ -1,136 +1,16 @@
use async_std::io::ReadExt as _;
-use async_std::os::unix::process::CommandExt as _;
use async_std::stream::StreamExt as _;
use std::os::unix::io::FromRawFd as _;
use std::os::unix::process::ExitStatusExt as _;
const PID0: nix::unistd::Pid = nix::unistd::Pid::from_raw(0);
-#[derive(Clone)]
-pub struct Env {
- latest_status: async_std::process::ExitStatus,
-}
-
-impl Env {
- pub fn new(code: i32) -> Self {
- Self {
- latest_status: async_std::process::ExitStatus::from_raw(
- code << 8,
- ),
- }
- }
-
- pub fn set_status(&mut self, status: async_std::process::ExitStatus) {
- self.latest_status = status;
- }
-
- pub fn latest_status(&self) -> &async_std::process::ExitStatus {
- &self.latest_status
- }
-}
-
-pub enum Command {
- Binary(async_std::process::Command),
- Builtin(crate::builtins::Command),
-}
-
-impl Command {
- pub fn new(exe: &crate::parse::Exe) -> Self {
- crate::builtins::Command::new(exe)
- .map_or_else(|| Self::new_binary(exe), Self::Builtin)
- }
-
- pub fn new_binary(exe: &crate::parse::Exe) -> Self {
- let mut cmd = async_std::process::Command::new(exe.exe());
- cmd.args(exe.args());
- Self::Binary(cmd)
- }
-
- pub fn new_builtin(exe: &crate::parse::Exe) -> Self {
- crate::builtins::Command::new(exe)
- .map_or_else(|| todo!(), Self::Builtin)
- }
-
- pub fn stdin(&mut self, fh: std::fs::File) {
- match self {
- Self::Binary(cmd) => {
- cmd.stdin(fh);
- }
- Self::Builtin(cmd) => {
- cmd.stdin(fh);
- }
- }
- }
-
- pub fn stdout(&mut self, fh: std::fs::File) {
- match self {
- Self::Binary(cmd) => {
- cmd.stdout(fh);
- }
- Self::Builtin(cmd) => {
- cmd.stdout(fh);
- }
- }
- }
-
- pub fn stderr(&mut self, fh: std::fs::File) {
- match self {
- Self::Binary(cmd) => {
- cmd.stderr(fh);
- }
- Self::Builtin(cmd) => {
- cmd.stderr(fh);
- }
- }
- }
-
- pub unsafe fn pre_exec<F>(&mut self, f: F)
- where
- F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
- {
- match self {
- Self::Binary(cmd) => {
- cmd.pre_exec(f);
- }
- Self::Builtin(cmd) => {
- cmd.pre_exec(f);
- }
- }
- }
-
- pub fn spawn(self, env: &Env) -> anyhow::Result<Child> {
- match self {
- Self::Binary(mut cmd) => Ok(Child::Binary(cmd.spawn()?)),
- Self::Builtin(cmd) => Ok(Child::Builtin(cmd.spawn(env)?)),
- }
- }
-}
-
-pub enum Child {
- Binary(async_std::process::Child),
- Builtin(crate::builtins::Child),
-}
-
-impl Child {
- pub fn id(&self) -> Option<u32> {
- match self {
- Self::Binary(child) => Some(child.id()),
- Self::Builtin(child) => child.id(),
- }
- }
-
- #[async_recursion::async_recursion]
- pub async fn status(self) -> anyhow::Result<std::process::ExitStatus> {
- match self {
- Self::Binary(child) => Ok(child.status_no_drop().await?),
- Self::Builtin(child) => Ok(child.status().await?),
- }
- }
-}
+mod command;
+pub use command::{Child, Command};
pub async fn run() -> anyhow::Result<i32> {
let (code, pipeline) = read_data().await?;
- let env = Env::new(code);
+ let env = crate::env::Env::new(code);
let children = spawn_children(&pipeline, &env)?;
let count = children.len();
@@ -180,7 +60,7 @@ async fn read_data() -> anyhow::Result<(i32, crate::parse::Pipeline)> {
fn spawn_children(
pipeline: &crate::parse::Pipeline,
- env: &Env,
+ env: &crate::env::Env,
) -> anyhow::Result<Vec<Child>> {
let mut cmds: Vec<_> = pipeline.exes().iter().map(Command::new).collect();
for i in 0..(cmds.len() - 1) {
diff --git a/src/state/history/mod.rs b/src/state/history/mod.rs
index 7423727..a7b5d88 100644
--- a/src/state/history/mod.rs
+++ b/src/state/history/mod.rs
@@ -99,7 +99,7 @@ impl History {
run_commands(
ast.clone(),
async_std::sync::Arc::clone(&entry),
- crate::command::Env::new(0),
+ crate::env::Env::new(0),
input_r,
resize_r,
event_w,
@@ -516,7 +516,7 @@ impl ExitInfo {
fn run_commands(
ast: crate::parse::Commands,
entry: async_std::sync::Arc<async_std::sync::Mutex<Entry>>,
- mut env: crate::command::Env,
+ mut env: crate::env::Env,
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>,
@@ -560,7 +560,7 @@ fn run_commands(
async fn run_pipeline(
pipeline: &crate::parse::Pipeline,
pty: &pty::Pty,
- env: &crate::command::Env,
+ env: &crate::env::Env,
) -> (async_std::process::ExitStatus, bool) {
let mut cmd = pty_process::Command::new(std::env::current_exe().unwrap());
cmd.arg("--internal-cmd-runner");
diff --git a/src/state/readline.rs b/src/state/readline.rs
index 41ffbe7..014efd8 100644
--- a/src/state/readline.rs
+++ b/src/state/readline.rs
@@ -24,11 +24,11 @@ impl Readline {
focus: bool,
offset: time::UtcOffset,
) -> anyhow::Result<()> {
- let pwd = crate::env::pwd()?;
- let user = crate::env::user()?;
- let hostname = crate::env::hostname()?;
- let time = crate::env::time(offset)?;
- let prompt_char = crate::env::prompt_char()?;
+ let pwd = crate::info::pwd()?;
+ let user = crate::info::user()?;
+ let hostname = crate::info::hostname()?;
+ let time = crate::info::time(offset)?;
+ let prompt_char = crate::info::prompt_char()?;
let id = format!("{}@{}", user, hostname);
let idlen: u16 = id.len().try_into().unwrap();