Update file/dir structure to be more robust

main
Ashelyn Dawn 2 years ago
parent d363bbfc1a
commit 6acc77e52e

@ -1,4 +1,4 @@
ashe@tilde.club's password:
ashe@tilde.club's password:
Welcome to AsheOS 21.12.1 LTS (HRT/Estrix 6.25.21-wasm)
* Technical: https://tempest.dev

@ -1,15 +1,7 @@
use std::str::FromStr;
use crate::{Directory, State};
static PROJECTS_DIR: &'static str = "~ashe ashe.gay tempest.dev";
static HOME_DIR: &'static str = "about.md contact.md \x1b[0;36mprojects\x1b[0m";
static ABOUT: &'static str = include_str!("../res/about.md");
static CONTACT: &'static str = include_str!("../res/contact.md");
static ASHE_GAY: &'static str = include_str!("../res/ashe.gay.md");
static TEMPEST_DEV: &'static str = include_str!("../res/tempest.dev.md");
static TILDE_ASHE: &'static str = include_str!("../res/~ashe.md");
use crate::State;
use crate::path;
pub fn handle_command(command_string : String, state : &mut State) {
let mut words = command_string.split(' ');
@ -18,96 +10,122 @@ pub fn handle_command(command_string : String, state : &mut State) {
let args = words.collect::<Vec<&str>>();
match command {
"help" => {
state.output.push_back("implemented commands: clear, pwd, cd, ls, cat".to_string())
}
"pwd" => {
state.output.push_back(state.cwd.clone())
}
"clear" => {
state.output.clear();
}
"cd" => {
if args.len() == 0 {
state.current_working_directory = Directory::Home;
if args.len() == 0 || args[0].is_empty() {
state.cwd = "/home/ashe".to_string();
} else {
match state.current_working_directory {
Directory::Home => {
match args[0] {
"~" => (),
"~/" => (),
".." => state.output.push_back(String::from_str("bash: cd: ..: Permission denied").unwrap()),
"../" => state.output.push_back(String::from_str("bash: cd: ..: Permission denied").unwrap()),
"projects" => state.current_working_directory = Directory::Projects,
"projects/" => state.current_working_directory = Directory::Projects,
_ => {
if args[0].chars().next().unwrap() == '/' {
state.output.push_back(format!("bash: cd: {}: Permission denied", args[0]));
} else {
state.output.push_back(format!("bash: cd: {}: No such file or directory", args[0]));
}
}
}
},
Directory::Projects => {
match args[0] {
"~" => state.current_working_directory = Directory::Home,
"~/" => state.current_working_directory = Directory::Home,
".." => state.current_working_directory = Directory::Home,
"../" => state.current_working_directory = Directory::Home,
_ => {
if args[0].chars().next().unwrap() == '/' {
state.output.push_back(format!("bash: cd: {}: Permission denied", args[0]));
} else {
state.output.push_back(format!("bash: cd: {}: No such file or directory", args[0]));
}
}
}
}
let target_path = *args.get(0).unwrap();
let absolute_target_path = path::join(state.cwd.as_str(), target_path);
if absolute_target_path == "/" {
return state.output.push_back(format!("cd: permission denied: {}", args[0]))
}
let target = state.fs_root.get_entry_by_path(absolute_target_path.as_str());
if target.is_err() {
return state.output.push_back(format!("cd: no such file or directory: {}", args[0]))
}
let target = target.unwrap();
if !target.is_dir() {
return state.output.push_back(format!("cd: not a directory: {}", args[0]))
}
if !target.permissions.can_read {
return state.output.push_back(format!("cd: permission denied: {}", args[0]))
}
state.cwd = absolute_target_path.to_string();
}
}
"ls" => match state.current_working_directory {
Directory::Home => match args.len() {
0 => state.output.push_back(String::from_str(HOME_DIR).unwrap()),
1 => match args[0] {
"projects" => state.output.push_back(String::from_str(PROJECTS_DIR).unwrap()),
"projects/" => state.output.push_back(String::from_str(PROJECTS_DIR).unwrap()),
_ => state.output.push_back(format!("bash: ls: '{}': No such file or directory", args[0]))
},
_ => state.output.push_back(format!("bash: ls: too many arguments"))
},
Directory::Projects => match args.len() {
0 => state.output.push_back(String::from_str(PROJECTS_DIR).unwrap()),
1 => match args[0] {
".." => state.output.push_back(String::from_str(HOME_DIR).unwrap()),
"../" => state.output.push_back(String::from_str(HOME_DIR).unwrap()),
_ => state.output.push_back(format!("bash: ls: '{}': No such file or directory", args[0]))
},
_ => state.output.push_back(format!("bash: ls: too many arguments"))
"ls" => {
let target_path;
if args.len() > 0 {
target_path = path::join(state.cwd.as_str(), args.get(0).unwrap());
} else {
target_path = state.cwd.clone()
}
let target_entry = state.fs_root
.get_entry_by_path(target_path.as_str());
if target_entry.is_err() {
console_log!("Error getting path {}, error: {}", target_path, target_entry.err().unwrap().filename);
return state.output.push_back(format!("ls: cannot access '{}': No such file or directory", target_path.as_str()))
}
let target_entry = target_entry.unwrap();
if !target_entry.permissions.can_read {
return state.output.push_back(format!("ls: permission denied: {}", args[0]))
}
if !target_entry.is_dir() {
return state.output.push_back(target_entry.name.to_string())
}
let target = target_entry.as_dir().unwrap();
let entries = target.get_entries();
let output = entries.fold(String::new(), |mut a, b| {
if b.is_dir() {
a.push_str("\x1b[0;36m");
}
a.push_str(b.name);
if b.is_dir() {
a.push_str("\x1b[0m");
}
a.push_str(" ");
a
});
state.output.push_back(output);
}
"cat" => match args.len() {
0 => state.output.push_back(format!("bash: cat: too few arguments")),
1 => match state.current_working_directory {
Directory::Home => match args[0] {
"about.md" => output_file(ABOUT, args[0], state),
"contact.md" => output_file(CONTACT, args[0], state),
"projects/ashe.gay" => output_file(ASHE_GAY, args[0], state),
"projects/tempest.dev" => output_file(TEMPEST_DEV, args[0], state),
"projects/~ashe" => output_file(TILDE_ASHE, args[0], state),
_ => state.output.push_back(format!("bash: cat: {}: No such file or directory", args[0]))
},
Directory::Projects => match args[0] {
"../about.md" => output_file(ABOUT, args[0], state),
"../contact.md" => output_file(CONTACT, args[0], state),
"ashe.gay" => output_file(ASHE_GAY, args[0], state),
"tempest.dev" => output_file(TEMPEST_DEV, args[0], state),
"~ashe" => output_file(TILDE_ASHE, args[0], state),
_ => state.output.push_back(format!("bash: cat: {}: No such file or directory", args[0]))
},
},
_ => state.output.push_back(format!("bash: cat: too many arguments"))
},
0 => state.output.push_back(format!("cat: too few arguments")),
1 => {
let target_path = *args.get(0).unwrap();
let absolute_target_path = path::join(state.cwd.as_str(), target_path);
let target = state.fs_root.get_entry_by_path(absolute_target_path.as_str());
if let Err(err) = target {
return state.output.push_back(format!("cat: no such file or directory: {}", err.filename))
}
let target = target.unwrap();
if !target.permissions.can_read {
return state.output.push_back(format!("cat: {}: Permission denied", target.name))
}
if target.is_dir() {
return state.output.push_back(format!("cat: {}: Is a directory", target.name))
}
let target_file = target.as_file().unwrap();
output_file(target_file.contents, args[0], state)
}
_ => state.output.push_back(format!("cat: too many arguments")),
}
_ => {
state.output.push_back(format!("bash: {}: command not found", command));
}

@ -0,0 +1,177 @@
pub struct Permissions {
pub can_read: bool,
pub can_write: bool,
pub can_exec: bool
}
pub struct DirEntry {
pub name: &'static str,
pub permissions: Permissions,
pub item: DirEntryContents
}
pub enum DirEntryContents {
Directory(Directory),
File(File)
}
pub struct Directory {
entries : Vec<DirEntry>
}
pub struct File {
pub contents: &'static str
}
#[derive(Debug)]
pub struct DirError {
pub filename: String
}
impl Directory {
pub fn get_entry_by_name(&self, name : &str) -> Result<&DirEntry, DirError> {
for entry in self.entries.iter() {
if entry.name == name {
return Ok(entry)
}
}
return Err(DirError {
filename: name.to_string()
})
}
pub fn get_entry_by_path(&self, path : &str) -> Result<&DirEntry, DirError> {
let mut segments = path.split("/").filter(|s| !s.is_empty());
let first_seg = segments.next().expect("Path has no segments!");
let remaining_path = segments.fold(String::new(), |mut a, b| {
a.reserve(b.len() + 1);
a.push_str("/");
a.push_str(b);
a
});
console_log!("In dir, getting {}. first_seg: {}. remaining: {}", path, first_seg, remaining_path);
let entry = self.get_entry_by_name(first_seg)?;
if remaining_path.is_empty() {
console_log!("Found entry! {}", entry.name);
return Ok(entry);
} else if entry.is_dir() {
console_log!("Found dir: {}", entry.name);
return entry.as_dir().unwrap().get_entry_by_path(remaining_path.as_str())
} else {
console_log!("Error accessing: {}", first_seg);
return Err(DirError {
filename: first_seg.to_string()
})
}
}
pub fn get_entries(&self) -> impl Iterator<Item = &DirEntry> {
self.entries.iter()
}
pub fn get_dirs(&self) -> impl Iterator<Item = &DirEntry> {
self.entries.iter().filter(|item| item.is_dir())
}
pub fn get_files(&self) -> impl Iterator<Item = &DirEntry> {
self.entries.iter().filter(|item| item.is_file())
}
}
impl DirEntry {
pub fn create_dir(name: &'static str, perms: bool) -> DirEntry {
DirEntry {
name,
permissions: Permissions {
can_read: perms,
can_write: perms,
can_exec: perms
},
item: DirEntryContents::Directory(Directory {
entries: Vec::new()
})
}
}
pub fn create_file(name: &'static str, perms: bool, contents: &'static str) -> DirEntry {
DirEntry {
name,
permissions: Permissions {
can_read: perms,
can_write: perms,
can_exec: perms
},
item: DirEntryContents::File(File {
contents
})
}
}
pub fn is_file(&self) -> bool {
if let DirEntryContents::File(file) = &self.item {
return true
}
return false
}
pub fn is_dir(&self) -> bool {
!self.is_file()
}
pub fn as_dir(&self) -> Option<&Directory> {
if let DirEntryContents::Directory(dir) = &self.item {
return Some(dir)
}
return None
}
pub fn as_file(&self) -> Option<&File> {
if let DirEntryContents::File(file) = &self.item {
return Some(file)
}
return None
}
pub fn get_entry_by_path(&self, path : &str) -> Result<&DirEntry, DirError> {
console_log!("In entry, getting {}", path);
if path == "/" || path.is_empty() {
return Ok(self)
}
self.as_dir().expect("Cannot get entries of file").get_entry_by_path(path)
}
}
pub fn setup_fs() -> DirEntry {
let mut root = DirEntry::create_dir("root", false);
let mut home = DirEntry::create_dir("home", false);
let mut ashe = DirEntry::create_dir("ashe", true);
let mut projects = DirEntry::create_dir("projects", true);
if let DirEntryContents::Directory(ref mut dir) = projects.item {
dir.entries.push(DirEntry::create_file("~ashe", true, include_str!("../res/~ashe.md")));
dir.entries.push(DirEntry::create_file("ashe.gay", true, include_str!("../res/ashe.gay.md")));
dir.entries.push(DirEntry::create_file("tempest.dev", true, include_str!("../res/tempest.dev.md")));
}
if let DirEntryContents::Directory(ref mut dir) = ashe.item {
dir.entries.push(DirEntry::create_file("about.md", true, include_str!("../res/about.md")));
dir.entries.push(DirEntry::create_file("contact.md", true, include_str!("../res/contact.md")));
dir.entries.push(projects)
}
if let DirEntryContents::Directory(ref mut dir) = home.item {
dir.entries.push(ashe)
}
if let DirEntryContents::Directory(ref mut dir) = root.item {
dir.entries.push(home);
}
return root;
}

@ -31,6 +31,7 @@ pub fn parse_key_event(event : &KeyboardEvent, ignore_ctrl : bool) -> Option<Key
if key.len() != 1 {
None
} else {
event.prevent_default();
Some(Key::Letter(key.chars().next().unwrap()))
}
}
@ -58,7 +59,7 @@ pub fn handle_key_event(state : &mut State, key : Key) -> Option<String> {
let mut tmp = [0; 4];
state.input += letter.encode_utf8(&mut tmp)
}
Key::Ctrl(letter) =>
Key::Ctrl(letter) =>
match letter.as_ref() {
Key::Letter(letter) => {
if *letter == 'c' {
@ -85,7 +86,7 @@ extern "C" {
pub fn print_state(target : &mut Element, state : &State) {
let mut output = state.output.iter().map(|s| (*s).clone()).collect::<Vec<String>>().join("\n");
if state.output.len() > 0 {
output += "\n";
}
@ -95,7 +96,7 @@ pub fn print_state(target : &mut Element, state : &State) {
output += state.prompt.as_str();
output += " ";
}
// TODO: Line overflow
output += state.input.as_str();
@ -108,6 +109,8 @@ pub fn print_state(target : &mut Element, state : &State) {
pub fn build_prompt(state : &State) -> String {
let mut prompt = String::new();
let cwd = state.cwd.replace("/home/ashe", "~");
prompt += "\x1b[0;36m";
prompt += "ashe";
prompt += "\x1b[2;33m";
@ -116,10 +119,7 @@ pub fn build_prompt(state : &State) -> String {
prompt += "\x1b[2;37m";
prompt += ":";
prompt += "\x1b[2;32m";
prompt += match state.current_working_directory {
crate::Directory::Home => "~",
crate::Directory::Projects => "~/projects"
};
prompt += &cwd.as_str();
prompt += "\x1b[2;37m";
prompt += "$";

@ -4,6 +4,8 @@ extern crate console_error_panic_hook;
mod js;
mod io;
mod commands;
mod fs;
mod path;
use std::collections::VecDeque;
use std::panic;
@ -24,22 +26,23 @@ pub enum Directory {
}
pub struct State {
// cwd : &Directory
fs_root: fs::DirEntry,
cwd: String,
input : String,
output : VecDeque<String>,
prompt: String,
max_rows: usize,
current_working_directory : Directory
}
impl State {
pub fn new() -> Self {
State {
fs_root: fs::setup_fs(),
cwd: "/home/ashe".to_string(),
input : String::new(),
output : VecDeque::new(),
prompt: String::new(),
max_rows: 24,
current_working_directory: Directory::Home
}
}
@ -88,7 +91,7 @@ async fn init(document : Document) {
if let Some(command) = command {
handle_command(command, &mut state);
}
state.prompt = io::build_prompt(&state);
io::print_state(&mut render_target, &state);
}) as Box<dyn FnMut(_)>);

@ -0,0 +1,34 @@
use std::str::FromStr;
pub fn join(root : &str, path : &str) -> String {
if path.starts_with("/") {
return String::from_str(path).unwrap()
}
let mut result_segments : Vec<&str> = root.split("/").filter(|s| !s.is_empty()).collect();
let path_segments = path.split("/").filter(|s| !s.is_empty());
for segment in path_segments {
if segment == "." {
continue
}
if segment == ".." {
if result_segments.len() > 0 {
result_segments.pop();
}
continue
}
result_segments.push(segment)
}
let result_path = result_segments.iter().fold(String::new(), |mut a, b| {
a.reserve(b.len() + 1);
a.push_str("/");
a.push_str(b);
a
});
return result_path
}
Loading…
Cancel
Save