diff --git a/Cargo.lock b/Cargo.lock index 2b21ae974ce2aa6cc99b949f9f9831e129dcd0be..aa57e8470b595a100a6ac0e1d08624ba168ddecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,33 +1949,6 @@ dependencies = [ "digest", ] -[[package]] -name = "peg" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -2433,7 +2406,6 @@ dependencies = [ "macros-rs", "mime", "mongodb", - "peg", "pickledb", "redis", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index d0793b57a00079d63860cc6af942103a8ffa76c8..40ef4cb6e2671cfb5dfe537b7d111e962d3ab65c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ description = "barebones http scripting" strip = true [dependencies] -peg = "0.8.4" home = "0.5.9" mime = "0.3.17" toml = "0.8.19" diff --git a/src/helpers.rs b/src/helpers.rs index 744b6c233ce4cb9d539f605552d223b676dfcc98..68bc6612b58f127d5e46747f90be2440840a67c1 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -18,23 +18,7 @@ pub fn rm_first(s: &str) -> &str { pub fn convert_to_format(input: &str) -> String { let re = Regex::new(r"\.(\w+)").unwrap(); let input = super::replace_chars(input); - format!("_route_{}", re.replace_all(&input.replace("/", "_"), |captures: &Captures| format!("__d{}", rm_first(&captures[0])))) -} - -pub fn route_to_fn(input: &str) -> String { - let input = super::replace_chars(input); - let re = Regex::new(r#"\{([^{}\s]+)\}"#).unwrap(); - let re_dot = Regex::new(r"\.(\w+)").unwrap(); - - let result = re.replace_all(&input, |captures: ®ex::Captures| { - let content = captures.get(1).map_or("", |m| m.as_str()); - format!("_arg_{content}") - }); - - format!( - "_route_fmt_{}", - re_dot.replace_all(&result.replace("/", "_"), |captures: &Captures| format!("__d{}", rm_first(&captures[0]))) - ) + format!("{}", re.replace_all(&input.replace("/", "_"), |captures: &Captures| format!("__d{}", rm_first(&captures[0])))) } pub fn collection_exists(d: &Database, name: &String) -> Result<bool, Box<EvalAltResult>> { diff --git a/src/main.rs b/src/main.rs index 5b4e66afa431007fa55342defec854f2cd4e2e53..5e84f4802476ace020910b1697fd95724d2e3ef6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod structs; use helpers::prelude::*; use modules::prelude::*; +use routes::prelude::*; use structs::{config::*, template::*}; use mime::Mime; @@ -95,6 +96,8 @@ fn match_route(route_template: &str, placeholders: &[&str], url: &str) -> Option } for (route_segment, url_segment) in route_segments.iter().zip(url_segments.iter()) { + println!("template: {route_template}, url: {url}, segment: {route_segment} -> {url_segment} -> {placeholders:?}"); + if let Some(placeholder_value) = match_segment(route_segment, url_segment, placeholders) { if !placeholder_value.is_empty() { matched_placeholders.push(placeholder_value); @@ -133,6 +136,9 @@ pub fn replace_chars(input: &str) -> String { } fn match_segment(route_segment: &str, url_segment: &str, placeholders: &[&str]) -> Option<String> { + println!("route_segment: {route_segment}"); + println!("url_segment: {url_segment}"); + if route_segment.starts_with('{') && route_segment.ends_with('}') { let placeholder = &route_segment[1..route_segment.len() - 1]; if placeholders.contains(&placeholder) { @@ -154,6 +160,8 @@ fn match_segment(route_segment: &str, url_segment: &str, placeholders: &[&str]) } async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { + let mut routes = Routes::new_cached(); + let url = match req.uri().to_string().strip_prefix("/") { Some(url) => url.to_string(), None => req.uri().to_string(), @@ -178,8 +186,6 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { return HttpResponse::Ok().body(""); } - let mut routes: HashMap<String, Vec<String>> = HashMap::new(); - let filename = &config.workers.get(0).unwrap(); let fs_pkg = FilesystemPackage::new(); let url_pkg = UrlPackage::new(); @@ -282,29 +288,84 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { .collect() } - fn extract_route(input: &str, route_name: Option<&str>) -> Vec<routes::Route> { - let re = Regex::new(r"(?m)^\s*#\[route\(([^)]+)\)(?:,\s*cfg\(([^)]+)\))?\]\s*([^\(]+)\([^)]*\)\s*\{([^}]+)\}").unwrap(); + fn extract_function_body(input: &str) -> &str { + let mut brace_count = 1; + let mut end_pos = 0; - re.captures_iter(input) - .filter_map(|captures| { - let cap = captures.unwrap(); + for (i, c) in input.char_indices() { + match c { + '{' => brace_count += 1, + '}' => brace_count -= 1, + _ => {} + } - let route = helpers::rm_first(cap[1].trim().trim_matches('"')).to_string(); - if route_name.map_or(true, |name| route == name) { - let cfg = cap.get(2).map(|m| parse_cfg(m.as_str())); - let fn_name = cap[3].trim().to_string(); - let fn_body = cap[4].trim().to_string(); - let fn_fmt = "".to_string(); + if brace_count == 0 { + end_pos = i + 1; + break; + } + } - Some(routes::Route { route, cfg, fn_name, fn_body, fn_fmt }) - } else { - None - } - }) - .collect() + &input[..end_pos] + } + + fn create_fn(route: &Route) -> String { + let args = match route.args.to_owned() { + Some(args) => match args.len() { + 0 => String::new(), + 1 => args[0].to_string(), + _ => args.join(", "), + }, + None => "".into(), + }; + + format!("fn {}({args}){{{}", route.fn_name, route.fn_body) } - let route_data = extract_route(&contents, None); + fn extract_route(input: &str, route_name: &str, args: Option<Vec<RouteData>>) -> Option<Route> { + let re = Regex::new(r#"(?m)^\s*(?:#\[route\(([^)]+)\)(?:,\s*cfg\([^)]+\))?\]\s*)?([^\(\s]+)\s*\(([^)]*)\)\s*\{"#).unwrap(); + + let input = R_INDEX.as_ref().unwrap().replace_all(&input, "index() {").to_string(); + let input = R_ERR.as_ref().unwrap().replace_all(&input, "error_$1() {").to_string(); + let input = R_WILD.as_ref().unwrap().replace_all(&input, "fn wildcard() {").to_string(); + + re.captures_iter(&input).find_map(|captures| { + let cap = captures.unwrap(); + + let route = if let Some(route_capture) = cap.get(1) { + helpers::rm_first(route_capture.as_str().trim_matches('"')).to_string() + } else { + cap.get(2).unwrap().as_str().to_string() + } + .into(); + + let params = cap.get(3).unwrap().as_str().trim().to_string(); + let cfg = cap.get(1).map(|m| parse_cfg(m.as_str())); + let fn_name = helpers::convert_to_format(cap.get(2).unwrap().as_str().trim()).into(); + + let body_start = cap.get(0).unwrap().end(); + let fn_body = extract_function_body(&input[body_start..]).trim().into(); + + if route_name == route { + Some(Route { + cfg, + fn_name, + fn_body, + route, + args: args.clone(), + }) + } else { + None + } + }) + } + + let original_file = contents.clone(); + + for route in vec!["index", "wildcard", "error_404"] { + if let Some(route) = extract_route(&original_file, route, None) { + routes.get_mut().push(route); + } + } let contents = { let pattern = r#"\{([^{}\s]+)\}"#; @@ -315,31 +376,34 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { let re_combine = Regex::new(pattern_combine).unwrap(); let re_rm_config = Regex::new(pattern_rm_config).unwrap(); - let result = re.replace_all(&contents, |captures: ®ex::Captures| { - let content = captures.get(1).map_or("", |m| m.as_str()); - format!("_arg_{content}") - }); + // let result = re.replace_all(&contents, |captures: ®ex::Captures| { + // let content = captures.get(1).map_or("", |m| m.as_str()); + // format!("_arg_{content}") + // }); - let result = re_rm_config.replace_all(&result, ""); + let result = re_rm_config.replace_all(&contents, ""); let result = result.replace("#[route(\"", "_route").replace("\")]", ""); let new_result_fmt = re_combine .replace_all(&result, |captures: ®ex::Captures| { let path = captures.get(1).map_or("", |m| m.as_str()); - let args = captures.get(3).map_or("", |m| m.as_str()); - - if args != "" { - let r_path = Regex::new(r"(?m)_arg_(\w+)").unwrap(); - let key = r_path.replace_all(&path, |captures: ®ex::Captures| { - let key = captures.get(1).map_or("", |m| m.as_str()); - format!("{{{key}}}") - }); + let args = captures.get(3).map_or(None, |m| Some(m.as_str())); + + let args_vec: Option<Vec<routes::Data>> = match args { + Some(args) => match args { + "" => None, + _ => Some(args.split(",").map(|s| s.to_string().replace(" ", "").into()).collect()), + }, + None => None, + }; + + if let Some(route) = extract_route(&original_file, path, args_vec) { + routes.get_mut().push(route); + } - routes.insert(string!(key), args.split(",").map(|s| s.to_string().replace(" ", "")).collect()); - format!("fmt_{path}({args})") - } else { - routes.insert(string!(path), vec![]); - format!("{path}()") + match args { + Some(args) => format!("fmt_{path}({args})"), + None => format!("{path}()"), } }) .into_owned(); @@ -360,7 +424,10 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { for captures in re_route.captures_iter(&contents) { let path = captures.unwrap().get(1).map_or("", |m| m.as_str()); - routes.insert(string!(path.replace("_", "/")), vec![]); + + if let Some(route) = extract_route(&original_file, &path.replace("_", "/"), None) { + routes.get_mut().push(route); + } } let result = R_DOT @@ -381,6 +448,8 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { ternary!(has_wildcard, R_WILD.as_ref().unwrap().replace_all(&result, "fn _wildcard() {").to_string(), result) }; + std::mem::drop(original_file); + let contents = { let slash = Regex::new(r"\$\((.*?)\)").unwrap(); slash.replace_all(&contents, |caps: ®ex::Captures| format!("${{{}}}", &caps[1])).to_string() @@ -393,10 +462,6 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { ast.set_source(filename.to_string_lossy().to_string()); - if url.as_str() == "" && has_index { - send!(engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, "_route_index", ()).unwrap()); - }; - fn extract_context(contents: String, err: String) -> Vec<(String, String)> { let re = Regex::new(r"line (\d+)").unwrap(); @@ -419,27 +484,75 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { vec![] } - for (route, args) in routes { - let url = url.clone(); - let args: Vec<&str> = args.iter().map(AsRef::as_ref).collect(); + println!("{:#?}", routes.get()); - if url.as_str() == route { - match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, helpers::convert_to_format(&url.to_string()), ()) { - Ok(response) => send!(response), - Err(err) => { - let body = ServerError { - error: err.to_string().replace("\n", "<br>"), - context: extract_context(contents, err.to_string()), - }; + for data in routes.get() { + let args: Vec<&str> = match data.args { + Some(ref args) => args.iter().map(AsRef::as_ref).collect(), + None => vec![], + }; - return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).content_type(ContentType::html()).body(body.render().unwrap()); + let cfg = match data.cfg { + Some(ref cfg) => cfg, + None => &HashMap::new(), + }; + + match match_route(&data.route, &args, url.as_str()) { + Some(inner) => { + let contents = create_fn(data); + + let mut ast = match engine.compile(&contents) { + Ok(ast) => ast, + Err(err) => helpers::error(&engine, &path, err), + }; + + ast.set_source(filename.to_string_lossy().to_string()); + + println!("{contents}"); + + match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, &data.fn_name, inner) { + Ok(response) => send!(response), + Err(err) => { + let body = ServerError { + error: err.to_string().replace("\n", "<br>"), + context: extract_context(contents, err.to_string()), + }; + + return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).content_type(ContentType::html()).body(body.render().unwrap()); + } } } + None => {} } - match match_route(&route, &args, url.as_str()) { - Some(data) => match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, helpers::route_to_fn(&route), data) { + if url.as_str() == "" && has_index { + let contents = create_fn(data); + + let mut ast = match engine.compile(&contents) { + Ok(ast) => ast, + Err(err) => helpers::error(&engine, &path, err), + }; + + ast.set_source(filename.to_string_lossy().to_string()); + + println!("{contents}"); + + send!(engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, "index", ()).unwrap()); + }; + + if url.as_str() == data.route { + let contents = create_fn(data); + + let mut ast = match engine.compile(&contents) { + Ok(ast) => ast, + Err(err) => helpers::error(&engine, &path, err), + }; + + ast.set_source(filename.to_string_lossy().to_string()); + + match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, &data.fn_name, ()) { Ok(response) => send!(response), + Err(err) => { let body = ServerError { error: err.to_string().replace("\n", "<br>"), @@ -448,24 +561,14 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).content_type(ContentType::html()).body(body.render().unwrap()); } - }, - None => {} + } } - } - - for data in route_data { - let name = data.route; - - let cfg = match data.cfg { - Some(cfg) => cfg, - None => HashMap::new(), - }; for (item, val) in cfg { match item.as_str() { "wildcard" => { - if url.splitn(2, '/').next().unwrap_or(&url) == name && parse_bool(&val) { - match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, helpers::convert_to_format(&name), ()) { + if url.splitn(2, '/').next().unwrap_or(&url) == data.route && parse_bool(&val) { + match engine.call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, helpers::convert_to_format(&data.route), ()) { Ok(response) => send!(response), Err(err) => { let body = ServerError { @@ -481,24 +584,33 @@ async fn handler(req: HttpRequest, config: Data<Config>) -> impl Responder { _ => {} } } - } - if has_wildcard || has_error_page { - let (body, content_type, status_code) = engine - .call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, ternary!(has_wildcard, "_wildcard", "_route_error_404"), ()) - .unwrap(); + if has_wildcard || has_error_page { + let contents = create_fn(data); - send!((body, content_type, ternary!(has_wildcard, status_code, StatusCode::NOT_FOUND))) - } else { - let body = Message { - error: "Function Not Found", - code: StatusCode::NOT_FOUND.as_u16(), - message: format!("Have you created the <code>{url}()</code> route?"), - note: "You can add <code>* {}</code> or <code>404 {}</code> routes as well", - }; + let mut ast = match engine.compile(&contents) { + Ok(ast) => ast, + Err(err) => helpers::error(&engine, &path, err), + }; + + ast.set_source(filename.to_string_lossy().to_string()); - send!((body.render().unwrap(), ContentType::html(), StatusCode::NOT_FOUND)) + let (body, content_type, status_code) = engine + .call_fn::<(String, ContentType, StatusCode)>(&mut scope, &ast, ternary!(has_wildcard, "wildcard", "error_404"), ()) + .unwrap(); + + send!((body, content_type, ternary!(has_wildcard, status_code, StatusCode::NOT_FOUND))) + } } + + let body = Message { + error: "Function Not Found", + code: StatusCode::NOT_FOUND.as_u16(), + message: format!("Have you created the <code>{url}()</code> route?"), + note: "You can add <code>* {}</code> or <code>404 {}</code> routes as well", + }; + + send!((body.render().unwrap(), ContentType::html(), StatusCode::NOT_FOUND)) } #[actix_web::main] diff --git a/src/routes.rs b/src/routes.rs index 84ed0c2b0da5b8e5a24776337541c1198f808edd..fed58ca7728ca8206f599b11f0684cec2619ddc2 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,16 +1,35 @@ +use smartstring::{LazyCompact, SmartString}; use std::collections::HashMap; +pub type Data = SmartString<LazyCompact>; + +type Args = Option<Vec<Data>>; type Config = Option<HashMap<String, String>>; +#[derive(Debug, Default)] +pub struct Routes { + storage: Vec<Route>, +} + #[derive(Debug, Default)] pub struct Route { pub cfg: Config, - pub route: String, - pub fn_name: String, - pub fn_body: String, - pub fn_fmt: String, + pub route: Data, + pub args: Args, + pub fn_name: Data, + pub fn_body: Data, +} + +impl Routes { + pub fn new_cached() -> Self { Routes { storage: Vec::new() } } + + pub fn get(&self) -> &Vec<Route> { &self.storage } + + pub fn get_mut(&mut self) -> &mut Vec<Route> { &mut self.storage } } -impl Route { - pub fn new() -> Self { Default::default() } +pub mod prelude { + pub use super::Data as RouteData; + pub use super::Route; + pub use super::Routes; }