ultdocs 0.1.1

(Experimental) Static site generator for API's, Postman and markdown documents
Documentation
use crate::postman::{Item, Request, Response};
use pulldown_cmark::html;

struct Route {
    method: String,
    path: String,
}

impl Route {
    fn new(method: String, path: String) -> Route {
        Route { method, path }
    }
}

fn remove_braces(text: &String) -> String {
    if text.contains("{") {
        let mut fixed_text: String;
        fixed_text = text.replace("{", "");
        fixed_text = fixed_text.replace("}", "");
        fixed_text
    } else {
        text.to_string()
    }
}

fn folder_endpoints(i: u16, item: &Item, routes: Vec<Route>, description: String) -> String {
    let mut routes_html = format!("<p>{} Endpoints</p>\n", item.name);
    let name = wrap_literal(&item.name);
    if routes.len() > 0 {
        for route in routes {
            let method = route.method;
            let path = wrap_literal(&route.path);
            routes_html.push_str(&format!(
                "<p><span class='{method}'>{method}</span>{path}</p>\n",
            ))
        }
        return format!(
            r#"
            <h{i} id={name}>{name}</h{i}> 
            <div class="grid grid-cols-1 md:grid-cols-2">
                <div class="p-2">
                    {description}
                </div>
                <div class="p-6 pl-0 mx-10 rounded-xl bg-primary-800 border-primary-100 border">
                    {routes_html}
                </div>
            </div>
            "#
        );
    } else {
        return format!(
            r#"
            <h{i} id={name}>{name}</h{i}> 
            <div>
                <div class="p-2">
                    {description}
                </div>
            </div>
            "#
        );
    }
}

fn wrap_literal(text: &String) -> String {
    let mut fixed_text = r#"{`"#.to_string();
    fixed_text.push_str(&text);
    fixed_text.push_str(r#"`}"#);
    fixed_text
}

pub fn folder(folder: &Item, i: u16) -> (String, String) {
    let mut description_html = "".to_string();
    if let Some(description) = &folder.description {
        let parser = pulldown_cmark::Parser::new(description);
        html::push_html(&mut description_html, parser);
    }

    let mut routes: Vec<Route> = Vec::new();
    // This digs into the requests in a folder and generates a route summary.
    // It is a secondary loop to the main recursive function, duplicating loops in some places
    // but It avoids having to copy data to an ordered HashMap so is more efficient.
    if let Some(sub_folder) = &folder.item {
        for item in sub_folder {
            if let Some(request) = &item.request {
                if let Some((_, path_no_base)) = request.url.raw.split_once('/') {
                    let mut path_fixed = "/".to_string();
                    path_fixed.push_str(path_no_base);
                    if let Some((path_no_params, _)) = path_fixed.split_once('?') {
                        let route = Route::new(request.method.clone(), path_no_params.to_string());
                        routes.push(route);
                    } else {
                        let route = Route::new(request.method.clone(), path_fixed);
                        routes.push(route);
                    }
                }
            }
        }
    }

    let folder_markup = folder_endpoints(i, folder, routes, description_html);
    let name = remove_braces(&folder.name);
    let link = format!("<a href='#{name}' class='a{i}'>{name}</a>");

    return (folder_markup, link);
}

pub fn request(item: &Item, request: &Request, i: u16) -> (String, String) {
    let mut description_parsed = String::new();
    if let Some(description) = &request.description {
        let mut html_output = String::new();
        let parser = pulldown_cmark::Parser::new(description);
        html::push_html(&mut html_output, parser);
        description_parsed.push_str(&html_output);
    }

    let url = &request.url.raw;
    let method = &request.method;
    let mut request_data = format!("\n'{i}': {{\npath: `{url}`,\nmethod: '{method}',\n");

    if let Some(body) = &request.body {
        request_data.push_str("body: `");
        request_data.push_str(&body.raw);
        request_data.push_str("`,");
    }
    request_data.push_str("},");
    // Converting Postman style variables with {{}} to svelte store references
    let re = regex::Regex::new(r"\{\{(\w+)\}\}").expect("Unable to parse regex");
    let request_data_fixed = re
        .replace_all(&request_data, |caps: &regex::Captures| {
            format!("${{$envs[$vars.environment].{}}}", &caps[1])
        })
        .to_string();

    let name = &item.name;
    let mut result = format!("\n<Request key={{'{i}'}}  title='{name}' >\n",);

    result.push_str(&description_parsed);
    result.push_str("\n</Request>\n");
    (result, request_data_fixed)
}

pub fn response(responses: &Vec<Response>) -> String {
    let mut responses_markup = String::new();
    for response in responses {
        let body = wrap_literal(&response.body);
        let name = &response.name;
        let response_markup = format!("<h6>{name}</h6>IndexMap<pre><code>{body}</code></pre>");
        responses_markup.push_str(&response_markup);
    }
    responses_markup
}

pub fn requests(requests_json: &String) -> String {
    let mut data = r#"
        import type { RequestsType } from "./globals";
        import type { Readable } from "svelte/store";
        import { derived } from "svelte/store";
        import { vars } from "./store";
        import { envs } from "./envs";

        export const requests: Readable<RequestsType> = derived(
            [vars, envs],
            ([$vars, $envs]) => {
                return {
    "#
    .to_string();

    data.push_str(&requests_json);
    data.push_str(
        r#"		
                }
            }
        );"#,
    );
    data
}