tealr_doc_gen 0.2.0

A cli tool to create online documentation for apis made with tealr
use std::{
    fs::{create_dir_all, read_to_string},
    path::{Path, PathBuf},
};

use tealr::{
    mlu::{ExportInstances, FromToLua, TealData},
    EnumGenerator, GlobalInstance, NameContainer, RecordGenerator, TypeGenerator, TypeName,
};

use crate::{
    app::Paths,
    doc_gen::{get_type_name, type_should_be_inlined},
};

#[derive(FromToLua, TypeName, Clone)]
struct SideBar {
    link_to: String,
    name: String,
    members: Vec<Members>,
}
#[derive(FromToLua, TypeName, Clone)]
struct Members {
    name: NameContainer,
}

#[derive(tealr::mlu::TealDerive)]
struct TestDocs;
impl TealData for TestDocs {
    fn add_methods<'lua, T: tealr::mlu::TealDataMethods<'lua, Self>>(methods: &mut T) {
        methods.document("```teal_lua");
        methods.document("local a:number = 1");
        methods.document("```");
        methods.document("some documentation for the next method");
        methods.add_method("test", |_, _, ()| Ok(1));
        methods.document_type("some documentation for the entire type");
    }

    fn add_fields<'lua, F: tealr::mlu::TealDataFields<'lua, Self>>(fields: &mut F) {
        fields.document("some docs for the next field");
        fields.add_field_function_get("a", |_, _| Ok(1))
    }
}
struct GlobalInstances {
    side_bar: Vec<SideBar>,
    link_path: PathBuf,
    etlua: String,
    template: String,
    type_members: TypeGenerator,
    type_name: String,
    all_types: Option<Vec<TypeGenerator>>,
    globals: Option<Vec<GlobalInstance>>,
}
impl Default for GlobalInstances {
    fn default() -> Self {
        Self {
            side_bar: Default::default(),
            link_path: Default::default(),
            etlua: Default::default(),
            template: Default::default(),
            type_members: TypeGenerator::Enum(EnumGenerator {
                name: Default::default(),
                variants: Default::default(),
            }),
            type_name: Default::default(),
            all_types: Default::default(),
            globals: Default::default(),
        }
    }
}

impl ExportInstances for GlobalInstances {
    fn add_instances<'lua, T: tealr::mlu::InstanceCollector<'lua>>(
        self,
        instance_collector: &mut T,
    ) -> tealr::mlu::mlua::Result<()> {
        let side_bar = self.side_bar;
        let link_path = self.link_path;
        let etlua = self.etlua;
        let template = self.template;
        let type_members = self.type_members;
        let type_name = self.type_name;
        let all_types = self.all_types;
        let globals = self.globals;

        instance_collector.add_instance("side_bar_types".into(), move |_| Ok(side_bar))?;
        instance_collector.add_instance("etlua".into(), move |lua| {
            lua.load(&etlua).set_name("etlua")?.into_function()
        })?;
        instance_collector.add_instance("template".into(), move |_| Ok(template))?;
        instance_collector.add_instance("type_name".into(), move |_| Ok(type_name))?;
        instance_collector.add_instance("type_members".into(), move |_| Ok(type_members))?;
        instance_collector.add_instance("globals".into(), move |_| Ok(globals))?;
        instance_collector.add_instance("all_types".into(), move |_| Ok(all_types))?;
        instance_collector.add_instance("create_link".into(), move |lua| {
            let link = link_path;
            tealr::mlu::TypedFunction::from_rust(
                move |_, name: String| Ok(link.join(name + ".html").to_string_lossy().to_string()),
                lua,
            )
        })?;
        instance_collector.add_instance("dedupe_by".into(), |lua| {
            tealr::mlu::TypedFunction::from_rust(
                |lua,
                 (a, b): (
                    Vec<tealr::mlu::generics::X>,
                    tealr::mlu::TypedFunction<tealr::mlu::generics::X, tealr::mlu::generics::Y>,
                )| {
                    let table = lua.create_table()?;
                    a.into_iter()
                        .filter_map(|x| match b.call(x.clone()) {
                            Ok(to_store) => match table.contains_key(to_store.clone()) {
                                Ok(true) => None,
                                Ok(false) => match table.set(to_store, true) {
                                    Ok(()) => Some(Ok(x)),
                                    Err(x) => Some(Err(x)),
                                },
                                Err(x) => Some(Err(x)),
                            },
                            Err(x) => Some(Err(x)),
                        })
                        .collect::<Result<Vec<_>, _>>()
                },
                lua,
            )
        })?;
        instance_collector.add_instance("parse_markdown".into(), |lua| {
            tealr::mlu::TypedFunction::from_rust(
                |_, markdown: String| Ok(crate::markdown::parse_markdown(&markdown)),
                lua,
            )
        })?;
        Ok(())
    }
}

