First few commands

This commit is contained in:
Daniel Kluge 2023-12-22 11:44:46 +01:00
commit a5e3039f80
7 changed files with 846 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

123
Cargo.lock generated Normal file
View File

@ -0,0 +1,123 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "proc-macro2"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasm-bindgen"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
name = "wasm-terminal"
version = "0.1.0"
dependencies = [
"wasm-bindgen",
]

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "wasm-terminal"
version = "0.1.0"
edition = "2021"
authors = ["Daniel Kluge <daniel-git@c0ntroller.de>"]
description = "Console application with commands in WASM for my website"
license = "GPT-3"
repository = "https://git.c0ntroller.de/c0ntroller/wasm-terminal"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.89"

239
src/console/commands.rs Normal file
View File

@ -0,0 +1,239 @@
use std::rc::Rc;
use super::{Command, types::Content};
pub fn commands() -> Vec<Rc<Command>> {
let mut available_commands: Vec<Rc<Command>> = Vec::new();
available_commands.push(
Rc::new(Command::new(
"debug".to_string(),
"Prints the args and flags".to_string(),
vec![
Rc::new(super::Flag::new("long".to_string(), "l".to_string(), "A long flag".to_string())),
Rc::new(super::Flag::new("short".to_string(), "s".to_string(), "A short flag".to_string())),
],
|_console, args, flags| {
let mut output = String::from("Flags");
for flag in flags {
output.push_str(&format!("\n\tLong: {}\n\tShort: {}\n\tDescription: {}", flag.long, flag.short, flag.description));
}
output.push_str("\nArgs:");
for arg in args {
output.push_str(&format!("\n\t{}", arg));
}
Ok(Some(output))
},
)));
available_commands.push(
Rc::new(Command::new(
"echo".to_string(),
"Prints the args".to_string(),
Vec::new(),
|_console, args, _flags| {
let mut output = String::new();
for arg in args {
output.push_str(&format!("{} ", arg));
}
Ok(Some(output))
},
)));
available_commands.push(
Rc::new(Command::new(
"pwd".to_string(),
"Prints the current working directory".to_string(),
Vec::new(),
|console, args, _flags| {
if args.len() > 0 {
return Err("pwd does not take any arguments");
}
Ok(Some(console.get_current_dir().borrow().get_path()))
},
)));
available_commands.push(
Rc::new(Command::new(
"cd".to_string(),
"Changes the current working directory".to_string(),
Vec::new(),
|console, args, _flags| {
if args.len() != 1 {
return Err("Only the path must be specified!");
}
match console.cd(args[0].clone()) {
Ok(_) => Ok(None),
Err(e) => Err(e),
}
},
)));
available_commands.push(
Rc::new(Command::new(
"ls".to_string(),
"Lists the contents of the current directory".to_string(),
vec![
Rc::new(super::Flag::new("list".to_string(), "l".to_string(), "Print contents in a list".to_string())),
],
|console, args, flags| {
if args.len() > 0 {
return Err("ls does not take any arguments");
}
// TODO
// Virtual cd to read the contents there
let seperator = match flags.iter().find(|f| f.long == "list" ) {
Some(_) => "\n",
None => "\t",
};
let mut output = String::new();
let current_dir = console.get_current_dir();
for dir in current_dir.borrow().get_subdirs() {
output.push_str(&format!("{}{}", dir.borrow().get_name(), seperator));
}
for file in current_dir.borrow().get_files() {
output.push_str(&format!("{}{}", file.borrow().get_name(), seperator));
}
Ok(Some(output))
},
)));
available_commands.push(
Rc::new(Command::new(
"mkdir".to_string(),
"Creates a new directory".to_string(),
Vec::new(),
|console, args, _flags| {
if args.len() != 1 {
return Err("Only the name must be specified!");
}
// TODO
// virtual_cd
// Get the directory name from the path
let current_dir = console.get_current_dir();
let result = match current_dir.borrow_mut().mkdir(args[0].clone()) {
Ok(_) => Ok(None),
Err(e) => Err(e),
};
result
},
)));
available_commands.push(
Rc::new(Command::new(
"touch".to_string(),
"Creates a new file".to_string(),
Vec::new(),
|console, args, _flags| {
if args.len() != 1 {
return Err("Only the name must be specified!");
}
// TODO
// virtual_cd
// Get the directory name from the path
let current_dir = console.get_current_dir();
let result = match current_dir.borrow_mut().touch(args[0].clone()) {
Ok(_) => Ok(None),
Err(e) => Err(e),
};
result
},
)));
available_commands.push(
Rc::new(Command::new(
"cat".to_string(),
"Prints the contents of a file".to_string(),
Vec::new(),
|console, args, _flags| {
if args.len() != 1 {
return Err("Only the name must be specified!");
}
// TODO
// virtual_cd
// Get the directory name from the path
let current_dir = console.get_current_dir();
let file = match current_dir.borrow().get_file(args[0].clone()) {
Some(file) => file,
None => return Err("File not found"),
};
let content = file.borrow().read();
Ok(Some(content))
},
)));
available_commands.push(
Rc::new(Command::new(
"rm".to_string(),
"Removes a file or directory".to_string(),
Vec::new(),
|console, args, _flags| {
if args.len() != 1 {
return Err("Only the name must be specified!");
}
// TODO
// virtual_cd
// Get the directory name from the path
let current_dir = console.get_current_dir();
let result = match current_dir.borrow_mut().remove_file(args[0].clone()) {
Ok(_) => Ok(None),
Err(e) => Err(e),
};
result
},
)));
// write into file
available_commands.push(
Rc::new(Command::new(
"write".to_string(),
"Writes into a file".to_string(),
vec![
Rc::new(super::Flag::new("append".to_string(), "a".to_string(), "Append to the file".to_string())),
],
|console, args, flags| {
// TODO
// virtual_cd
// Get the directory name from the path
let current_dir = console.get_current_dir();
let file = match current_dir.borrow().get_file(args[0].clone()) {
Some(file) => file,
None => return Err("File not found"),
};
if flags.iter().find(|f| f.long == "append" ).is_some() {
file.borrow_mut().append(args[1].clone());
return Ok(None);
} else {
file.borrow_mut().write(args[1].clone());
Ok(None)
}
},
)));
available_commands
}

