#![allow(non_camel_case_types)] use std::sync::{Arc, Mutex}; use std::{env::*, fs::OpenOptions, path::PathBuf}; use pish_derive::FromArgs; use super::{Builtin, BuiltinError as Error, BuiltinResult as Result}; use crate::parse::CmdDisplay; use crate::*; #[allow(unused)] pub enum ArgParseError<'a> { LeftoverArg(&'a [u8]), MissingArg(&'static str), MissingArgValue(&'static str), ArgValueParseError(&'static str, String), } pub trait ArgParse: Sized { fn parse<'a>(args: &'a [BString]) -> std::result::Result>; } fn read_args(args: &[BString], w: &mut dyn Write) -> std::result::Result { let err = match T::parse(args) { Ok(t) => return Ok(t), Err(e) => e, }; match err { ArgParseError::LeftoverArg(items) => { w.write_all(b"leftover argument: ")?; w.write_all(items)?; w.write_all(b"\n")?; } ArgParseError::MissingArg(arg) => { writeln!(w, "argument `{arg}` is missing")?; } ArgParseError::MissingArgValue(arg) => { writeln!(w, "argument `{arg}` is missing its value")?; } ArgParseError::ArgValueParseError(arg, err) => { writeln!(w, "failed to parse value of `{arg}`: {err}")?; } } Err(Error::Exit(-2)) } pub struct cd; impl Builtin for cd { fn name(&self) -> &str { "cd" } fn io( &self, se: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { let mut dir = match current_dir() { Ok(path) => path.as_os_str().as_bytes().to_vec(), Err(_) => vec![b'.'], }; std::mem::swap(&mut dir, &mut se.lock().unwrap().prev_path); let target_path: BString = match args.first().map(|v| &v[..]) { Some(b"-") => dir, Some(path) => path.to_vec(), None => { if let Some(home) = std::env::var_os("HOME") { home.into_encoded_bytes() } else { writeln!(stdout, "$HOME not set")?; return Err(Error::Exit(-1)); } } }; if set_current_dir(OsStr::from_bytes(&target_path)).is_err() { write!(stdout, "failed to cd into ")?; stdout.write_all(&target_path)?; writeln!(stdout, "\n")?; Err(Error::Exit(1)) } else { Ok(()) } } } pub struct clear; impl Builtin for clear { fn name(&self) -> &str { "clear" } fn io( &self, _session: Arc>, _args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { stdout.write_all(b"\x1B[2J\x1B[1;1H")?; Ok(()) } } /// restart shell pub struct re; impl Builtin for re { fn name(&self) -> &str { "re" } fn special(&self, session: Arc>, _args: &[BString]) { session.lock().unwrap().raw.disable(); crate::reload::begin_reload(); session.lock().unwrap().raw.enable(); // something went wrong, let's restore raw mode } fn io( &self, _session: Arc>, _args: &[BString], _stdin: &mut dyn Read, _stdout: &mut dyn Write, ) -> Result { Ok(()) } } pub struct Sink { name: &'static str, append: bool, } impl Builtin for Sink { fn name(&self) -> &str { self.name } fn io( &self, _session: Arc>, args: &[BString], stdin: &mut dyn Read, _stdout: &mut dyn Write, ) -> Result { let Some(path) = args.first() else { return Err(Error::Exit(1)); }; let path = PathBuf::from(OsStr::from_bytes(path)); let mut file = OpenOptions::new() .write(true) .create(true) .append(self.append) .open(path)?; std::io::copy(stdin, &mut file)?; Ok(()) } } pub const fn sink(name: &'static str, append: bool) -> Sink { Sink { name, append } } pub struct from; impl Builtin for from { fn name(&self) -> &str { "from" } fn io( &self, _session: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { let Some(path) = args.first() else { return Err(Error::Exit(1)); }; let path = PathBuf::from(OsStr::from_bytes(path)); let mut file = OpenOptions::new().read(true).open(path)?; std::io::copy(&mut file, stdout)?; Ok(()) } } pub struct _type; impl Builtin for _type { fn name(&self) -> &str { "type" } fn io( &self, session: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { for arg in args { let kind = super::get_command_kind(&session.lock().unwrap(), &arg[..]); let kind_str = match kind { run::CommandKind::Builtin(_) => "builtin", run::CommandKind::Fun(_) => "function", run::CommandKind::Path(_) => "command (if it exists)", }; writeln!(stdout, "{} is {}", String::from_utf8_lossy(arg), kind_str)?; } Ok(()) } } pub struct builtins; impl Builtin for builtins { fn name(&self) -> &str { "builtins" } fn io( &self, _session: Arc>, _args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { let mut bs = super::BUILTINS.to_vec(); bs.sort_by_key(|b| b.name()); for b in bs.into_iter() { write!(stdout, "{} ", b.name())?; } writeln!(stdout)?; Ok(()) } } #[derive(FromArgs, Debug)] struct HistoryArgs { /// displays only local shell session history local: bool, /// displays only history of current directory here: bool, /// only shows if cwd perfectly matches strict: bool, at: Option, // TODO: temporal control, i.e. before & after } fn display_history>(it: T, stdout: &mut dyn Write) -> Result { let now = crate::date::DateTime::now(); for entry in it { let delta = now.relative_to(&entry.time); for _ in 0..crate::date::DateTime::longest_reasonable_delta() - delta.len() { stdout.write_all(b" ")?; } stdout.write_all(delta.as_bytes())?; stdout.write_all(b" ")?; stdout.write_all(&entry.cmd)?; stdout.write_all(b"\n")?; } Ok(()) } pub struct history; impl Builtin for history { fn name(&self) -> &str { "history" } fn io( &self, session: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { let args: HistoryArgs = read_args(args, stdout)?; let path_prefix = if args.here { Some(current_dir()?.as_os_str().as_bytes().to_vec()) } else { args.at.map(|path| path.as_os_str().as_bytes().to_vec()) }; let min_time = None; let max_time = None; if args.local { let hist = session.lock().unwrap().history.clone(); display_history( crate::history::local_history_filter( hist, min_time, max_time, path_prefix.as_deref(), args.strict, ), stdout, ) } else { let Ok(hist) = crate::history::HistoryQueryer::new() else { writeln!(stdout, "error opening global history file")?; return Err(Error::Exit(-1)); }; let Ok(it) = hist.query(min_time, max_time, path_prefix.as_deref(), args.strict) else { writeln!(stdout, "error querying global history")?; return Err(Error::Exit(-1)); }; display_history(it, stdout) } } } pub struct escape; impl Builtin for escape { fn name(&self) -> &str { "escape" } fn io( &self, _session: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { for arg in args.iter() { let escaped = arg.escape_ascii().to_string(); stdout.write_all(escaped.as_bytes())?; stdout.write_all(b" ")?; } Ok(()) } } pub struct parse; impl Builtin for parse { fn name(&self) -> &str { "parse" } fn io( &self, _session: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { for arg in args { match crate::parse::do_parse(arg) { Ok(parsed) => { write!(stdout, "ok ")?; parsed.cdisplay(stdout)?; writeln!(stdout)?; } Err(err) => { writeln!(stdout, "err {:?} {}", err.0, err.1.escape_ascii())?; } } } Ok(()) } } pub struct null; impl Builtin for null { fn name(&self) -> &str { "null" } fn io( &self, _session: Arc>, _args: &[BString], stdin: &mut dyn Read, _stdout: &mut dyn Write, ) -> Result { io::copy(stdin, &mut io::sink())?; Ok(()) } } pub struct var; impl Builtin for var { fn name(&self) -> &str { "$" } fn io( &self, session: Arc>, args: &[BString], _stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> Result { if args.is_empty() { return Err(Error::Exit(-1)); } else if args.len() > 1 { return Err(Error::Exit(-2)); } let se = session.lock().unwrap(); if let Some(val) = se.vars.get(&args[0]) { stdout.write_all(val.as_slice())?; Ok(()) } else { Err(Error::Exit(1)) } } }