history now logs "cd" and "exit", history bug when starting a new session and inputting a new command then leaving will concat onto the old history's same line

This commit is contained in:
2025-01-12 02:00:56 -05:00
parent d40f4bece3
commit f8d97ded00
7 changed files with 99 additions and 127 deletions

View File

@ -1,41 +1,14 @@
use std::{io, process, str::SplitWhitespace, path::{Path, PathBuf}};
use uzers::User;
use std::{
io,
process,
str::SplitWhitespace,
path::{Path, PathBuf},
};
use crate::{history::History, MapDisplay};
use crate::{history::History, session::MapDisplay, valid_pbuf::IsValid};
enum ValidStatus {
NoRootFolder,
TryExists(io::Error)
}
trait PathBufIsValid {
fn is_valid(&self) -> Result<PathBuf, ValidStatus>;
fn is_valid_or_home(&self) -> Option<PathBuf>;
}
impl PathBufIsValid for PathBuf {
fn is_valid(&self) -> Result<PathBuf, ValidStatus> {
match self.try_exists() {
Ok(true) => Ok(self.to_path_buf()),
Ok(false) => Err(ValidStatus::NoRootFolder),
Err(trye_error) => Err(ValidStatus::TryExists(trye_error))
}
}
fn is_valid_or_home(&self) -> Option<PathBuf> {
match self.is_valid() {
Ok(valid) => Some(valid),
Err(valid_status) => {
match valid_status {
ValidStatus::NoRootFolder => println!("cd: /root: No such file or directory"),
ValidStatus::TryExists(error) => println!("cd: {error}"),
};
None
},
}
self.is_valid_or(self.is_dir(), home::home_dir)
}
}
@ -114,39 +87,27 @@ impl ChangeDirectory for Command {
}
}
pub struct Command {
input: String,
history: Option<History>
}
pub struct Command(String);
impl Command {
pub fn new(input: String) -> Self {
Self {
history: History::init(),
input
}
pub const fn new(input: String) -> Self {
Self(input)
}
pub fn write_history(&mut self) {
if let Some(history_file) = self.history.as_mut() {
history_file.write(&self.input);
};
}
pub fn spawn_handle(&mut self, command_process: io::Result<process::Child>) {
pub fn spawn_sys_cmd(&mut self, history: &mut History, command_process: io::Result<process::Child>) {
if let Ok(mut child) = command_process {
self.write_history();
history.add(self.0.as_str());
child.wait().ok();
} else {
println!("Unknown command: {}", self.input)
println!("Unknown command: {}", self.0)
}
}
pub fn exec(&mut self) {
let mut args = self.input.split_whitespace();
pub fn exec(&mut self, history: &mut History) {
let mut args = self.0.split_whitespace();
if let Some(command) = args.next() {
match command {
"cd" => { self.change_directory(args); },
command => { self.spawn_handle(process::Command::new(command).args(args).spawn()); }
"cd" => if self.change_directory(args).is_some() { history.add(self.0.as_str()) },
command => { self.spawn_sys_cmd(history, process::Command::new(command).args(args).spawn()); }
}
}
}

View File

@ -1,48 +1,52 @@
use std::{fs::{File, OpenOptions}, io::{BufRead, BufReader, Write}, path::PathBuf};
use crate::{rc::{self}, shell_error, valid_pbuf::IsValid, MapDisplay};
use crate::{rc::{self}, session::{self, MapDisplay}, valid_pbuf::IsValid};
#[derive(Debug, Clone)]
pub struct History {
history_file: PathBuf,
checked_empty: bool
file: Option<PathBuf>,
history: Vec<String>,
}
impl History {
pub fn init() -> Option<Self> {
rc::config_dir().map(|mut config| {
config.push(".history");
config.is_valid_file_or_create(b"");
Self {
history_file: config,
checked_empty: false
}
})
}
pub fn is_empty(&mut self) -> bool {
match self.checked_empty {
true => true,
false => self.read().map_or(false, |history_l| {
self.checked_empty = true;
history_l.is_empty()
})
pub fn init() -> Self {
Self {
history: Vec::new(),
file: rc::config_dir().map(|mut history_pbuf| {
history_pbuf.push(".history");
history_pbuf.is_valid_file_or_create(b"");
history_pbuf
}),
}
}
pub fn write<S: AsRef<str>>(&mut self, content: S) {
OpenOptions::new().append(true).open(self.history_file.as_path()).map_or_display(|mut file| {
let write_data = match self.is_empty() {
true => content.as_ref().to_owned(),
false => format!("\n{}", content.as_ref()),
};
if let Err(write_err) = file.write_all(write_data.as_bytes()) {
shell_error(write_err);
};
});
pub fn add(&mut self, command: &str) {
match self.history.last() {
Some(last_cmd) => if last_cmd != command { self.history.push(command.to_owned()); },
None => self.history.push(command.to_owned()),
};
}
pub fn read(&self) -> Option<Vec<String>> {
File::open(&self.history_file).map_or_display_none(|file| {
Some(BufReader::new(file).lines().map_while(Result::ok).collect::<Vec<String>>())
pub fn write_to_file_fallible(&mut self) {
if !self.history.is_empty() {
if let Some(history_file) = &self.file {
OpenOptions::new().append(true).open(history_file.as_path()).map_or_display(|mut file| {
let history_content = match self.history.len()==1 {
true => format!("\n{}", self.history[0]),
false => self.history.join("\n")
};
if let Err(write_err) = file.write_all(history_content.as_bytes()) {
session::shell_error(write_err);
};
});
}
}
}
pub fn read_file_fallible(&self) -> Option<Vec<String>> {
self.file.as_ref().and_then(|history_file| {
File::open(history_file).map_or_display_none(|file| {
Some(BufReader::new(file).lines().map_while(Result::ok).collect::<Vec<String>>())
})
})
}
}

View File

@ -7,26 +7,4 @@ pub mod ps;
pub mod rc;
pub mod vm;
mod valid_pbuf;
#[inline]
pub fn shell_error<E: core::fmt::Display>(err: E) {
color_print::ceprintln!("<bold,r>[!]:</> {err}")
}
pub trait MapDisplay<T, E: core::fmt::Display> {
fn map_or_display<F: FnOnce(T)>(self, f: F);
fn map_or_display_none<R, F: FnOnce(T) -> Option<R>>(self, f: F) -> Option<R>;
}
impl<T, E: core::fmt::Display> MapDisplay<T, E> for Result<T, E> {
///Map but display the error to stdout
#[inline]
fn map_or_display<F: FnOnce(T)>(self, f: F) {
self.map_or_else(|e| shell_error(e), f)
}
///Map but display the error to stdout and return `None`
#[inline]
fn map_or_display_none<R, F: FnOnce(T) -> Option<R>>(self, f: F) -> Option<R> {
self.map_or_else(|e| { shell_error(e); None }, f)
}
}
mod valid_pbuf;

View File

@ -1,17 +1,40 @@
use std::{cell::RefCell, fs, io::{self}, rc::Rc};
use core::fmt;
use color_print::ceprintln;
use crate::{
commands, ps::{self, Ps}, rc::{self}, shell_error, vm::{self, LuauVm}, MapDisplay
commands, history::History, ps::{self, Ps}, rc::{self}, vm::{self, LuauVm}
};
#[inline]
pub fn shell_error<E: fmt::Display>(err: E) {
ceprintln!("<bold,r>[!]:</> {err}")
}
pub trait MapDisplay<T, E: fmt::Display> {
fn map_or_display<F: FnOnce(T)>(self, f: F);
fn map_or_display_none<R, F: FnOnce(T) -> Option<R>>(self, f: F) -> Option<R>;
}
impl<T, E: fmt::Display> MapDisplay<T, E> for Result<T, E> {
///Map but display the error to stdout
#[inline]
fn map_or_display<F: FnOnce(T)>(self, f: F) {
self.map_or_else(|e| shell_error(e), f)
}
///Map but display the error to stdout and return `None`
#[inline]
fn map_or_display_none<R, F: FnOnce(T) -> Option<R>>(self, f: F) -> Option<R> {
self.map_or_else(|e| { shell_error(e); None }, f)
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub norc: bool
}
pub struct LambdaShell {
terminate: bool,
history: History,
config: Config,
vm: LuauVm,
ps: Rc<RefCell<Ps>>,
@ -22,6 +45,7 @@ impl LambdaShell {
Self {
ps: Rc::clone(&ps),
vm: vm::LuauVm::new(ps),
history: History::init(),
terminate: false,
config,
}
@ -31,8 +55,11 @@ impl LambdaShell {
io::Write::flush(&mut io::stdout()).map(|()| {
let mut input = String::new();
io::stdin().read_line(&mut input).map_or_display(|_size| match input.trim() {
"exit" => self.terminate = true,
trim => commands::Command::new(trim.to_owned()).exec()
"exit" => {
self.terminate = true;
self.history.add("exit");
},
trim => commands::Command::new(trim.to_owned()).exec(&mut self.history)
})
})
}
@ -49,6 +76,7 @@ impl LambdaShell {
}
}
self.ps.borrow().display();
loop {
if self.terminate { break } else {
match self.wait() {
@ -57,6 +85,7 @@ impl LambdaShell {
}
}
}
self.history.write_to_file_fallible();
}
pub fn vm_exec(&self, source: String) {

View File

@ -1,7 +1,7 @@
use std::{fs::{self, File}, io::{self, Write}, path::PathBuf};
use thiserror::Error;
use crate::MapDisplay;
use crate::session::MapDisplay;
#[derive(Debug, Error)]
#[allow(dead_code)]

View File

@ -5,7 +5,7 @@ use std::{cell::RefCell, rc::Rc};
use core::fmt;
use shell::ShellGlobal;
use crate::{ps::Ps, MapDisplay};
use crate::{ps::Ps, session::MapDisplay};
mod shell;
mod terminal;