189
src/console/mod.rs Normal file
View File

@ -0,0 +1,189 @@
mod types;
mod commands;
use std::{cell::RefCell, rc::Rc};
use types::*;
use commands::commands;
use wasm_bindgen::prelude::*;
pub struct Flag {
pub long: String,
pub short: String,
pub description: String,
}
#[allow(unused)]
impl Flag {
pub fn new(long: String, short: String, description: String) -> Flag {
Flag {
long,
short,
description,
}
}
}
pub struct Command {
pub name: String,
pub description: String,
pub flags: Vec<Rc<Flag>>,
function: fn(&mut Console, Vec<String>, Vec<Rc<Flag>>) -> Result<Option<String>, &'static str>,
}
impl Command {
pub fn new(name: String, description: String, flags: Vec<Rc<Flag>>, function: fn(&mut Console, Vec<String>, Vec<Rc<Flag>>) -> Result<Option<String>, &'static str>) -> Command {
Command {
name,
description,
flags,
function,
}
}
fn parse_flags(&self, args: Vec<String>) -> (Vec<Rc<Flag>>, Vec<String>) {
let mut flags: Vec<Rc<Flag>> = Vec::new();
let mut other = Vec::new();
for arg in args {
if arg.starts_with("--") {
let f = match self.flags.iter().find(|f| arg[2..] == f.long) {
Some(f) => Rc::clone(f),
None => continue,
};
flags.push(f);
} else if arg.starts_with("-") {
let f = match self.flags.iter().find(|f| arg[1..] == f.short) {
Some(f) => Rc::clone(f),
None => continue,
};
flags.push(f);
} else {
other.push(arg);
}
}
(flags, other)
}
pub fn execute(&self, console: &mut Console, args: Vec<String>) -> Result<Option<String>, &'static str> {
let (flags, args) = self.parse_flags(args.clone());
(self.function)(console, args, flags)
}
}
#[wasm_bindgen]
pub struct Console {
root: Rc<RefCell<Directory>>,
pwd: RefCell<Vec<Rc<RefCell<Directory>>>>,
last_commands: Vec<String>,
available_commands: Vec<Rc<Command>>,
output: Vec<String>,
}
impl Console {
fn get_current_dir(&self) -> Rc<RefCell<Directory>> {
match self.pwd.borrow().last() {
Some(dir) => Rc::clone(dir),
None => Rc::clone(&self.root),
}
}
fn cd(&mut self, path: String) -> Result<(), &'static str> {
let segments = PathSegment::parse_path_segment(path);
let mut current = self.get_current_dir();
for segment in segments {
match segment {
PathSegment::Root => {
self.pwd.borrow_mut().clear();
continue;
},
PathSegment::This => continue,
PathSegment::Parent => {
if self.pwd.borrow().len() == 0 {
return Err("Cannot go above root directory");
}
self.pwd.borrow_mut().pop();
continue;
},
PathSegment::SubDirectory(name) => {
let dir = match current.borrow().get_dir(name) {
Some(dir) => dir,
None => return Err("Directory does not exist"),
};
self.pwd.borrow_mut().push(dir);
},
}
current = self.get_current_dir();
}
Ok(())
}
// Function to get a directory from a path
// the PWD should be unchanged after this function
/*fn virtual_cd(&self, path: String) -> Result<Box<&mut Directory>, &'static str> {
}*/
fn execute_inner(&mut self, command: String) -> Option<String> {
let mut args: Vec<String> = command.split(" ").map(|s| s.to_string()).collect();
let command = match args.first() {
Some(command) => command,
None => return Some("No command entered".to_string()),
};
let command = match self.available_commands.iter().find(|c| c.name == *command) {
Some(command) => command.clone(),
None => return Some(format!("Command {} not found", command)),
};
args.remove(0);
let result = command.execute(self, args);
match result {
Ok(result) => {
self.last_commands.push(command.name.clone());
result
},
Err(e) => Some(e.to_string()),
}
}
}
#[wasm_bindgen]
impl Console {
pub fn new() -> Console {
let root = Rc::new(RefCell::new(Directory::new("".to_string(), "/".to_string())));
let console = Console {
root,
pwd: RefCell::new(Vec::new()),
output: Vec::new(),
last_commands: Vec::new(),
available_commands: commands(),
};
console
}
pub fn get_output(&self) -> Vec<String> {
self.output.clone()
}
pub fn get_last_commands(&self) -> Vec<String> {
self.last_commands.clone()
}
pub fn execute(&mut self, command: String) -> Option<String> {
self.output.push(format!("$ {}", command));
self.last_commands.push(command.clone());
let result = self.execute_inner(command);
if result.is_some() {
self.output.push(result.clone().unwrap());
}
result
}
}

