commit a5e3039f805976c9cbcb77fb13fdd646264a7c72 Author: Daniel Kluge Date: Fri Dec 22 11:44:46 2023 +0100 First few commands diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2919eee --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6ac004a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "wasm-terminal" +version = "0.1.0" +edition = "2021" +authors = ["Daniel Kluge "] +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" diff --git a/src/console/commands.rs b/src/console/commands.rs new file mode 100644 index 0000000..e24cadf --- /dev/null +++ b/src/console/commands.rs @@ -0,0 +1,239 @@ +use std::rc::Rc; + +use super::{Command, types::Content}; + +pub fn commands() -> Vec> { + let mut available_commands: Vec> = 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 +} \ No newline at end of file diff --git a/src/console/mod.rs b/src/console/mod.rs new file mode 100644 index 0000000..9197b24 --- /dev/null +++ b/src/console/mod.rs @@ -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>, + function: fn(&mut Console, Vec, Vec>) -> Result, &'static str>, +} + +impl Command { + pub fn new(name: String, description: String, flags: Vec>, function: fn(&mut Console, Vec, Vec>) -> Result, &'static str>) -> Command { + Command { + name, + description, + flags, + function, + } + } + + fn parse_flags(&self, args: Vec) -> (Vec>, Vec) { + let mut flags: Vec> = 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) -> Result, &'static str> { + let (flags, args) = self.parse_flags(args.clone()); + (self.function)(console, args, flags) + } +} + +#[wasm_bindgen] +pub struct Console { + root: Rc>, + pwd: RefCell>>>, + last_commands: Vec, + available_commands: Vec>, + output: Vec, +} + +impl Console { + fn get_current_dir(&self) -> Rc> { + 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, &'static str> { + + }*/ + + fn execute_inner(&mut self, command: String) -> Option { + let mut args: Vec = 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 { + self.output.clone() + } + + pub fn get_last_commands(&self) -> Vec { + self.last_commands.clone() + } + + pub fn execute(&mut self, command: String) -> Option { + 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 + } +} \ No newline at end of file diff --git a/src/console/types.rs b/src/console/types.rs new file mode 100644 index 0000000..d4a1a15 --- /dev/null +++ b/src/console/types.rs @@ -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>>>, + subdirs: RefCell>>>, + 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>, &'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>> { + 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>> { + self.files.borrow().clone() + } + + pub fn get_subdirs(&self) -> Vec>> { + self.subdirs.borrow().clone() + } + + pub fn get_dir(&self, name: String) -> Option>> { + 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> { + 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> { + 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>, &'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>, &'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 { + let mut segments: Vec = 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 + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b69c7a9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +mod console; \ No newline at end of file