extern crate console_error_panic_hook; mod types; mod commands; use std::{cell::RefCell, rc::Rc}; use types::*; use commands::commands; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } 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, skip_last_n: Option) -> Result<(), &'static str> { let segments = PathSegment::parse_path_segment(path); let mut current = self.get_current_dir(); let mut index = 0; let break_index = match skip_last_n { Some(n) => segments.len() - n as usize, None => segments.len(), }; for segment in segments { if index >= break_index { break; } else { index += 1; } 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(&mut self, path: String, skip_last_n: Option) -> Result>, &'static str> { let pwd = self.pwd.borrow().clone(); match self.cd(path, skip_last_n) { Ok(_) => (), Err(e) => return Err(e), }; let result = self.get_current_dir(); self.pwd.replace(pwd); Ok(result) } 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 { #[cfg(not(debug_assertions))] console_error_panic_hook::set_once(); 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_pwd(&self) -> String { match self.pwd.borrow().last() { Some(dir) => dir.borrow().get_path(), None => "/".to_string(), } } 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 } }