use std::path::PathBuf; use futures::{StreamExt, TryStreamExt}; use tokio::io::AsyncReadExt; use crate::common::{sanitize,Style,PropertiesOverride}; //holy smokes what am I doing lmao //This giant machine is supposed to search for files according to style rules //e.g. ScriptName.server.lua or init.lua //Obviously I got carried away //I could use an enum! //I could use a struct! //I could use a trait! //I could use an error! //I could use a match! //I could use a function! //eventually: #[derive(Debug)] #[allow(dead_code)]//idk why this thinks it's dead code, the errors are printed out in various places pub enum QueryResolveError{ NotFound,//0 results Ambiguous,//>1 results JoinError(tokio::task::JoinError), IO(std::io::Error), } impl std::fmt::Display for QueryResolveError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for QueryResolveError{} struct FileWithName{ file:tokio::fs::File, name:String, } async fn get_file_async(mut path:PathBuf,file_name:impl AsRef)->Result{ let name=file_name.as_ref().to_str().unwrap().to_owned(); path.push(file_name); match tokio::fs::File::open(path).await{ Ok(file)=>Ok(FileWithName{file,name}), Err(e)=>match e.kind(){ std::io::ErrorKind::NotFound=>Err(QueryResolveError::NotFound), _=>Err(QueryResolveError::IO(e)), }, } } type QueryHintResult=Result; trait Query{ async fn resolve(self)->QueryHintResult; } type QueryHandle=tokio::task::JoinHandle>; struct QuerySingle{ script:QueryHandle, } impl QuerySingle{ fn rox(search_path:&PathBuf,search_name:&str)->Self{ Self{ script:tokio::spawn(get_file_async(search_path.clone(),format!("{}.lua",search_name))) } } } impl Query for QuerySingle{ async fn resolve(self)->QueryHintResult{ match self.script.await{ Ok(Ok(file))=>Ok(FileHint{file,hint:ScriptHint::ModuleScript}), Ok(Err(e))=>Err(e), Err(e)=>Err(QueryResolveError::JoinError(e)), } } } struct QueryTriple{ module:QueryHandle, server:QueryHandle, client:QueryHandle, } impl QueryTriple{ fn rox_rojo(search_path:&PathBuf,search_name:&str,search_module:bool)->Self{ //this should be implemented as constructors of Triplet and Quadruplet to fully support Trey's suggestion let module_name=if search_module{ format!("{}.module.lua",search_name) }else{ format!("{}.lua",search_name) }; Self{ module:tokio::spawn(get_file_async(search_path.clone(),module_name)), server:tokio::spawn(get_file_async(search_path.clone(),format!("{}.server.lua",search_name))), client:tokio::spawn(get_file_async(search_path.clone(),format!("{}.client.lua",search_name))), } } fn rojo(search_path:&PathBuf)->Self{ QueryTriple::rox_rojo(search_path,"init",false) } } //these functions can be achieved with macros, but I have not learned that yet fn mega_triple_join(query_triplet:(QueryHintResult,QueryHintResult,QueryHintResult))->QueryHintResult{ match query_triplet{ //unambiguously locate file (Ok(f),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound)) |(Err(QueryResolveError::NotFound),Ok(f),Err(QueryResolveError::NotFound)) |(Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Ok(f))=>Ok(f), //multiple files located (Ok(_),Ok(_),Err(QueryResolveError::NotFound)) |(Ok(_),Err(QueryResolveError::NotFound),Ok(_)) |(Err(QueryResolveError::NotFound),Ok(_),Ok(_)) |(Ok(_),Ok(_),Ok(_))=>Err(QueryResolveError::Ambiguous), //no files located (Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound))=>Err(QueryResolveError::NotFound), //other error (Err(e),_,_) |(_,Err(e),_) |(_,_,Err(e))=>Err(e), } } //LETS GOOOOOOOOOOOOOOOO fn mega_quadruple_join(query_quad:(QueryHintResult,QueryHintResult,QueryHintResult,QueryHintResult))->QueryHintResult{ match query_quad{ //unambiguously locate file (Ok(f),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound)) |(Err(QueryResolveError::NotFound),Ok(f),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound)) |(Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Ok(f),Err(QueryResolveError::NotFound)) |(Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Ok(f))=>Ok(f), //multiple files located (Ok(_),Ok(_),Ok(_),Err(QueryResolveError::NotFound)) |(Ok(_),Ok(_),Err(QueryResolveError::NotFound),Ok(_)) |(Ok(_),Ok(_),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound)) |(Ok(_),Err(QueryResolveError::NotFound),Ok(_),Ok(_)) |(Ok(_),Err(QueryResolveError::NotFound),Ok(_),Err(QueryResolveError::NotFound)) |(Ok(_),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Ok(_)) |(Err(QueryResolveError::NotFound),Ok(_),Ok(_),Ok(_)) |(Err(QueryResolveError::NotFound),Ok(_),Ok(_),Err(QueryResolveError::NotFound)) |(Err(QueryResolveError::NotFound),Ok(_),Err(QueryResolveError::NotFound),Ok(_)) |(Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Ok(_),Ok(_)) |(Ok(_),Ok(_),Ok(_),Ok(_))=>Err(QueryResolveError::Ambiguous), //no files located (Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound))=>Err(QueryResolveError::NotFound), //other error (Err(e),_,_,_) |(_,Err(e),_,_) |(_,_,Err(e),_) |(_,_,_,Err(e))=>Err(e), } } impl Query for QueryTriple{ async fn resolve(self)->QueryHintResult{ let (module,server,client)=tokio::join!(self.module,self.server,self.client); mega_triple_join(( module.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::ModuleScript}), server.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::Script}), client.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::LocalScript}), )) } } struct QueryQuad{ module_implicit:QueryHandle, module_explicit:QueryHandle, server:QueryHandle, client:QueryHandle, } impl QueryQuad{ fn rox_rojo(search_path:&PathBuf,search_name:&str)->Self{ let fill=QueryTriple::rox_rojo(search_path,search_name,true); Self{ module_implicit:QuerySingle::rox(search_path,search_name).script,//Script.lua module_explicit:fill.module,//Script.module.lua server:fill.server, client:fill.client, } } } impl Query for QueryQuad{ async fn resolve(self)->QueryHintResult{ let (module_implicit,module_explicit,server,client)=tokio::join!(self.module_implicit,self.module_explicit,self.server,self.client); mega_quadruple_join(( module_implicit.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::ModuleScript}), module_explicit.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::ModuleScript}), server.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::Script}), client.map_err(|e|QueryResolveError::JoinError(e))?.map(|file|FileHint{file,hint:ScriptHint::LocalScript}), )) } } struct ScriptWithOverrides{ overrides:PropertiesOverride, source:String, } #[derive(Debug)] pub enum ScriptWithOverridesError{ UnimplementedProperty(String), } impl std::fmt::Display for ScriptWithOverridesError{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for ScriptWithOverridesError{} impl ScriptWithOverrides{ fn from_source(mut source:String)->Result{ let mut overrides=PropertiesOverride::default(); let mut count=0; for line in source.lines(){ //only string type properties are supported atm if let Some(captures)=lazy_regex::regex!(r#"^\-\-\!\s*Properties\.([A-z]\w*)\s*\=\s*"(\w+)"$"#) .captures(line){ count+=line.len(); match &captures[1]{ "Name"=>overrides.name=Some(captures[2].to_owned()), "ClassName"=>overrides.class=Some(captures[2].to_owned()), other=>Err(ScriptWithOverridesError::UnimplementedProperty(other.to_owned()))?, } }else{ break; } } Ok(ScriptWithOverrides{overrides,source:source.split_off(count)}) } } enum CompileClass{ Folder, Script(String), LocalScript(String), ModuleScript(String), Model(Vec), } struct CompileNode{ name:String, blacklist:Option, class:CompileClass, } #[derive(Debug)] pub enum CompileNodeError{ IO(std::io::Error), ScriptWithOverrides(ScriptWithOverridesError), InvalidClassOrHint{ class:Option, hint:ScriptHint }, QueryResolveError(QueryResolveError), /// Conversion from OsString to String failed FileName(std::ffi::OsString), ExtensionNotSupportedInStyle{ extension:String, style:Option