Progress
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub trait VecDequeAliases<T> {
|
||||
fn fpop(&mut self) -> Option<T>;
|
||||
fn fpush(&mut self, value: T);
|
||||
fn bpop(&mut self) -> Option<T>;
|
||||
fn bpush(&mut self, value: T);
|
||||
fn to_vec(self) -> Vec<T>;
|
||||
}
|
||||
|
||||
impl<T> VecDequeAliases<T> for VecDeque<T> {
|
||||
/// Alias for pop_front()
|
||||
fn fpop(&mut self) -> Option<T> {
|
||||
self.pop_front()
|
||||
}
|
||||
/// Alias for push_front()
|
||||
fn fpush(&mut self, value: T) {
|
||||
self.push_front(value);
|
||||
}
|
||||
/// Alias for pop_back()
|
||||
fn bpop(&mut self) -> Option<T> {
|
||||
self.pop_back()
|
||||
}
|
||||
/// Alias for push_back()
|
||||
fn bpush(&mut self, value: T) {
|
||||
self.push_back(value);
|
||||
}
|
||||
/// Just turns the deque into a vector
|
||||
fn to_vec(mut self) -> Vec<T> {
|
||||
let mut vec = vec![];
|
||||
while let Some(item) = self.fpop() {
|
||||
vec.push(item)
|
||||
}
|
||||
vec
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,81 @@
|
||||
use std::fmt::Display;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use crate::parse::lex::Span;
|
||||
use crate::prelude::*;
|
||||
use crate::{parse::lex::Span, prelude::*};
|
||||
|
||||
pub type ShResult<T> = Result<T,ShErr>;
|
||||
pub type ShResult<'s,T> = Result<T,ShErr<'s>>;
|
||||
|
||||
pub trait ResultExt {
|
||||
fn eprint(self) -> Self;
|
||||
fn abort_if_err(&self);
|
||||
#[derive(Debug)]
|
||||
pub enum ShErr<'s> {
|
||||
Simple { kind: ShErrKind, msg: String },
|
||||
Full { kind: ShErrKind, msg: String, span: Span<'s> }
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct BlamePair {
|
||||
input: String,
|
||||
span: Rc<RefCell<Span>>
|
||||
}
|
||||
|
||||
impl BlamePair {
|
||||
pub fn new(input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
Self { input, span }
|
||||
impl<'s> ShErr<'s> {
|
||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||
let msg = msg.into();
|
||||
Self::Simple { kind, msg }
|
||||
}
|
||||
pub fn start(&self) -> usize {
|
||||
self.span.borrow().start()
|
||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span<'s>) -> Self {
|
||||
let msg = msg.into();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
pub fn end(&self) -> usize {
|
||||
self.span.borrow().end()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.input.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for BlamePair {
|
||||
fn into(self) -> String {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: Display> ResultExt for Result<T, E> {
|
||||
fn eprint(self) -> Self {
|
||||
if let Err(err) = &self {
|
||||
eprintln!("{}", err);
|
||||
pub fn unpack(self) -> (ShErrKind,String,Option<Span<'s>>) {
|
||||
match self {
|
||||
ShErr::Simple { kind, msg } => (kind,msg,None),
|
||||
ShErr::Full { kind, msg, span } => (kind,msg,Some(span))
|
||||
}
|
||||
self
|
||||
}
|
||||
fn abort_if_err(&self) {
|
||||
if let Err(err) = &self {
|
||||
eprintln!("{}", err);
|
||||
sh_quit(1)
|
||||
pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self {
|
||||
let (kind,msg,_) = sherr.unpack();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Display for ShErr<'s> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg),
|
||||
Self::Full { msg, kind: _, span: _ } => writeln!(f, "{}", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Blame {
|
||||
/// Blame a span for a propagated error. This will convert a ShErr::Simple into a ShErr::Full
|
||||
/// This will also set the span on a ShErr::Builder
|
||||
fn blame(self, input: String, span: Rc<RefCell<Span>>) -> Self;
|
||||
|
||||
/// If an error is propagated to this point, then attempt to blame a span.
|
||||
/// If the error in question has already blamed a span, don't overwrite it.
|
||||
/// Used as a last resort in higher level contexts in case an error somehow goes unblamed
|
||||
fn try_blame(self, input: String, span: Rc<RefCell<Span>>) -> Self;
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ShErr {
|
||||
impl<'s> From<std::io::Error> for ShErr<'s> {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
ShErr::io()
|
||||
let msg = std::io::Error::last_os_error();
|
||||
ShErr::simple(ShErrKind::IoErr, msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::env::VarError> for ShErr {
|
||||
impl<'s> From<std::env::VarError> for ShErr<'s> {
|
||||
fn from(value: std::env::VarError) -> Self {
|
||||
ShErr::simple(ShErrKind::InternalErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rustyline::error::ReadlineError> for ShErr {
|
||||
impl<'s> From<rustyline::error::ReadlineError> for ShErr<'s> {
|
||||
fn from(value: rustyline::error::ReadlineError) -> Self {
|
||||
ShErr::simple(ShErrKind::ParseErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Errno> for ShErr {
|
||||
impl<'s> From<Errno> for ShErr<'s> {
|
||||
fn from(value: Errno) -> Self {
|
||||
ShErr::simple(ShErrKind::Errno, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Blame for Result<T,ShErr> {
|
||||
fn blame(self, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
if let Err(mut e) = self {
|
||||
e.blame(input,span);
|
||||
Err(e)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
fn try_blame(self, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
if let Err(mut e) = self {
|
||||
e.try_blame(input,span);
|
||||
Err(e)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Copy,Clone,PartialEq,Eq)]
|
||||
#[derive(Debug)]
|
||||
pub enum ShErrKind {
|
||||
IoErr,
|
||||
SyntaxErr,
|
||||
ParseErr,
|
||||
InternalErr,
|
||||
ExecFail,
|
||||
ResourceLimitExceeded,
|
||||
BadPermission,
|
||||
Errno,
|
||||
FileNotFound,
|
||||
CmdNotFound,
|
||||
CleanExit,
|
||||
FuncReturn,
|
||||
@@ -121,156 +83,3 @@ pub enum ShErrKind {
|
||||
LoopBreak,
|
||||
Null
|
||||
}
|
||||
|
||||
impl Default for ShErrKind {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum ShErr {
|
||||
Simple { kind: ShErrKind, message: String },
|
||||
Full { kind: ShErrKind, message: String, blame: BlamePair },
|
||||
}
|
||||
|
||||
impl ShErr {
|
||||
pub fn simple<S: Into<String>>(kind: ShErrKind, message: S) -> Self {
|
||||
Self::Simple { kind, message: message.into() }
|
||||
}
|
||||
pub fn io() -> Self {
|
||||
io::Error::last_os_error().into()
|
||||
}
|
||||
pub fn full<S: Into<String>>(kind: ShErrKind, message: S, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
let blame = BlamePair::new(input.to_string(), span);
|
||||
Self::Full { kind, message: message.into(), blame }
|
||||
}
|
||||
pub fn try_blame(&mut self, input: String, span: Rc<RefCell<Span>>) {
|
||||
let blame_pair = BlamePair::new(input, span);
|
||||
match self {
|
||||
Self::Full {..} => {
|
||||
/* Do not overwrite */
|
||||
}
|
||||
Self::Simple { kind, message } => {
|
||||
*self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn blame(&mut self, input: String, span: Rc<RefCell<Span>>) {
|
||||
let blame_pair = BlamePair::new(input, span);
|
||||
match self {
|
||||
Self::Full { kind: _, message: _, blame } => {
|
||||
*blame = blame_pair;
|
||||
}
|
||||
Self::Simple { kind, message } => {
|
||||
*self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn with_msg(&mut self, new_message: String) {
|
||||
match self {
|
||||
Self::Full { kind: _, message, blame: _ } => {
|
||||
*message = new_message
|
||||
}
|
||||
Self::Simple { kind: _, message } => {
|
||||
*message = new_message
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn kind(&self) -> ShErrKind {
|
||||
match self {
|
||||
ShErr::Simple { kind, message: _ } => {
|
||||
*kind
|
||||
}
|
||||
ShErr::Full { kind, message: _, blame: _ } => {
|
||||
*kind
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn with_kind(&mut self, new_kind: ShErrKind) {
|
||||
match self {
|
||||
Self::Full { kind, message: _, blame: _ } => {
|
||||
*kind = new_kind
|
||||
}
|
||||
Self::Simple { kind, message: _ } => {
|
||||
*kind = new_kind
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn display_kind(&self) -> String {
|
||||
match self {
|
||||
ShErr::Simple { kind, message: _ } |
|
||||
ShErr::Full { kind, message: _, blame: _ } => {
|
||||
match kind {
|
||||
ShErrKind::IoErr => "I/O Error: ".into(),
|
||||
ShErrKind::SyntaxErr => "Syntax Error: ".into(),
|
||||
ShErrKind::ParseErr => "Parse Error: ".into(),
|
||||
ShErrKind::InternalErr => "Internal Error: ".into(),
|
||||
ShErrKind::ExecFail => "Execution Failed: ".into(),
|
||||
ShErrKind::Errno => "ERRNO: ".into(),
|
||||
ShErrKind::CmdNotFound => "Command not found: ".into(),
|
||||
ShErrKind::CleanExit |
|
||||
ShErrKind::FuncReturn |
|
||||
ShErrKind::LoopContinue |
|
||||
ShErrKind::LoopBreak |
|
||||
ShErrKind::Null => "".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_line(&self) -> (usize,usize,String) {
|
||||
if let ShErr::Full { kind: _, message: _, blame } = self {
|
||||
unsafe {
|
||||
let mut dist = 0;
|
||||
let mut line_no = 0;
|
||||
let window = self.get_window();
|
||||
let mut lines = window.lines();
|
||||
while let Some(line) = lines.next() {
|
||||
line_no += 1;
|
||||
dist += line.len();
|
||||
if dist > blame.start() {
|
||||
dist -= line.len();
|
||||
let offset = blame.start() - dist;
|
||||
return (offset,line_no,line.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
(0,0,String::new())
|
||||
} else {
|
||||
(0,0,String::new())
|
||||
}
|
||||
}
|
||||
pub fn get_window(&self) -> String {
|
||||
if let ShErr::Full { kind: _, message: _, blame } = self.clone() {
|
||||
let window: String = blame.into();
|
||||
window.split_once('\n').unwrap_or((&window,"")).0.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ShErr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let error_display = match self {
|
||||
ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message),
|
||||
ShErr::Full { kind: _, message, blame } => {
|
||||
let (offset,line_no,line_text) = self.get_line();
|
||||
let dist = blame.end().saturating_sub(blame.start());
|
||||
let padding = " ".repeat(offset);
|
||||
let line_inner = "~".repeat(dist.saturating_sub(2));
|
||||
let err_kind = &self.display_kind().styled(Style::Red | Style::Bold);
|
||||
let stat_line = format!("[{}:{}] - {}{}",line_no,offset,err_kind,message);
|
||||
let indicator_line = if dist == 1 {
|
||||
format!("{}^",padding)
|
||||
} else {
|
||||
format!("{}^{}^",padding,line_inner)
|
||||
};
|
||||
let error_full = format!("\n{}\n{}\n{}\n",stat_line,line_text,indicator_line);
|
||||
|
||||
error_full
|
||||
}
|
||||
};
|
||||
write!(f,"{}",error_display)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
pub mod sys;
|
||||
#[macro_use]
|
||||
pub mod utils;
|
||||
pub mod collections;
|
||||
pub mod error;
|
||||
pub mod term;
|
||||
|
||||
118
src/libsh/sys.rs
118
src/libsh/sys.rs
@@ -1,118 +0,0 @@
|
||||
use std::{fmt::Display, os::{fd::AsRawFd, unix::fs::PermissionsExt}};
|
||||
|
||||
use nix::sys::termios;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||
|
||||
pub fn get_path_cmds() -> ShResult<Vec<String>> {
|
||||
let mut cmds = vec![];
|
||||
let path_var = std::env::var("PATH")?;
|
||||
let paths = path_var.split(':');
|
||||
|
||||
for path in paths {
|
||||
let path = PathBuf::from(&path);
|
||||
if path.is_dir() {
|
||||
let path_files = std::fs::read_dir(&path)?;
|
||||
for file in path_files {
|
||||
let file_path = file?.path();
|
||||
if file_path.is_file() {
|
||||
if let Ok(meta) = std::fs::metadata(&file_path) {
|
||||
let perms = meta.permissions();
|
||||
if perms.mode() & 0o111 != 0 {
|
||||
let file_name = file_path.file_name().unwrap();
|
||||
cmds.push(file_name.to_str().unwrap().to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cmds)
|
||||
}
|
||||
|
||||
pub fn get_bin_path(command: &str, shenv: &ShEnv) -> Option<PathBuf> {
|
||||
let env = shenv.vars().env();
|
||||
let path_var = env.get("PATH")?;
|
||||
let mut paths = path_var.split(':');
|
||||
|
||||
let script_check = PathBuf::from(command);
|
||||
if script_check.is_file() {
|
||||
return Some(script_check)
|
||||
}
|
||||
while let Some(raw_path) = paths.next() {
|
||||
let mut path = PathBuf::from(raw_path);
|
||||
path.push(command);
|
||||
//TODO: handle this unwrap
|
||||
if path.exists() {
|
||||
return Some(path)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn write_out(text: impl Display) -> ShResult<()> {
|
||||
write(borrow_fd(1), text.to_string().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_err(text: impl Display) -> ShResult<()> {
|
||||
write(borrow_fd(2), text.to_string().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return is `readpipe`, `writepipe`
|
||||
/// Contains all of the necessary boilerplate for grabbing two pipe fds using libc::pipe()
|
||||
pub fn c_pipe() -> Result<(RawFd,RawFd),Errno> {
|
||||
let mut pipes: [i32;2] = [0;2];
|
||||
let ret = unsafe { libc::pipe(pipes.as_mut_ptr()) };
|
||||
if ret < 0 {
|
||||
return Err(Errno::from_raw(ret))
|
||||
}
|
||||
Ok((pipes[0],pipes[1]))
|
||||
}
|
||||
|
||||
pub fn sh_quit(code: i32) -> ! {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
if let Some(termios) = crate::get_saved_termios() {
|
||||
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
}
|
||||
if code == 0 {
|
||||
write_err("exit\n").ok();
|
||||
} else {
|
||||
write_err(format!("exit {code}\n")).ok();
|
||||
}
|
||||
exit(code);
|
||||
}
|
||||
|
||||
pub fn read_to_string(fd: i32) -> ShResult<String> {
|
||||
let mut buf = Vec::with_capacity(4096);
|
||||
let mut temp_buf = [0u8;1024];
|
||||
|
||||
loop {
|
||||
match read(fd, &mut temp_buf) {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(n) => buf.extend_from_slice(&temp_buf[..n]),
|
||||
Err(Errno::EINTR) => continue, // Retry on EINTR
|
||||
Err(e) => return Err(e.into()), // Return other errors
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&buf).to_string())
|
||||
}
|
||||
|
||||
pub fn execvpe(cmd: String, argv: Vec<String>, envp: Vec<String>) -> Result<(),Errno> {
|
||||
let cmd_raw = CString::new(cmd).unwrap();
|
||||
|
||||
let argv = argv.into_iter().map(|arg| CString::new(arg).unwrap()).collect::<Vec<CString>>();
|
||||
let envp = envp.into_iter().map(|var| CString::new(var).unwrap()).collect::<Vec<CString>>();
|
||||
|
||||
nix::unistd::execvpe(&cmd_raw, &argv, &envp).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
use std::{fmt::Display, ops::BitOr};
|
||||
|
||||
pub trait Styled: Sized + Display {
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||
let styles: StyleSet = style.into();
|
||||
let reset = Style::Reset;
|
||||
format!("{styles}{self}{reset}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Styled for T {}
|
||||
|
||||
/// Enum representing a single ANSI style
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
// Undoes all styles
|
||||
Reset,
|
||||
// Foreground Colors
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
@@ -20,36 +32,86 @@ pub enum Style {
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite,
|
||||
RGB(u8, u8, u8), // Custom foreground color
|
||||
|
||||
// Background Colors
|
||||
BgBlack,
|
||||
BgRed,
|
||||
BgGreen,
|
||||
BgYellow,
|
||||
BgBlue,
|
||||
BgMagenta,
|
||||
BgCyan,
|
||||
BgWhite,
|
||||
BgBrightBlack,
|
||||
BgBrightRed,
|
||||
BgBrightGreen,
|
||||
BgBrightYellow,
|
||||
BgBrightBlue,
|
||||
BgBrightMagenta,
|
||||
BgBrightCyan,
|
||||
BgBrightWhite,
|
||||
BgRGB(u8, u8, u8), // Custom background color
|
||||
|
||||
// Text Attributes
|
||||
Bold,
|
||||
Dim,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikethrough,
|
||||
Reversed,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
impl Display for Style {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Style::Reset => "\x1b[0m",
|
||||
Style::Black => "\x1b[30m",
|
||||
Style::Red => "\x1b[31m",
|
||||
Style::Green => "\x1b[32m",
|
||||
Style::Yellow => "\x1b[33m",
|
||||
Style::Blue => "\x1b[34m",
|
||||
Style::Magenta => "\x1b[35m",
|
||||
Style::Cyan => "\x1b[36m",
|
||||
Style::White => "\x1b[37m",
|
||||
Style::BrightBlack => "\x1b[90m",
|
||||
Style::BrightRed => "\x1b[91m",
|
||||
Style::BrightGreen => "\x1b[92m",
|
||||
Style::BrightYellow => "\x1b[93m",
|
||||
Style::BrightBlue => "\x1b[94m",
|
||||
Style::BrightMagenta => "\x1b[95m",
|
||||
Style::BrightCyan => "\x1b[96m",
|
||||
Style::BrightWhite => "\x1b[97m",
|
||||
Style::Bold => "\x1b[1m",
|
||||
Style::Italic => "\x1b[3m",
|
||||
Style::Underline => "\x1b[4m",
|
||||
Style::Reversed => "\x1b[7m",
|
||||
Style::Reset => write!(f, "\x1b[0m"),
|
||||
|
||||
// Foreground colors
|
||||
Style::Black => write!(f, "\x1b[30m"),
|
||||
Style::Red => write!(f, "\x1b[31m"),
|
||||
Style::Green => write!(f, "\x1b[32m"),
|
||||
Style::Yellow => write!(f, "\x1b[33m"),
|
||||
Style::Blue => write!(f, "\x1b[34m"),
|
||||
Style::Magenta => write!(f, "\x1b[35m"),
|
||||
Style::Cyan => write!(f, "\x1b[36m"),
|
||||
Style::White => write!(f, "\x1b[37m"),
|
||||
Style::BrightBlack => write!(f, "\x1b[90m"),
|
||||
Style::BrightRed => write!(f, "\x1b[91m"),
|
||||
Style::BrightGreen => write!(f, "\x1b[92m"),
|
||||
Style::BrightYellow => write!(f, "\x1b[93m"),
|
||||
Style::BrightBlue => write!(f, "\x1b[94m"),
|
||||
Style::BrightMagenta => write!(f, "\x1b[95m"),
|
||||
Style::BrightCyan => write!(f, "\x1b[96m"),
|
||||
Style::BrightWhite => write!(f, "\x1b[97m"),
|
||||
Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"),
|
||||
|
||||
// Background colors
|
||||
Style::BgBlack => write!(f, "\x1b[40m"),
|
||||
Style::BgRed => write!(f, "\x1b[41m"),
|
||||
Style::BgGreen => write!(f, "\x1b[42m"),
|
||||
Style::BgYellow => write!(f, "\x1b[43m"),
|
||||
Style::BgBlue => write!(f, "\x1b[44m"),
|
||||
Style::BgMagenta => write!(f, "\x1b[45m"),
|
||||
Style::BgCyan => write!(f, "\x1b[46m"),
|
||||
Style::BgWhite => write!(f, "\x1b[47m"),
|
||||
Style::BgBrightBlack => write!(f, "\x1b[100m"),
|
||||
Style::BgBrightRed => write!(f, "\x1b[101m"),
|
||||
Style::BgBrightGreen => write!(f, "\x1b[102m"),
|
||||
Style::BgBrightYellow => write!(f, "\x1b[103m"),
|
||||
Style::BgBrightBlue => write!(f, "\x1b[104m"),
|
||||
Style::BgBrightMagenta => write!(f, "\x1b[105m"),
|
||||
Style::BgBrightCyan => write!(f, "\x1b[106m"),
|
||||
Style::BgBrightWhite => write!(f, "\x1b[107m"),
|
||||
Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"),
|
||||
|
||||
// Text attributes
|
||||
Style::Bold => write!(f, "\x1b[1m"),
|
||||
Style::Dim => write!(f, "\x1b[2m"), // New
|
||||
Style::Italic => write!(f, "\x1b[3m"),
|
||||
Style::Underline => write!(f, "\x1b[4m"),
|
||||
Style::Strikethrough => write!(f, "\x1b[9m"), // New
|
||||
Style::Reversed => write!(f, "\x1b[7m"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +124,7 @@ pub struct StyleSet {
|
||||
|
||||
impl StyleSet {
|
||||
pub fn new() -> Self {
|
||||
Self { styles: Vec::new() }
|
||||
Self { styles: vec![] }
|
||||
}
|
||||
|
||||
pub fn add(mut self, style: Style) -> Self {
|
||||
@@ -71,9 +133,14 @@ impl StyleSet {
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> String {
|
||||
self.styles.iter().map(|s| s.as_str()).collect::<String>()
|
||||
impl Display for StyleSet {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for style in &self.styles {
|
||||
style.fmt(f)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +148,7 @@ impl StyleSet {
|
||||
impl BitOr for Style {
|
||||
type Output = StyleSet;
|
||||
|
||||
fn bitor(self, rhs: Self) -> StyleSet {
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
StyleSet::new().add(self).add(rhs)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +157,7 @@ impl BitOr for Style {
|
||||
impl BitOr<Style> for StyleSet {
|
||||
type Output = StyleSet;
|
||||
|
||||
fn bitor(self, rhs: Style) -> StyleSet {
|
||||
fn bitor(self, rhs: Style) -> Self::Output {
|
||||
self.add(rhs)
|
||||
}
|
||||
}
|
||||
@@ -100,9 +167,3 @@ impl From<Style> for StyleSet {
|
||||
StyleSet::new().add(style)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply styles to a string
|
||||
pub fn style_text<Str: Display, Sty: Into<StyleSet>>(text: Str, styles: Sty) -> String {
|
||||
let styles = styles.into();
|
||||
format!("{}{}{}", styles.as_str(), text, Style::Reset.as_str())
|
||||
}
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
use core::fmt::{Debug, Display, Write};
|
||||
use std::{os::fd::{AsRawFd, BorrowedFd}, str::FromStr};
|
||||
|
||||
|
||||
use crate::{parse::lex::EXPANSIONS, prelude::*};
|
||||
|
||||
use super::term::StyleSet;
|
||||
|
||||
pub trait RedirTargetType {
|
||||
fn as_tgt(self) -> RedirTarget;
|
||||
}
|
||||
|
||||
impl RedirTargetType for PathBuf {
|
||||
fn as_tgt(self) -> RedirTarget {
|
||||
RedirTarget::File(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RedirTargetType for i32 {
|
||||
fn as_tgt(self) -> RedirTarget {
|
||||
RedirTarget::Fd(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StrOps {
|
||||
/// This function operates on anything that implements `AsRef<str>` and `Display`, which is mainly strings.
|
||||
/// It takes a 'Style' which can be passed as a single Style object like `Style::Cyan` or a Bit OR of many styles,
|
||||
/// For instance: `Style::Red | Style::Bold | Style::Italic`
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Display> StrOps for T {
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||
style_text(&self, style)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArgVec {
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String>;
|
||||
fn drop_first(self) -> Vec<Token>;
|
||||
}
|
||||
|
||||
impl ArgVec for Vec<Token> {
|
||||
/// Converts the contained tokens into strings.
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String> {
|
||||
let mut argv_iter = self.into_iter();
|
||||
let mut argv_processed = vec![];
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let cleaned = clean_string(&arg.as_raw(shenv)).trim_matches(' ').to_string();
|
||||
argv_processed.push(cleaned);
|
||||
}
|
||||
argv_processed
|
||||
}
|
||||
/// This is used to ignore the first argument
|
||||
/// Most commonly used in builtins where execvpe is not used
|
||||
fn drop_first(self) -> Vec<Token> {
|
||||
self[1..].to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test {
|
||||
($test:block) => {
|
||||
$test
|
||||
exit(1)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum LogLevel {
|
||||
ERROR = 1,
|
||||
WARN = 2,
|
||||
INFO = 3,
|
||||
DEBUG = 4,
|
||||
TRACE = 5,
|
||||
NULL = 0
|
||||
}
|
||||
|
||||
impl Display for LogLevel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)),
|
||||
WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)),
|
||||
INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)),
|
||||
DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)),
|
||||
TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)),
|
||||
NULL => write!(f,"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:expr, $($var:ident),+) => {{
|
||||
$(
|
||||
let var_name = stringify!($var);
|
||||
if $level <= log_level() {
|
||||
let file = file!();
|
||||
let file_styled = file.styled(Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = line.to_string().styled(Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {} = {:#?}",$level, file_styled,line_styled,var_name, &$var);
|
||||
|
||||
write(borrow_fd(2),format!("{}\n",logged).as_bytes()).unwrap();
|
||||
}
|
||||
)+
|
||||
}};
|
||||
|
||||
($level:expr, $lit:literal) => {{
|
||||
if $level <= log_level() {
|
||||
let file = file!();
|
||||
let file_styled = file.styled(Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = line.to_string().styled(Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, $lit);
|
||||
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
|
||||
}
|
||||
}};
|
||||
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
if $level <= log_level() {
|
||||
let formatted = format!($($arg)*);
|
||||
let file = file!();
|
||||
let file_styled = file.styled(Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = line.to_string().styled(Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, formatted);
|
||||
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bp {
|
||||
($var:expr) => {
|
||||
log!($var);
|
||||
let mut buf = String::new();
|
||||
readln!("Press enter to continue", buf);
|
||||
};
|
||||
($($arg:tt)*) => {
|
||||
log!($(arg)*);
|
||||
let mut buf = String::new();
|
||||
readln!("Press enter to continue", buf);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn borrow_fd<'a>(fd: i32) -> BorrowedFd<'a> {
|
||||
unsafe { BorrowedFd::borrow_raw(fd) }
|
||||
}
|
||||
|
||||
// TODO: add more of these
|
||||
#[derive(Debug,Clone,PartialEq,Copy)]
|
||||
pub enum RedirType {
|
||||
Input,
|
||||
Output,
|
||||
Append,
|
||||
HereDoc,
|
||||
HereString
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum RedirTarget {
|
||||
Fd(i32),
|
||||
File(PathBuf),
|
||||
HereDoc(String),
|
||||
HereString(String),
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct RedirBldr {
|
||||
src: Option<i32>,
|
||||
op: Option<RedirType>,
|
||||
tgt: Option<RedirTarget>,
|
||||
}
|
||||
|
||||
impl RedirBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { src: None, op: None, tgt: None }
|
||||
}
|
||||
pub fn with_src(self, src: i32) -> Self {
|
||||
Self { src: Some(src), op: self.op, tgt: self.tgt }
|
||||
}
|
||||
pub fn with_op(self, op: RedirType) -> Self {
|
||||
Self { src: self.src, op: Some(op), tgt: self.tgt }
|
||||
}
|
||||
pub fn with_tgt(self, tgt: RedirTarget) -> Self {
|
||||
Self { src: self.src, op: self.op, tgt: Some(tgt) }
|
||||
}
|
||||
pub fn src(&self) -> Option<i32> {
|
||||
self.src
|
||||
}
|
||||
pub fn op(&self) -> Option<RedirType> {
|
||||
self.op
|
||||
}
|
||||
pub fn tgt(&self) -> Option<&RedirTarget> {
|
||||
self.tgt.as_ref()
|
||||
}
|
||||
pub fn build(self) -> Redir {
|
||||
Redir::new(self.src.unwrap(), self.op.unwrap(), self.tgt.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RedirBldr {
|
||||
type Err = ShErr;
|
||||
fn from_str(raw: &str) -> ShResult<Self> {
|
||||
let mut redir_bldr = RedirBldr::new().with_src(1);
|
||||
let mut chars = raw.chars().peekable();
|
||||
|
||||
let mut raw_src = String::new();
|
||||
while chars.peek().is_some_and(|ch| ch.is_ascii_digit()) {
|
||||
raw_src.push(chars.next().unwrap())
|
||||
}
|
||||
if !raw_src.is_empty() {
|
||||
let src = raw_src.parse::<i32>().unwrap();
|
||||
redir_bldr = redir_bldr.with_src(src);
|
||||
}
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'<' => {
|
||||
redir_bldr = redir_bldr.with_src(0);
|
||||
if chars.peek() == Some(&'<') {
|
||||
chars.next();
|
||||
if chars.peek() == Some(&'<') {
|
||||
chars.next();
|
||||
redir_bldr = redir_bldr.with_op(RedirType::HereString);
|
||||
break
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::HereDoc);
|
||||
let body = extract_heredoc_body(raw)?;
|
||||
redir_bldr = redir_bldr.with_tgt(RedirTarget::HereDoc(body));
|
||||
break
|
||||
}
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Input);
|
||||
}
|
||||
}
|
||||
'>' => {
|
||||
if chars.peek() == Some(&'>') {
|
||||
chars.next();
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Append);
|
||||
break
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Output);
|
||||
break
|
||||
}
|
||||
}
|
||||
'&' => {
|
||||
let mut raw_tgt = String::new();
|
||||
while chars.peek().is_some_and(|ch| ch.is_ascii_digit()) {
|
||||
raw_tgt.push(chars.next().unwrap())
|
||||
}
|
||||
let redir_target = RedirTarget::Fd(raw_tgt.parse::<i32>().unwrap());
|
||||
redir_bldr = redir_bldr.with_tgt(redir_target);
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
Ok(redir_bldr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Redir {
|
||||
pub src: i32,
|
||||
pub op: RedirType,
|
||||
pub tgt: RedirTarget
|
||||
}
|
||||
|
||||
impl Redir {
|
||||
pub fn new(src: i32, op: RedirType, tgt: RedirTarget) -> Self {
|
||||
Self { src, op, tgt }
|
||||
}
|
||||
pub fn output(src: i32, tgt: impl RedirTargetType) -> Self {
|
||||
Self::new(src, RedirType::Output, tgt.as_tgt())
|
||||
}
|
||||
pub fn input(src: i32, tgt: impl RedirTargetType) -> Self {
|
||||
Self::new(src, RedirType::Input, tgt.as_tgt())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct CmdRedirs {
|
||||
open: Vec<RawFd>,
|
||||
targets_fd: Vec<Redir>,
|
||||
targets_file: Vec<Redir>,
|
||||
targets_text: Vec<Redir>,
|
||||
}
|
||||
|
||||
impl CmdRedirs {
|
||||
pub fn new(mut redirs: Vec<Redir>) -> Self {
|
||||
let mut targets_fd = vec![];
|
||||
let mut targets_file = vec![];
|
||||
let mut targets_text = vec![];
|
||||
while let Some(redir) = redirs.pop() {
|
||||
let Redir { src: _, op: _, tgt } = &redir;
|
||||
match tgt {
|
||||
RedirTarget::Fd(_) => targets_fd.push(redir),
|
||||
RedirTarget::File(_) => targets_file.push(redir),
|
||||
_ => targets_text.push(redir)
|
||||
}
|
||||
}
|
||||
Self { open: vec![], targets_fd, targets_file, targets_text }
|
||||
}
|
||||
pub fn activate(&mut self) -> ShResult<()> {
|
||||
self.open_file_tgts()?;
|
||||
self.open_fd_tgts()?;
|
||||
self.open_text_tgts()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_text_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_text.pop() {
|
||||
let Redir { src, op: _, tgt } = redir;
|
||||
let (rpipe, wpipe) = c_pipe()?;
|
||||
let src = borrow_fd(src);
|
||||
let wpipe_fd = borrow_fd(wpipe);
|
||||
match tgt {
|
||||
RedirTarget::HereDoc(body) |
|
||||
RedirTarget::HereString(body) => {
|
||||
write(wpipe_fd, body.as_bytes())?;
|
||||
close(wpipe).ok();
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
dup2(rpipe, src.as_raw_fd())?;
|
||||
close(rpipe).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_file_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_file.pop() {
|
||||
let Redir { src, op, tgt } = redir;
|
||||
let src = borrow_fd(src);
|
||||
|
||||
let file_fd = if let RedirTarget::File(path) = tgt {
|
||||
let flags = match op {
|
||||
RedirType::Input => OFlag::O_RDONLY,
|
||||
RedirType::Output => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
|
||||
RedirType::Append => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_APPEND,
|
||||
_ => unimplemented!()
|
||||
};
|
||||
let mode = Mode::from_bits(0o644).unwrap();
|
||||
open(&path,flags,mode)?
|
||||
} else { unreachable!() };
|
||||
|
||||
dup2(file_fd.as_raw_fd(),src.as_raw_fd())?;
|
||||
close(file_fd.as_raw_fd())?;
|
||||
self.open.push(src.as_raw_fd());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_fd_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_fd.pop() {
|
||||
let Redir { src, op: _, tgt } = redir;
|
||||
let tgt = if let RedirTarget::Fd(fd) = tgt {
|
||||
borrow_fd(fd)
|
||||
} else { unreachable!() };
|
||||
let src = borrow_fd(src);
|
||||
dup2(tgt.as_raw_fd(), src.as_raw_fd())?;
|
||||
close(tgt.as_raw_fd())?;
|
||||
self.open.push(src.as_raw_fd());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_heredoc_body(body: &str) -> ShResult<String> {
|
||||
log!(DEBUG,body);
|
||||
if let Some(cleaned) = body.strip_prefix("<<") {
|
||||
if let Some((delim,body)) = cleaned.split_once('\n') {
|
||||
if let Some(body) = body.trim().strip_suffix(&delim) {
|
||||
Ok(body.to_string())
|
||||
} else {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Malformed closing delimiter in heredoc"))
|
||||
}
|
||||
} else {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Invalid heredoc delimiter"))
|
||||
}
|
||||
} else {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Invalid heredoc operator"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_expansion(s: &str) -> Option<TkRule> {
|
||||
let rule = Lexer::get_rule(s);
|
||||
if EXPANSIONS.contains(&rule) {
|
||||
Some(rule)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_string(s: impl ToString) -> String {
|
||||
let s = s.to_string();
|
||||
if (s.starts_with('"') && s.ends_with('"')) ||
|
||||
(s.starts_with('\'') && s.ends_with('\'')) ||
|
||||
(s.starts_with('`') && s.ends_with('`'))
|
||||
{
|
||||
if s.len() > 1 {
|
||||
s[1..s.len() - 1].to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
} else if s.starts_with("$(") && s.ends_with(')') {
|
||||
s[2..s.len() - 1].to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user