Compare commits

..

No commits in common. "master" and "rc" have entirely different histories.
master ... rc

12 changed files with 352 additions and 365 deletions

32
Cargo.lock generated
View File

@ -10,9 +10,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.7.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]] [[package]]
name = "bstr" name = "bstr"
@ -32,9 +32,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.8" version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0cf6e91fde44c773c6ee7ec6bba798504641a8bc2eb7e37a04ffbf4dfaa55a" checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -194,9 +194,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.15" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -351,9 +351,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -384,9 +384,9 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.43" version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -465,9 +465,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -476,18 +476,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.11" version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.11" version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -12,3 +12,9 @@ mlua = { version = "0.10.0", features = ["luau-jit", "vendored"] }
thiserror = "2.0.9" thiserror = "2.0.9"
uzers = "0.12.1" uzers = "0.12.1"
whoami = "1.5.2" whoami = "1.5.2"
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1

View File

@ -1,16 +1,22 @@
use std::{io, process, str::SplitWhitespace, path::{Path, PathBuf}};
use uzers::User; use uzers::User;
use std::{
io,
process,
str::SplitWhitespace,
path::{Path, PathBuf},
};
use crate::{history::History, session::MapDisplay, valid_pbuf::IsValid}; use crate::MapDisplay;
enum ValidStatus {
NoRootFolder,
TryExists(io::Error)
}
trait PathBufIsValid { trait PathBufIsValid {
fn is_valid(&self) -> Result<PathBuf, ValidStatus>;
fn is_valid_or_home(&self) -> Option<PathBuf>; fn is_valid_or_home(&self) -> Option<PathBuf>;
} }
impl PathBufIsValid for PathBuf {
fn is_valid_or_home(&self) -> Option<PathBuf> {
self.is_valid_or(self.is_dir(), home::home_dir)
}
}
trait ChangeDirectory { trait ChangeDirectory {
fn change_directory(&self, args: SplitWhitespace) -> Option<PathBuf>; fn change_directory(&self, args: SplitWhitespace) -> Option<PathBuf>;
@ -20,6 +26,30 @@ trait ChangeDirectory {
fn previous_dir(&self) -> Option<PathBuf>; fn previous_dir(&self) -> Option<PathBuf>;
fn home_dir(&self) -> Option<PathBuf>; fn home_dir(&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
},
}
}
}
impl ChangeDirectory for Command { impl ChangeDirectory for Command {
fn set_current_dir(&self, new_path: &Path) -> Option<PathBuf> { fn set_current_dir(&self, new_path: &Path) -> Option<PathBuf> {
std::env::set_current_dir(new_path).map_or_display_none(|()| Some(new_path.to_path_buf())) std::env::set_current_dir(new_path).map_or_display_none(|()| Some(new_path.to_path_buf()))
@ -89,25 +119,24 @@ impl ChangeDirectory for Command {
pub struct Command(String); pub struct Command(String);
impl Command { impl Command {
pub const fn new(input: String) -> Self { pub fn new(input: String) -> Self {
Self(input) Self(input)
} }
pub fn spawn_sys_cmd(&mut self, history: &mut History, command_process: io::Result<process::Child>) { pub fn spawn(&self, command_process: io::Result<process::Child>) {
if let Ok(mut child) = command_process { command_process.map_or_display_none(|mut child| Some(child.wait()));
history.add(self.0.as_str());
child.wait().ok();
} else {
println!("Unknown command: {}", self.0)
}
} }
pub fn exec(&mut self, history: &mut History) { pub fn exec(&self) {
let mut args = self.0.split_whitespace(); let mut args = self.0.split_whitespace();
if let Some(command) = args.next() { if let Some(command) = args.next() {
match command { match command {
"cd" => if self.change_directory(args).is_some() { history.add(self.0.as_str()) }, "cd" => {
command => { self.spawn_sys_cmd(history, process::Command::new(command).args(args).spawn()); } self.change_directory(args);
},
command => {
self.spawn(process::Command::new(command).args(args).spawn());
}
} }
} }
} }

View File

@ -1,52 +0,0 @@
use std::{fs::{File, OpenOptions}, io::{BufRead, BufReader, Write}, path::PathBuf};
use crate::{rc::{self}, session::{self, MapDisplay}, valid_pbuf::IsValid};
#[derive(Debug, Clone)]
pub struct History {
fs_history: Option<Vec<String>>,
history: Vec<String>,
file: Option<PathBuf>,
}
impl History {
pub fn init() -> Self {
let file = rc::config_dir().map(|mut config_dir| {
config_dir.push(".history");
config_dir.is_valid_file_or_create(b"");
config_dir
});
let fs_history = file.as_ref().and_then(|file| {
File::open(file).map_or_display_none(|file| {
Some(BufReader::new(file).lines().map_while(Result::ok).collect::<Vec<String>>())
})
});
Self {
history: Vec::new(),
fs_history,
file,
}
}
pub fn write_to_file_fallible(&mut self) {
if self.history.is_empty() { return; }
if let (Some(history_file), Some(fs_history)) = (&self.file, &self.fs_history) {
OpenOptions::new()
.append(true)
.open(history_file.as_path())
.map_or_display(|mut file| {
let newline_maybe = if fs_history.is_empty() { "" } else { "\n" };
if let Err(write_err) = file.write_all(format!("{newline_maybe}{}", self.history.join("\n")).as_bytes()) {
session::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()),
};
}
}

View File

@ -1,10 +1,22 @@
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod session; pub mod session;
pub mod commands; pub mod commands;
pub mod history;
pub mod ps; pub mod ps;
pub mod rc; pub mod rc;
pub mod vm; pub mod vm;
mod valid_pbuf; pub trait MapDisplay<T, E: std::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: std::fmt::Display> MapDisplay<T, E> for Result<T, E> {
///Map and display an error
#[inline]
fn map_or_display<F: FnOnce(T)>(self, f: F) {
self.map_or_else(|e| color_print::ceprintln!("<bold,r>[!]:</> {e}"), f)
}
///Map and display an error but return `None`
#[inline]
fn map_or_display_none<R, F: FnOnce(T) -> Option<R>>(self, f: F) -> Option<R> {
self.map_or_else(|e| { color_print::ceprintln!("<bold,r>[!]:</> {e}"); None }, f)
}
}

View File

@ -1,28 +1,24 @@
pub const DEFAULT_PS: &str = concat!("lambdashell-", env!("CARGO_PKG_VERSION"), " "); pub const DEFAULT_PS: &str = concat!("lambdashell-", env!("CARGO_PKG_VERSION"));
#[derive(Debug)]
pub struct Ps(String); pub struct Ps(String);
impl Ps { impl Ps {
pub const fn set(prompt: String) -> Self { pub fn set(prompt: String) -> Self {
Self(prompt) Self(prompt)
} }
//rustc: `std::string::String::as_str` is not yet stable as a const fn
pub fn get(&self) -> &str {
self.0.as_str()
}
pub fn modify(&mut self, prompt: String) {
self.0 = prompt
}
pub fn display(&self) {
print!("{}", self.0);
}
pub fn working_dir_name(&self) -> String { pub fn working_dir_name(&self) -> String {
std::env::current_dir().map_or("?".to_owned(), |path| path.file_name().map_or("?".to_owned(), |name| { std::env::current_dir().map_or("?".to_owned(), |path| {
match name.to_os_string() == whoami::username_os() { path.file_name().map_or("?".to_owned(), |name| {
true => "~".to_owned(), let name_os_string = name.to_os_string();
false => name.to_string_lossy().to_string(), match name_os_string == whoami::username_os() && name_os_string != "root" {
} true => "~".to_owned(),
})) false => name.to_string_lossy().to_string(),
}
})
})
}
pub fn display(&self) {
print!("{}", self.0);
} }
} }

View File

@ -1,19 +1,83 @@
use std::path::PathBuf; use std::{path::PathBuf, fs::{self, File}, io::{self, Write}};
use thiserror::Error;
use crate::valid_pbuf::IsValid; use crate::MapDisplay;
pub const DEFAULT_CONFIG_CONTENT: &str = r#"--!strict pub const DEFAULT_CONFIG_CONTENT: &str = r#"--!strict
local cyan = TERMINAL.OUT.FOREGROUND.CYAN local cyan = TERMINAL.OUT.FOREGROUND.CYAN
local red = TERMINAL.OUT.FOREGROUND.RED
local username = cyan(SHELL.SYSTEM.USERNAME)
local hostname = SHELL.SYSTEM.HOSTNAME local hostname = SHELL.SYSTEM.HOSTNAME
local username = SHELL.SYSTEM.USERNAME
username = if username == "root" then red(username) else cyan(username)
SHELL.PROMPT = `{username}@{hostname} λ `"#; SHELL.PROMPT = `{username}@{hostname} λ `"#;
#[derive(Debug, Error)]
#[allow(dead_code)]
enum IsValidDirErr {
#[error("Failed to see if a file exists: {0}")]
TryExists(#[from] io::Error),
#[error("Not a valid entry")]
NotAnEntry,
#[error("Directory missing")]
Missing
}
#[allow(dead_code)]
enum CreateErr {
TryExists(io::Error),
Passable
}
#[allow(dead_code)]
trait IsValid {
fn is_valid(&self, is_dir_or_file: bool) -> Result<PathBuf, IsValidDirErr>;
fn is_valid_option(&self, is_dir_or_file: bool) -> Option<PathBuf>;
fn is_valid_file_or_create(&self, default_file_bytes: &[u8]) -> Option<PathBuf>;
fn is_valid_dir_or_create(&self) -> Option<PathBuf>;
fn is_valid_or<F>(&self, is_content: bool, f: F) -> Option<PathBuf>
where
F: FnOnce() -> Option<PathBuf>;
}
impl IsValid for PathBuf {
fn is_valid(&self, is_content: bool) -> Result<PathBuf, IsValidDirErr> {
match self.try_exists() {
Ok(true) => if is_content { Ok(self.to_path_buf()) } else { Err(IsValidDirErr::NotAnEntry) },
Ok(false) => Err(IsValidDirErr::Missing),
Err(try_e) => Err(IsValidDirErr::TryExists(try_e))
}
}
fn is_valid_or<F>(&self, is_content: bool, f: F) -> Option<PathBuf>
where
F: FnOnce() -> Option<PathBuf>
{
self.is_valid(is_content).map_err(|e| match e {
IsValidDirErr::TryExists(try_e) => CreateErr::TryExists(try_e),
IsValidDirErr::NotAnEntry | IsValidDirErr::Missing => CreateErr::Passable
}).map_or_else(|e| match e {
CreateErr::TryExists(_) => None,
CreateErr::Passable => f()
}, Some)
}
fn is_valid_dir_or_create(&self) -> Option<PathBuf> {
self.is_valid_or(self.is_dir(), || fs::create_dir(self).map_or_display_none(|()| Some(self.to_path_buf())))
}
fn is_valid_file_or_create(&self, default_file_bytes: &[u8]) -> Option<PathBuf> {
self.is_valid_or(self.is_file(), || {
File::create(self).map_or_display_none(|mut file| {
file.write_all(default_file_bytes).map_or_display_none(|()| Some(self.to_path_buf()))
})
})
}
fn is_valid_option(&self, is_dir_or_file: bool) -> Option<PathBuf> {
self.is_valid(is_dir_or_file).ok()
}
}
pub fn config_dir() -> Option<PathBuf> { pub fn config_dir() -> Option<PathBuf> {
let mut config = home::home_dir()?; let mut config = home::home_dir()?;
config.push(".config"); config.push(".config");
@ -27,3 +91,10 @@ pub fn config_file() -> Option<PathBuf> {
config_file.push("init.luau"); config_file.push("init.luau");
config_file.is_valid_file_or_create(DEFAULT_CONFIG_CONTENT.as_bytes()) config_file.is_valid_file_or_create(DEFAULT_CONFIG_CONTENT.as_bytes())
} }
// TODO: history.rs
pub fn history_file() -> Option<PathBuf> {
let mut config_file = config_dir()?;
config_file.push(".history");
config_file.is_valid_file_or_create(b"")
}

View File

@ -1,52 +1,29 @@
use std::{cell::RefCell, fs, io::{self}, rc::Rc}; use std::{fs, io::{self}};
use core::fmt;
use color_print::ceprintln;
use crate::{ use crate::{
commands, history::History, ps::{self, Ps}, rc::{self}, vm::{self, LuauVm} vm::{LuauVm, self},
commands,
ps,
rc,
MapDisplay,
}; };
#[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 struct Config {
pub norc: bool pub norc: bool
} }
pub struct LambdaShell { pub struct LambdaShell {
terminate: bool,
history: History,
config: Config,
vm: LuauVm, vm: LuauVm,
ps: Rc<RefCell<Ps>>, ps1: String,
config: Config,
terminating: bool,
} }
impl LambdaShell { impl LambdaShell {
pub fn create(config: Config) -> Self { pub fn create(config: Config) -> Self {
let ps = Rc::new(RefCell::new(Ps::set(ps::DEFAULT_PS.to_owned())));
Self { Self {
ps: Rc::clone(&ps), vm: vm::LuauVm::new(),
vm: vm::LuauVm::new(ps), ps1: ps::DEFAULT_PS.to_owned(),
history: History::init(), terminating: false,
terminate: false,
config, config,
} }
} }
@ -54,19 +31,18 @@ impl LambdaShell {
pub fn wait(&mut self) -> Result<(), io::Error> { pub fn wait(&mut self) -> Result<(), io::Error> {
io::Write::flush(&mut io::stdout()).map(|()| { io::Write::flush(&mut io::stdout()).map(|()| {
let mut input = String::new(); let mut input = String::new();
io::stdin().read_line(&mut input).map_or_display(|_size| match input.trim() { io::stdin().read_line(&mut input).map_or_display(|_size| {
"exit" => { match input.trim() {
self.terminate = true; //special casey
self.history.add("exit"); "exit" => self.terminating = true,
}, trim => commands::Command::new(trim.to_owned()).exec()
trim => commands::Command::new(trim.to_owned()).exec(&mut self.history) };
}) })
}) })
} }
pub fn error<E: fmt::Display>(&mut self, err: E) { pub fn vm_exec(&self, source: String) {
shell_error(err); self.vm.exec(source);
self.terminate = true;
} }
pub fn start(&mut self) { pub fn start(&mut self) {
@ -75,20 +51,20 @@ impl LambdaShell {
fs::read_to_string(conf_file).map_or_display(|luau_conf| self.vm_exec(luau_conf)); fs::read_to_string(conf_file).map_or_display(|luau_conf| self.vm_exec(luau_conf));
} }
} }
self.ps.borrow().display();
loop { loop {
if self.terminate { break } else { match self.terminating {
match self.wait() { true => break,
Ok(()) => self.ps.borrow().display(), false => {
Err(flush_err) => self.error(flush_err),
} match self.wait() {
Ok(()) => {},
Err(flush_error) => {
println!("{flush_error}");
break;
}
}
},
} }
} }
self.history.write_to_file_fallible();
}
pub fn vm_exec(&self, source: String) {
self.vm.exec(source);
} }
} }

View File

@ -1,75 +0,0 @@
use std::{fs::{self, File}, io::{self, Write}, path::PathBuf};
use thiserror::Error;
use crate::session::MapDisplay;
#[derive(Debug, Error)]
#[allow(dead_code)]
pub enum IsValidDirErr {
#[error("Failed to see if a file exists: {0}")]
TryExists(#[from] io::Error),
#[error("Not a valid entry")]
NotAnEntry,
#[error("Directory missing")]
Missing
}
#[allow(dead_code)]
enum CreateErr {
TryExists(io::Error),
Passable
}
#[allow(dead_code)]
pub trait IsValid {
fn is_valid(&self, is_dir_or_file: bool) -> Result<PathBuf, IsValidDirErr>;
fn is_valid_option(&self, is_dir_or_file: bool) -> Option<PathBuf>;
fn is_valid_file_or_create(&self, default_file_bytes: &[u8]) -> Option<PathBuf>;
fn is_valid_dir_or_create(&self) -> Option<PathBuf>;
fn is_valid_or<F>(&self, is_content: bool, f: F) -> Option<PathBuf>
where
F: FnOnce() -> Option<PathBuf>;
}
impl IsValid for PathBuf {
#[inline]
fn is_valid(&self, is_content: bool) -> Result<PathBuf, IsValidDirErr> {
match self.try_exists() {
Ok(true) => if is_content { Ok(self.to_path_buf()) } else { Err(IsValidDirErr::NotAnEntry) },
Ok(false) => Err(IsValidDirErr::Missing),
Err(try_e) => Err(IsValidDirErr::TryExists(try_e))
}
}
#[inline]
fn is_valid_or<F>(&self, is_content: bool, f: F) -> Option<PathBuf>
where
F: FnOnce() -> Option<PathBuf>
{
self.is_valid(is_content).map_err(|e| match e {
IsValidDirErr::TryExists(try_e) => CreateErr::TryExists(try_e),
IsValidDirErr::NotAnEntry | IsValidDirErr::Missing => CreateErr::Passable
}).map_or_else(|e| match e {
CreateErr::TryExists(_) => None,
CreateErr::Passable => f()
}, Some)
}
#[inline]
fn is_valid_dir_or_create(&self) -> Option<PathBuf> {
self.is_valid_or(self.is_dir(), || fs::create_dir(self).map_or_display_none(|()| Some(self.to_path_buf())))
}
#[inline]
fn is_valid_file_or_create(&self, default_file_bytes: &[u8]) -> Option<PathBuf> {
self.is_valid_or(self.is_file(), || {
File::create(self).map_or_display_none(|mut file| {
file.write_all(default_file_bytes).map_or_display_none(|()| Some(self.to_path_buf()))
})
})
}
#[inline]
fn is_valid_option(&self, is_dir_or_file: bool) -> Option<PathBuf> {
self.is_valid(is_dir_or_file).ok()
}
}

View File

@ -1,11 +1,9 @@
use mlua::{Function, Lua as Luau, MultiValue, Result as lResult, Table, Value}; use mlua::{Function, Lua as Luau, MultiValue, Result as lResult, Table, Value};
use color_print::{cformat, ceprintln}; use color_print::{cformat, ceprintln};
use terminal::TerminalGlobal;
use std::{cell::RefCell, rc::Rc};
use core::fmt; use core::fmt;
use shell::ShellGlobal; use shell::Shell;
use crate::{ps::Ps, session::MapDisplay}; use crate::{vm::terminal::Terminal, MapDisplay};
mod shell; mod shell;
mod terminal; mod terminal;
@ -26,54 +24,51 @@ impl<T, E: fmt::Display> LuauRuntimeErr<T> for Result<T, E> {
trait Globals { trait Globals {
const LIB_VERSION: &str; const LIB_VERSION: &str;
const CONV_ERROR: &str;
fn global_warn(&self, luau_globals: &Table) -> lResult<()>; fn global_warn(&self, luau_globals: &Table) -> lResult<()>;
fn global_version(&self, luau_globals: &Table) -> lResult<()>; fn global_version(&self, luau_globals: &Table) -> lResult<()>;
} }
impl Globals for LuauVm { impl Globals for LuauVm {
const LIB_VERSION: &str = env!("CARGO_PKG_VERSION"); const LIB_VERSION: &str = env!("CARGO_PKG_VERSION");
const CONV_ERROR: &str = "<SHELL CONVERSION ERROR>";
fn global_warn(&self, luau_globals: &Table) -> lResult<()> { fn global_warn(&self, luau_globals: &Table) -> lResult<()> {
let luau_print = luau_globals.get::<Function>("print")?; let luau_print = luau_globals.get::<Function>("print")?;
luau_globals.raw_set("warn", self.vm.create_function(move |this, args: MultiValue| -> lResult<()> { luau_globals.set("warn", self.0.create_function(move |this, args: MultiValue| -> lResult<()> {
let luau_multi_values = args.into_iter() let luau_multi_values = args.into_iter()
.map(|value| cformat!("<bold,y>{}</>", value.to_string().unwrap_or(Self::CONV_ERROR.to_owned()))) .map(|value| cformat!("<bold,y>{}</>", value.to_string().unwrap_or("<SHELL CONVERSION ERROR>".to_owned())))
.map(|arg_v| Value::String(this.create_string(arg_v).unwrap())) .map(|arg_v| Value::String(this.create_string(arg_v).unwrap()))
.collect::<MultiValue>(); .collect::<MultiValue>();
luau_print.call::<()>(luau_multi_values)?; luau_print.call::<()>(luau_multi_values).unwrap();
Ok(()) Ok(())
})?) })?)
} }
fn global_version(&self, luau_globals: &Table) -> lResult<()> { fn global_version(&self, luau_globals: &Table) -> lResult<()> {
let luau_info = luau_globals.get::<String>("_VERSION")?; let luau_info = luau_globals.get::<String>("_VERSION")?;
luau_globals.raw_set("_VERSION", format!("{luau_info}, liblambdashell {}", Self::LIB_VERSION)) luau_globals.set("_VERSION", format!("{luau_info}, liblambdashell {}", Self::LIB_VERSION))
} }
} }
pub struct LuauVm { pub struct LuauVm(pub Luau);
vm: Luau,
ps: Rc<RefCell<Ps>>
}
impl LuauVm { impl LuauVm {
pub(crate) fn new(ps: Rc<RefCell<Ps>>) -> Self { pub(crate) fn new() -> Self {
Self { vm: Luau::new(), ps } Self(Luau::new())
} }
fn set_shell_globals(&self) -> lResult<()> { fn set_shell_globals(&self) -> lResult<()> {
let luau_globals = self.vm.globals(); let luau_globals = self.0.globals();
self.global_shell(&luau_globals)?;
self.global_terminal(&luau_globals)?;
self.global_warn(&luau_globals)?; self.global_warn(&luau_globals)?;
self.global_version(&luau_globals)?; self.global_version(&luau_globals)?;
luau_globals.raw_set("getfenv", mlua::Nil)?; self.global_terminal(&luau_globals)?;
luau_globals.raw_set("setfenv", mlua::Nil)?; self.global_shell(&luau_globals)?;
self.vm.sandbox(true)?; luau_globals.set("getfenv", mlua::Nil)?;
luau_globals.set("setfenv", mlua::Nil)?;
self.0.sandbox(true)?;
Ok(()) Ok(())
} }
pub fn exec(&self, source: String) { pub fn exec(&self, source: String) {
self.set_shell_globals().map_or_display_none(|()| self.vm.load(source).exec().map_or_luau_rt_err(Some)); self.set_shell_globals().map_or_display_none(|()| {
self.0.load(source).exec().map_or_luau_rt_err(Some)
});
} }
} }

View File

@ -1,47 +1,57 @@
use mlua::{Lua as Luau, MetaMethod, Result as lResult, Table, UserData, UserDataFields, UserDataMethods}; use mlua::{Result as lResult, Table, Value};
use std::{cell::RefCell, rc::Rc};
use whoami::fallible; use whoami::fallible;
use crate::{ps::Ps, vm::LuauVm}; use crate::vm::LuauVm;
const DEFAULT_HOSTNAME: &str = "hostname"; const DEFAULT_HOSTNAME: &str = "hostname";
fn luau_sys_details(luau: &Luau) -> lResult<Table> { trait PsPrompt {
let system = luau.create_table()?; fn ps_prompt(&self) -> lResult<Table>;
system.raw_set("DESKTOP_ENV", whoami::desktop_env().to_string())?;
system.raw_set("DEVICENAME", whoami::devicename().to_string())?;
system.raw_set("USERNAME", whoami::username().to_string())?;
system.raw_set("REALNAME", whoami::realname().to_string())?;
system.raw_set("PLATFORM", whoami::platform().to_string())?;
system.raw_set("DISTRO", whoami::distro().to_string())?;
system.raw_set("ARCH", whoami::arch().to_string())?;
system.raw_set("HOSTNAME", fallible::hostname().unwrap_or(DEFAULT_HOSTNAME.to_owned()))?;
Ok(system)
} }
impl PsPrompt for LuauVm {
struct Shell(Rc<RefCell<Ps>>); fn ps_prompt(&self) -> lResult<Table> {
impl UserData for Shell { let prompt_table = self.0.create_table()?;
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { let prompt_metatable = self.0.create_table()?;
fields.add_field_method_get("PROMPT", |_, this| Ok(this.0.borrow().get().to_owned())); prompt_metatable.set("__index", self.0.create_function(|_, (table, index): (Table, Value)| -> lResult<String> {
fields.add_field_method_get("SYSTEM", |luau, _| luau_sys_details(luau)); table.raw_get::<String>(index)
} })?)?;
prompt_metatable.set("__newindex", self.0.create_function(|_, _: String| -> lResult<String> {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { Ok("placeholder".to_owned())
methods.add_meta_method_mut(MetaMethod::NewIndex, |_, this, (tindex, tvalue): (String, String)| -> lResult<()> { })?)?;
if tindex == "PROMPT" { prompt_table.set("__metatable", mlua::Nil)?;
this.0.borrow_mut().modify(tvalue); prompt_table.set_metatable(Some(prompt_metatable));
} prompt_table.set_readonly(false);
Ok(()) Ok(prompt_table)
});
} }
} }
pub trait ShellGlobal { trait System {
fn sys_details(&self) -> lResult<Table>;
}
impl System for LuauVm {
fn sys_details(&self) -> lResult<Table> {
let system = self.0.create_table()?;
system.set("DESKTOP_ENV", whoami::desktop_env().to_string())?;
system.set("DEVICENAME", whoami::devicename().to_string())?;
system.set("USERNAME", whoami::username().to_string())?;
system.set("REALNAME", whoami::realname().to_string())?;
system.set("PLATFORM", whoami::platform().to_string())?;
system.set("DISTRO", whoami::distro().to_string())?;
system.set("HOSTNAME", fallible::hostname().unwrap_or(DEFAULT_HOSTNAME.to_owned()))?;
Ok(system)
}
}
pub trait Shell {
fn global_shell(&self, luau_globals: &Table) -> lResult<()>; fn global_shell(&self, luau_globals: &Table) -> lResult<()>;
} }
impl ShellGlobal for LuauVm { impl Shell for LuauVm {
fn global_shell(&self, luau_globals: &Table) -> lResult<()> { fn global_shell(&self, luau_globals: &Table) -> lResult<()> {
luau_globals.set("SHELL", Shell(Rc::clone(&self.ps)))?; let shell = self.0.create_table()?;
let ps_prompt = self.ps_prompt()?;
shell.set("SYSTEM", self.sys_details()?)?;
shell.set("PROMPT", ps_prompt)?;
luau_globals.set("SHELL", shell)?;
Ok(()) Ok(())
} }
} }

View File

@ -1,93 +1,112 @@
use mlua::{UserDataFields, Lua as Luau, Result as lResult, Table, UserData}; use mlua::{Function, Result as lResult, Table};
use const_format::str_split; use const_format::str_split;
use crossterm::style::Stylize; use crossterm::style::Stylize;
use crate::vm::LuauVm; use crate::vm::LuauVm;
macro_rules! foreground_styles_luau { macro_rules! foreground_styles_luau {
($luau:expr, $style_table:expr, $($color:ident)+) => { ($self:expr, $style_table:expr, $($color:ident)+) => {
$( $(
$style_table.raw_set(stringify!($color).to_ascii_uppercase(), $luau.create_function(|_, text: String| -> lResult<String> { $style_table.set(stringify!($color).to_ascii_uppercase(), $self.0.create_function(|_, text: String| -> lResult<String> {
Ok(text.$color().to_string()) Ok(text.$color().to_string())
})?)?; })?)?;
)+ )+
}; };
} }
macro_rules! background_styles_luau { macro_rules! background_styles_luau {
($luau:expr, $style_table:expr, $($color:ident)+) => { ($self:expr, $style_table:expr, $($color:ident)+) => {
$( $(
$style_table.raw_set( $style_table.set(
str_split!(stringify!($color), "_")[1..].join("_").to_ascii_uppercase(), str_split!(stringify!($color), "_")[1..].join("_").to_ascii_uppercase(),
$luau.create_function(|_, text: String| -> lResult<String> { $self.0.create_function(|_, text: String| -> lResult<String> {
Ok(text.$color().to_string()) Ok(text.$color().to_string())
})?)?; })?)?;
)+ )+
}; };
} }
fn text_styles_funcs(luau: &Luau) -> lResult<(Table, Table)> { trait Colors {
let foreground_table = luau.create_table()?; fn background(&self, term_out_table: &Table) -> lResult<()>;
let background_table = luau.create_table()?; fn foreground(&self, term_out_table: &Table) -> lResult<()>;
foreground_styles_luau!(luau, foreground_table,
dark_grey dark_red dark_green dark_cyan
dark_yellow dark_magenta dark_blue
red grey black green yellow
blue magenta cyan white
underlined
underline_dark_grey underline_dark_red underline_dark_green underline_dark_cyan
underline_dark_yellow underline_dark_magenta underline_dark_blue underline_red
underline_grey underline_black underline_green underline_yellow
underline_blue underline_magenta underline_cyan underline_white
bold
);
background_styles_luau!(luau, background_table,
on_dark_grey on_dark_red on_dark_green on_dark_cyan
on_dark_yellow on_dark_magenta on_dark_blue
on_red on_grey on_black
on_green on_yellow
on_blue on_magenta
on_cyan on_white
);
Ok((foreground_table, background_table))
} }
impl Colors for LuauVm {
fn fields_write_funcs<F: UserDataFields<Terminal>>(fields: &mut F) { fn background(&self, term_out_table: &Table) -> lResult<()> {
fields.add_field_method_get("WRITE", |luau, _| luau.create_function(|_, s: String| -> lResult<()> { let foreground_table = self.0.create_table()?;
print!("{s}"); foreground_styles_luau!(self, foreground_table,
Ok(()) dark_grey dark_red dark_green dark_cyan
})); dark_yellow dark_magenta dark_blue
fields.add_field_method_get("WRITE_ERROR", |luau, _| luau.create_function(|_, s: String| -> lResult<()> { red grey black green yellow
eprint!("{s}"); blue magenta cyan white
Ok(()) underlined
})); underline_dark_grey underline_dark_red underline_dark_green underline_dark_cyan
fields.add_field_method_get("WRITE_ERROR_LN", |luau, _| luau.create_function(|_, s: String| -> lResult<()> { underline_dark_yellow underline_dark_magenta underline_dark_blue underline_red
eprintln!("{s}"); underline_grey underline_black underline_green underline_yellow
Ok(()) underline_blue underline_magenta underline_cyan underline_white
})); bold
} );
term_out_table.set("FOREGROUND", foreground_table)
struct Terminal;
impl UserData for Terminal {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields_write_funcs(fields);
fields.add_field_method_get("OUT", |luau, _| {
let (foreground, background) = text_styles_funcs(luau)?;
let out_table = luau.create_table()?;
out_table.raw_set("FOREGROUND", foreground)?;
out_table.raw_set("BACKGROUND", background)?;
Ok(out_table)
});
} }
// fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
// } fn foreground(&self, term_out_table: &Table) -> lResult<()> {
let background_table = self.0.create_table()?;
background_styles_luau!(self, background_table,
on_dark_grey on_dark_red on_dark_green on_dark_cyan
on_dark_yellow on_dark_magenta on_dark_blue
on_red on_grey on_black
on_green on_yellow
on_blue on_magenta
on_cyan on_white
);
term_out_table.set("BACKGROUND", background_table)
}
} }
pub trait TerminalGlobal { trait Write {
fn write(&self) -> lResult<Function>;
fn write_error(&self) -> lResult<Function>;
fn write_error_ln(&self) -> lResult<Function>;
}
impl Write for LuauVm {
fn write(&self) -> lResult<Function> {
self.0.create_function(|_, s: String| -> lResult<()> {
print!("{s}");
Ok(())
})
}
fn write_error(&self) -> lResult<Function> {
self.0.create_function(|_, s: String| -> lResult<()> {
eprint!("{s}");
Ok(())
})
}
fn write_error_ln(&self) -> lResult<Function> {
self.0.create_function(|_, s: String| -> lResult<()> {
eprintln!("{s}");
Ok(())
})
}
}
pub trait Terminal {
fn out(&self) -> lResult<Table>;
fn global_terminal(&self, luau_globals: &Table) -> lResult<()>; fn global_terminal(&self, luau_globals: &Table) -> lResult<()>;
} }
impl TerminalGlobal for LuauVm { impl Terminal for LuauVm {
fn out(&self) -> lResult<Table> {
let term_out_table = self.0.create_table()?;
self.background(&term_out_table)?;
self.foreground(&term_out_table)?;
Ok(term_out_table)
}
fn global_terminal(&self, luau_globals: &Table) -> lResult<()> { fn global_terminal(&self, luau_globals: &Table) -> lResult<()> {
luau_globals.raw_set("TERMINAL", Terminal) let term_table = self.0.create_table()?;
term_table.set("OUT", self.out()?)?;
term_table.set("WRITE", self.write()?)?;
term_table.set("WRITE_ERROR", self.write_error()?)?;
term_table.set("WRITE_ERROR_LN", self.write_error_ln()?)?;
luau_globals.set("TERMINAL", term_table)
} }
} }