script 0.5.0

barebones http scripting
use futures::future::join_all;
use pest::iterators::Pair;
use pest::Parser;
use pest_derive::Parser;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;

#[derive(Parser)]
#[grammar = "routes/grammar.peg"]
struct RouteParser;

fn extract_cfg(pair: Pair<Rule>) -> HashMap<String, String> {
    let mut cfg = HashMap::new();
    for entry in pair.into_inner().flat_map(|p| p.into_inner()) {
        if let (Some(key), Some(value)) = (entry.clone().into_inner().next(), entry.into_inner().nth(1)) {
            cfg.insert(key.as_str().to_string(), value.as_str().trim_matches('"').to_string());
        }
    }
    cfg
}

fn extract_block_content(block: &str) -> String {
    let lines: Vec<&str> = block.lines().collect();
    if lines.len() < 3 {
        return block.trim().trim_matches('{').trim_matches('}').trim().to_string();
    }

    let indent = lines[1].chars().take_while(|c| c.is_whitespace()).count();
    lines[1..lines.len() - 1]
        .iter()
        .map(|line| if line.len() > indent { &line[indent..] } else { line.trim() })
        .collect::<Vec<&str>>()
        .join("\n")
}

fn extract_route_info(pair: Pair<Rule>, input: &str) -> super::Route {
    let mut route_info = super::Route::default();

    for inner_pair in pair.into_inner() {
        match inner_pair.as_rule() {
            Rule::route_attr => {
                for attr_pair in inner_pair.into_inner() {
                    match attr_pair.as_rule() {
                        Rule::string_literal => {
                            route_info.route = attr_pair.as_str().trim_matches('"').into();
                        }
                        Rule::cfg_block => {
                            route_info.cfg = Some(extract_cfg(attr_pair));
                        }
                        _ => {}
                    }
                }
            }
            Rule::function_def => {
                for func_pair in inner_pair.into_inner() {
                    match func_pair.as_rule() {
                        Rule::route_name => {
                            route_info.fn_name = func_pair.as_str().into();

                            if route_info.route.is_empty() {
                                route_info.route = format!("/{}", func_pair.as_str()).into();
                            }
                        }
                        Rule::parameters => {
                            route_info.args = Some(func_pair.into_inner().map(|p| p.as_str().into()).collect());
                        }
                        Rule::block => {
                            route_info.fn_body = extract_block_content(func_pair.as_str()).into();

                            let start_pos = func_pair.as_span().start();
                            let end_pos = func_pair.as_span().end();
                            let file_lines: Vec<&str> = input.lines().collect();

                            route_info.start_pos = file_lines.iter().take_while(|line| input.find(line.to_owned()).unwrap() < start_pos).count() - 1;
                            route_info.end_pos = file_lines.iter().take_while(|line| input.find(line.to_owned()).unwrap() <= end_pos).count() - 1;
                        }
                        _ => {}
                    }
                }
            }
            Rule::block => {
                route_info.fn_body = extract_block_content(inner_pair.as_str()).into();

                let start_pos = inner_pair.as_span().start();
                let end_pos = inner_pair.as_span().end();
                let file_lines: Vec<&str> = input.lines().collect();

                route_info.start_pos = file_lines.iter().take_while(|line| input.find(line.to_owned()).unwrap() < start_pos).count() - 1;
                route_info.end_pos = file_lines.iter().take_while(|line| input.find(line.to_owned()).unwrap() <= end_pos).count() - 1;
            }
            _ => {}
        }
    }

    route_info
}

fn process_pair<'i>(pair: Pair<'i, Rule>, input: &'i str) -> Pin<Box<dyn Future<Output = Vec<(String, super::Route)>> + 'i>> {
    Box::pin(async move {
        let mut index: Vec<(String, super::Route)> = Vec::new();

        match pair.as_rule() {
            Rule::route_definition => index.push(extract_route_info(pair, input).save(super::RtKind::Normal).await),
            Rule::not_found => index.push(extract_route_info(pair, input).save(super::RtKind::NotFound).await),
            Rule::wildcard => index.push(extract_route_info(pair, input).save(super::RtKind::Wildcard).await),
            _ => {
                for inner_pair in pair.into_inner() {
                    let mut inner_index = process_pair(inner_pair, input).await;
                    index.append(&mut inner_index);
                }
            }
        }

        index
    })
}

pub async fn try_parse(input: &str) {
    match RouteParser::parse(Rule::grammar, input) {
        Ok(pairs) => {
            let futures: Vec<_> = pairs.into_iter().map(|pair| process_pair(pair, input)).collect();
            let results = join_all(futures).await;
            let index: Vec<(String, super::Route)> = results.into_iter().flatten().collect();

            super::Route::update_index(index).await;

            match super::Route::cleanup().await {
                Ok(_) => tracing::trace!("Cache cleanup completed successfully"),
                Err(err) => tracing::error!(err = err.to_string(), "Error during cache cleanup"),
            };
        }
        Err(e) => println!("Error: {}", e),
    }
}