Compare commits

..

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

12 changed files with 328 additions and 331 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

@ -2,9 +2,28 @@ 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; #[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)
}
}

View File

@ -1,28 +1,27 @@
pub const DEFAULT_PS: &str = concat!("lambdashell-", env!("CARGO_PKG_VERSION"), " "); pub const DEFAULT_PS: &str = concat!("lambdashell-", env!("CARGO_PKG_VERSION"), " ");
#[derive(Debug)] #[derive(Debug, Clone)]
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) -> String {
pub fn get(&self) -> &str { self.0
self.0.as_str()
}
pub fn modify(&mut self, prompt: String) {
self.0 = prompt
} }
pub fn display(&self) { pub fn display(&self) {
print!("{}", self.0); 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| {
let name_os_string = name.to_os_string();
match name_os_string == whoami::username_os() && name_os_string != "root" {
true => "~".to_owned(), true => "~".to_owned(),
false => name.to_string_lossy().to_string(), false => name.to_string_lossy().to_string(),
} }
})) })
})
} }
} }

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,53 +1,29 @@
use std::{cell::RefCell, fs, io::{self}, rc::Rc}; use std::{fs, io::{self}};
use core::fmt; use core::fmt;
use color_print::ceprintln;
use crate::{ use crate::{
commands, history::History, ps::{self, Ps}, rc::{self}, vm::{self, LuauVm} commands, ps::{self, Ps}, rc, shell_error, vm::{self, LuauVm}, 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)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
pub norc: bool pub norc: bool
} }
pub struct LambdaShell { pub struct LambdaShell {
terminate: bool, terminate: bool,
history: History,
config: Config, config: Config,
vm: LuauVm, vm: LuauVm,
ps: Rc<RefCell<Ps>>, ps: Ps,
} }
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()))); let ps = Ps::set(ps::DEFAULT_PS.to_owned());
Self { Self {
ps: Rc::clone(&ps), vm: vm::LuauVm::new(ps.to_owned()),
vm: vm::LuauVm::new(ps),
history: History::init(),
terminate: false, terminate: false,
config, config,
ps
} }
} }
@ -55,11 +31,8 @@ impl LambdaShell {
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| match input.trim() {
"exit" => { "exit" => self.terminate = true,
self.terminate = true; trim => commands::Command::new(trim.to_owned()).exec()
self.history.add("exit");
},
trim => commands::Command::new(trim.to_owned()).exec(&mut self.history)
}) })
}) })
} }
@ -75,17 +48,16 @@ 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(); self.ps.display();
loop { loop {
if self.terminate { break } else { if self.terminate { break } else {
match self.wait() { match self.wait() {
Ok(()) => self.ps.borrow().display(), Ok(()) => self.ps.display(),
Err(flush_err) => self.error(flush_err), Err(flush_err) => self.error(flush_err),
} }
} }
} }
self.history.write_to_file_fallible();
} }
pub fn vm_exec(&self, source: String) { pub fn vm_exec(&self, source: String) {

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::{ps::Ps, vm::terminal::Terminal, MapDisplay};
mod shell; mod shell;
mod terminal; mod terminal;
@ -36,7 +34,7 @@ impl Globals for LuauVm {
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.vm.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(Self::CONV_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()))
@ -48,27 +46,27 @@ impl Globals for LuauVm {
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 {
vm: Luau, vm: Luau,
ps: Rc<RefCell<Ps>> ps: Ps,
} }
impl LuauVm { impl LuauVm {
pub(crate) fn new(ps: Rc<RefCell<Ps>>) -> Self { pub(crate) fn new(ps: Ps) -> Self {
Self { vm: Luau::new(), ps } Self { vm: Luau::new(), ps }
} }
fn set_shell_globals(&self) -> lResult<()> { fn set_shell_globals(&self) -> lResult<()> {
let luau_globals = self.vm.globals(); let luau_globals = self.vm.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)?;
luau_globals.set("getfenv", mlua::Nil)?;
luau_globals.set("setfenv", mlua::Nil)?;
self.vm.sandbox(true)?; self.vm.sandbox(true)?;
Ok(()) Ok(())
} }

View File

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

View File

@ -1,34 +1,38 @@
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.vm.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.vm.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, }
impl Colors for LuauVm {
fn background(&self, term_out_table: &Table) -> lResult<()> {
let foreground_table = self.vm.create_table()?;
foreground_styles_luau!(self, foreground_table,
dark_grey dark_red dark_green dark_cyan dark_grey dark_red dark_green dark_cyan
dark_yellow dark_magenta dark_blue dark_yellow dark_magenta dark_blue
red grey black green yellow red grey black green yellow
@ -40,7 +44,12 @@ fn text_styles_funcs(luau: &Luau) -> lResult<(Table, Table)> {
underline_blue underline_magenta underline_cyan underline_white underline_blue underline_magenta underline_cyan underline_white
bold bold
); );
background_styles_luau!(luau, background_table, term_out_table.set("FOREGROUND", foreground_table)
}
fn foreground(&self, term_out_table: &Table) -> lResult<()> {
let background_table = self.vm.create_table()?;
background_styles_luau!(self, background_table,
on_dark_grey on_dark_red on_dark_green on_dark_cyan on_dark_grey on_dark_red on_dark_green on_dark_cyan
on_dark_yellow on_dark_magenta on_dark_blue on_dark_yellow on_dark_magenta on_dark_blue
on_red on_grey on_black on_red on_grey on_black
@ -48,46 +57,56 @@ fn text_styles_funcs(luau: &Luau) -> lResult<(Table, Table)> {
on_blue on_magenta on_blue on_magenta
on_cyan on_white on_cyan on_white
); );
Ok((foreground_table, background_table)) term_out_table.set("BACKGROUND", background_table)
}
} }
fn fields_write_funcs<F: UserDataFields<Terminal>>(fields: &mut F) { trait Write {
fields.add_field_method_get("WRITE", |luau, _| luau.create_function(|_, s: String| -> lResult<()> { 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.vm.create_function(|_, s: String| -> lResult<()> {
print!("{s}"); print!("{s}");
Ok(()) Ok(())
})); })
fields.add_field_method_get("WRITE_ERROR", |luau, _| luau.create_function(|_, s: String| -> lResult<()> { }
fn write_error(&self) -> lResult<Function> {
self.vm.create_function(|_, s: String| -> lResult<()> {
eprint!("{s}"); eprint!("{s}");
Ok(()) Ok(())
})); })
fields.add_field_method_get("WRITE_ERROR_LN", |luau, _| luau.create_function(|_, s: String| -> lResult<()> { }
fn write_error_ln(&self) -> lResult<Function> {
self.vm.create_function(|_, s: String| -> lResult<()> {
eprintln!("{s}"); eprintln!("{s}");
Ok(()) Ok(())
})); })
}
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) {
// }
} }
pub trait TerminalGlobal { 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.vm.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.vm.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)
} }
} }