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:
@ -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()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>>())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
24
src/lib.rs
24
src/lib.rs
@ -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;
|
@ -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) {
|
||||
|
@ -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)]
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user