279
src/console/types.rs Normal file
View File

@ -0,0 +1,279 @@
use std::{cell::RefCell, rc::Rc};
pub trait Content {
fn get_name(&self) -> String;
fn get_path(&self) -> String;
}
pub struct File {
path: String,
name: String,
content: String,
}
impl Content for File {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_path(&self) -> String {
self.path.clone()
}
}
#[allow(unused)]
impl File {
pub fn new(name: String, content: String, path: String) -> File {
File {
name,
content,
path,
}
}
pub fn empty_new(name: String, path: String) -> File {
File {
name,
content: String::new(),
path,
}
}
pub fn read(&self) -> String {
self.content.clone()
}
pub fn write(&mut self, content: String) {
self.content = content;
}
pub fn append(&mut self, content: String) {
self.content.push_str(&content);
}
pub fn rename(&mut self, name: String) {
self.name = name;
}
}
pub struct Directory {
name: String,
files: RefCell<Vec<Rc<RefCell<File>>>>,
subdirs: RefCell<Vec<Rc<RefCell<Directory>>>>,
path: String,
}
impl Content for Directory {
fn get_name(&self) -> String {
self.name.clone()
}
fn get_path(&self) -> String {
self.path.clone()
}
}
#[allow(unused)]
impl Directory {
pub fn new(name: String, path: String) -> Directory {
Directory {
name,
files: RefCell::new(Vec::new()),
subdirs: RefCell::new(Vec::new()),
path,
}
}
pub fn path(&self) -> String {
self.path.clone() + "/" + &self.name
}
pub fn add_file(&mut self, file: File) -> Result<(), &'static str>{
if self.get_dir(file.name.clone()).is_some() {
return Err("File already exists");
}
if file.name.contains('/') || file.name.contains('\\')|| file.name.contains(' ') {
return Err("File name cannot contain spraces, '/' or '\\'");
}
self.files.borrow_mut().push(Rc::new(RefCell::new(file)));
Ok(())
}
pub fn add_dir(&mut self, dir: Directory) -> Result<(), &'static str> {
if self.get_dir(dir.name.clone()).is_some() {
return Err("Directory already exists");
}
if dir.name.contains('/') || dir.name.contains('\\')|| dir.name.contains(' ') {
return Err("Directory name cannot contain spraces, '/' or '\\'");
}
self.subdirs.borrow_mut().push(Rc::new(RefCell::new(dir)));
Ok(())
}
pub fn mkdir(&self, name: String) -> Result<Rc<RefCell<Directory>>, &'static str> {
if self.get_dir(name.clone()).is_some() {
return Err("Directory already exists");
}
if name.contains('/') || name.contains('\\')|| name.contains(' ') {
return Err("Directory name cannot contain spraces, '/' or '\\'");
}
let dir = Rc::new(RefCell::new(Directory::new(name, self.path())));
self.subdirs.borrow_mut().push(dir.clone());
Ok(dir)
}
pub fn get_file(&self, name: String) -> Option<Rc<RefCell<File>>> {
for file in self.files.borrow().iter() {
if file.borrow().get_name() == name {
return Some(Rc::clone(file));
}
}
None
}
pub fn get_files(&self) -> Vec<Rc<RefCell<File>>> {
self.files.borrow().clone()
}
pub fn get_subdirs(&self) -> Vec<Rc<RefCell<Directory>>> {
self.subdirs.borrow().clone()
}
pub fn get_dir(&self, name: String) -> Option<Rc<RefCell<Directory>>> {
for dir in self.subdirs.borrow().iter() {
if dir.borrow().get_name() == name {
return Some(Rc::clone(dir));
}
}
None
}
/*pub fn get_file_mut(&self, name: String) -> Option<Rc<File>> {
for file in self.files.borrow_mut().iter() {
if file.get_name() == name {
return Some(Rc::clone(file));
}
}
None
}
pub fn get_dir_mut(&self, name: String) -> Option<Rc<Directory>> {
for dir in self.subdirs.borrow().iter() {
if dir.get_name() == name {
return Some(Rc::clone(dir));
}
}
None
}*/
pub fn remove_file(&self, name: String) -> Result<(), &'static str> {
let mut index = 0;
for file in self.files.borrow().iter() {
if file.borrow().get_name() == name {
self.files.borrow_mut().remove(index);
return Ok(());
}
index += 1;
}
Err("File not found")
}
pub fn remove_dir(&self, name: String) -> Result<(), &'static str> {
let mut index = 0;
for dir in self.subdirs.borrow().iter() {
if dir.borrow().get_name() == name {
self.subdirs.borrow_mut().remove(index);
return Ok(());
}
index += 1;
}
Err("Directory not found")
}
pub fn rename(&mut self, name: String) {
self.name = name;
}
pub fn touch(&self, name: String) -> Result<Rc<RefCell<File>>, &'static str> {
if self.get_file(name.clone()).is_some() {
return Err("File already exists");
}
if name.contains('/') || name.contains('\\')|| name.contains(' ') {
return Err("File name cannot contain spraces, '/' or '\\'");
}
let file = Rc::new(RefCell::new(File::empty_new(name, self.path.clone())));
self.files.borrow_mut().push(file.clone());
Ok(file)
}
pub fn create_file(&self, name: String, content: String) -> Result<Rc<RefCell<File>>, &'static str> {
if self.get_file(name.clone()).is_some() {
return Err("File already exists");
}
if name.contains('/') || name.contains('\\')|| name.contains(' ') {
return Err("File name cannot contain spraces, '/' or '\\'");
}
let file = Rc::new(RefCell::new(File::new(name, content, self.path())));
self.files.borrow_mut().push(file.clone());
Ok(file)
}
}
impl Drop for Directory {
fn drop(&mut self) {
self.files.borrow_mut().clear();
self.subdirs.borrow_mut().clear();
}
}
pub enum PathSegment {
This,
Parent,
SubDirectory(String),
Root,
}
impl PathSegment {
pub fn parse_path_segment(path: String) -> Vec<PathSegment> {
let mut segments: Vec<PathSegment> = Vec::new();
let mut current: String = String::new();
let mut first = true;
for c in path.chars() {
if c == '/' {
if first {
segments.push(PathSegment::Root);
}
first = false;
if current == "." {
segments.push(PathSegment::This);
} else if current == ".." {
segments.push(PathSegment::Parent);
} else {
segments.push(PathSegment::SubDirectory(current));
}
current = String::new();
} else {
current.push(c);
}
}
if current == "." {
segments.push(PathSegment::This);
} else if current == ".." {
segments.push(PathSegment::Parent);
} else {
segments.push(PathSegment::SubDirectory(current));
}
segments
}
}

1
src/lib.rs Normal file
View File

@ -0,0 +1 @@
mod console;