pub(crate) fn run_template(paths: Paths) -> Result<(), anyhow::Error> {
    let json = read_to_string(paths.json)?;
    let type_defs: tealr::TypeWalker = serde_json::from_str(&json)?;

    let link_path = Path::new("/").join(&paths.root);

    let sidebar: Vec<SideBar> = type_defs
        .given_types
        .iter()
        .map(|v| {
            let members = match v {
                tealr::TypeGenerator::Record(x) => x
                    .functions
                    .iter()
                    .map(|x| x.name.clone())
                    .chain(x.mut_functions.iter().map(|x| x.name.clone()))
                    .chain(x.fields.iter().map(|x| x.name.clone()))
                    .chain(x.static_fields.iter().map(|x| x.name.clone()))
                    .chain(x.methods.iter().map(|x| x.name.clone()))
                    .chain(x.mut_methods.iter().map(|x| x.name.clone()))
                    .chain(x.meta_function.iter().map(|x| x.name.clone()))
                    .chain(x.meta_function_mut.iter().map(|x| x.name.clone()))
                    .chain(x.meta_method.iter().map(|x| x.name.clone()))
                    .chain(x.meta_method_mut.iter().map(|x| x.name.clone()))
                    .map(|x| Members { name: x })
                    .collect(),
                tealr::TypeGenerator::Enum(x) => x
                    .variants
                    .iter()
                    .map(|v| Members { name: v.clone() })
                    .collect(),
            };
            let (name, link_to) = if type_should_be_inlined(v) {
                (paths.name.to_string(), "index".to_string())
            } else {
                let x = get_type_name(v).to_string();
                (x.clone(), x)
            };
            let link_to = link_to + ".html";
            SideBar {
                link_to: link_path.join(link_to).to_string_lossy().into_owned(),
                name,
                members,
            }
        })
        .collect();
    let write_path = Path::new(&paths.build_dir).join(&paths.root);
    create_dir_all(&write_path)?;
    let mut z = RecordGenerator::new::<RecordGenerator>(true);
    for type_def in type_defs.iter() {
        match type_def {
            TypeGenerator::Record(x) => {
                let x = x.to_owned();
                if x.should_be_inlined {
                    z.documentation.extend(x.documentation);
                    z.fields.extend(x.fields);
                    z.functions.extend(x.functions);
                    z.meta_function.extend(x.meta_function);
                    z.meta_function_mut.extend(x.meta_function_mut);
                    z.meta_method.extend(x.meta_method);
                    z.meta_method_mut.extend(x.meta_method_mut);
                    z.methods.extend(x.methods);
                    z.mut_functions.extend(x.mut_functions);
                    z.mut_methods.extend(x.mut_methods);
                    z.static_fields.extend(x.static_fields);
                    z.type_doc += &x.type_doc;
                    continue;
                }
            }
            TypeGenerator::Enum(_) => (),
        }
        run_and_write(
            sidebar.clone(),
            type_def,
            &write_path,
            None,
            None,
            link_path.clone(),
        )?;
    }
    run_and_write(
        sidebar,
        &TypeGenerator::Record(Box::new(z)),
        &write_path,
        Some(type_defs.global_instances_off),
        Some(type_defs.given_types),
        link_path,
    )?;
    Ok(())
}

fn run_and_write(
    sidebar: Vec<SideBar>,
    type_def: &TypeGenerator,
    write_path: &Path,
    global_instances: Option<Vec<tealr::GlobalInstance>>,
    all_types: Option<Vec<TypeGenerator>>,
    link_path: PathBuf,
) -> Result<(), anyhow::Error> {
    let type_name = if type_should_be_inlined(type_def) {
        "index".into()
    } else {
        get_type_name(type_def)
    };
    let etlua = include_str!("../etlua.lua").to_string();
    let template = include_str!("../base_template.etlua").to_string();
    let template_runner = include_str!("../base_run_template.lua");
    let lua = unsafe { tealr::mlu::mlua::Lua::unsafe_new() };
    let instance_setter = GlobalInstances {
        side_bar: sidebar,
        link_path,
        etlua,
        template,
        type_members: type_def.to_owned(),
        type_name: type_name.to_string(),
        all_types,
        globals: global_instances,
    };
    tealr::mlu::set_global_env(instance_setter, &lua)?;

    let document: tealr::mlu::mlua::String = lua
        .load(template_runner)
        .set_name("template_runner")?
        .call(())?;
    let page_path = write_path.join(format!("{type_name}.html"));
    std::fs::write(page_path, document.as_bytes())?;
    Ok(())
}

//will be used later :)
#[allow(dead_code)]
fn generate_self() -> Result<(), Box<dyn std::error::Error>> {
    let x = tealr::TypeWalker::new()
        .process_type::<tealr::EnumGenerator>()
        .process_type::<tealr::ExportedFunction>()
        .process_type::<tealr::Field>()
        .process_type::<tealr::GlobalInstance>()
        .process_type::<tealr::KindOfType>()
        .process_type::<tealr::NamePart>()
        .process_type::<tealr::RecordGenerator>()
        .process_type::<tealr::TealType>()
        .process_type::<tealr::TypeGenerator>()
        .process_type::<tealr::TypeWalker>()
        .process_type::<Members>()
        .process_type::<SideBar>()
        .process_type::<TestDocs>()
        .process_type_inline::<TestDocs>()
        .document_global_instance::<GlobalInstances>()?;
    std::fs::write("./self.json", serde_json::to_string_pretty(&x)?)?;
    std::fs::write("./self.d.tl", x.generate_global("tealr_doc_gen")?)?;
    Ok(())
}