teo-generator 0.3.9

SDK generators for Teo
Documentation
use std::env;
use crate::utils::filters;
use std::process::Command;
use askama::Template;
use async_trait::async_trait;
use teo_parser::r#type::Type;
use teo_runtime::config::client::Client;
use teo_runtime::namespace::Namespace;
use teo_runtime::traits::named::Named;
use crate::client::ctx::Ctx;
use crate::client::generator::Generator;
use crate::client::generators::kotlin::lookup;
use crate::outline::outline::{Mode, Outline};
use crate::utils::exts::ClientExt;
use crate::utils::file::FileUtil;
use crate::utils::lookup::Lookup;
use crate::utils::message::green_message;

fn package_name_from_ctx_conf(ctx: &Ctx, package_mode: bool, inferred: String) -> String {
    if package_mode {
        inferred
    } else {
        let mut slice: &str = ctx.conf.dest.as_str();
        for prefix in ["src/main/java", "src\\main\\java", "src/main/kotlin", "src\\main\\kotlin"] {
            if let Some(index) = slice.rfind(prefix) {
                slice = &slice[(index + 1 + prefix.len())..]
            }
        }
        slice.replace("/", ".").replace("\\", ".")
    }
}

fn maybe_any_prefix(t: &Type) -> &'static str {
    let lookup_result = lookup(t).unwrap();
    return if lookup_result.matches("^Any\\??$").count() > 0 {
        "@Serializable(with=AnySerializer::class) "
    } else {
        ""
    }
}

fn maybe_underscore(name: &str) -> String {
    if name.starts_with("_") {
        format!("@SerialName(\"{}\") ", name)
    } else {
        "".to_owned()
    }
}

#[derive(Template)]
#[template(path = "client/kotlin/readme.md.jinja", escape = "none")]
pub(self) struct KotlinReadMeTemplate<'a> {
    pub(self) conf: &'a Client,
}

#[derive(Template)]
#[template(path = "client/kotlin/build.gradle.kts.jinja", escape = "none")]
pub(self) struct KotlinBuildGradleTemplate<'a> {
    pub(self) conf: &'a Client,
}

#[derive(Template)]
#[template(path = "client/kotlin/settings.gradle.kts.jinja", escape = "none")]
pub(self) struct KotlinSettingsGradleTemplate<'a> {
    pub(self) conf: &'a Client,
}

#[derive(Template)]
#[template(path = "client/kotlin/namespace.kt.jinja", escape = "none")]
pub(self) struct KotlinNamespaceTemplate<'a> {
    pub(self) main_namespace: &'a Namespace,
    pub(self) namespace: &'a Namespace,
    pub(self) outline: &'a Outline,
    pub(self) conf: &'a Client,
    pub(self) lookup: &'static dyn Lookup,
    pub(crate) render_namespace: &'static dyn Fn(&Namespace, &Client, &Namespace) -> String,
    pub(self) maybe_any_prefix: &'static dyn Fn(&Type) -> &'static str,
    pub(self) maybe_underscore: &'static dyn Fn(&str) -> String,
}

#[derive(Template)]
#[template(path = "client/kotlin/teo.kt.jinja", escape = "none")]
pub(self) struct KotlinMainTemplate<'a> {
    pub(self) package_name: String,
    pub(self) namespace: &'a Namespace,
    pub(self) outline: &'a Outline,
    pub(self) conf: &'a Client,
    pub(self) lookup: &'static dyn Lookup,
    pub(crate) render_namespace: &'static dyn Fn(&Namespace, &Client, &Namespace) -> String,
}

unsafe impl Send for KotlinMainTemplate<'_> { }
unsafe impl Sync for KotlinMainTemplate<'_> { }
unsafe impl Send for KotlinNamespaceTemplate<'_> { }
unsafe impl Sync for KotlinNamespaceTemplate<'_> { }

pub(crate) fn render_namespace(namespace: &Namespace, conf: &Client, main_namespace: &Namespace) -> String {
    let content = KotlinNamespaceTemplate {
        conf,
        namespace,
        render_namespace: &render_namespace,
        outline: &Outline::new(namespace, Mode::Client, main_namespace, true),
        lookup: &lookup,
        main_namespace,
        maybe_any_prefix: &maybe_any_prefix,
        maybe_underscore: &maybe_underscore,
    }.render().unwrap();
    if namespace.path().is_empty() {
        content
    } else {
        format!("class {} {{\n", namespace.name()) + &indent::indent_by(4, content.as_str()) + "\n}"
    }
}

pub(in crate::client) struct KotlinGenerator { }

impl KotlinGenerator {
    pub fn new() -> Self {
        Self {}
    }
}

#[async_trait]
impl Generator for KotlinGenerator {

    fn module_directory_in_package(&self, conf: &Client) -> String {
        "src/main/kotlin".to_owned()
    }

    async fn generate_module_files(&self, ctx: &Ctx, generator: &FileUtil) -> teo_result::Result<()> {
        generator.clear_root_directory().await?;
        Ok(())
    }

    async fn generate_package_files(&self, ctx: &Ctx, generator: &FileUtil) -> teo_result::Result<()> {
        generator.ensure_root_directory().await?;
        let base = generator.get_base_dir();
        let mut has_project = false;
        for file in ["build.gradle", "build.gradle.kts"] {
            let proj_file = base.join(file);
            if proj_file.exists() { has_project = true; break; }
        }
        if !has_project {
            let saved_cwd = env::current_dir().unwrap();
            env::set_current_dir(base).unwrap();
            green_message("run", format!("`gradle init --type basic --dsl kotlin --project-name {}`", ctx.conf.inferred_package_name_camel_case()));
            let exit_status = Command::new("gradle").arg("init").arg("--type").arg("basic").arg("--dsl").arg("kotlin").arg("--project-name").arg(ctx.conf.inferred_package_name_camel_case()).spawn()?.wait()?;
            if exit_status.success() {
                env::set_current_dir(saved_cwd).unwrap();
                generator.generate_file(".gitignore", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/client/kotlin/gitignore"))).await?;
                generator.generate_file("README.md", KotlinReadMeTemplate { conf: ctx.conf }.render().unwrap()).await?;
                generator.generate_file("build.gradle.kts", KotlinBuildGradleTemplate { conf: ctx.conf }.render().unwrap()).await?;
                generator.generate_file("settings.gradle.kts", KotlinSettingsGradleTemplate { conf: ctx.conf }.render().unwrap()).await?;
            }
        }
        Ok(())
    }

    async fn update_parent_package_files(&self, ctx: &Ctx, generator: &FileUtil) -> teo_result::Result<()> {
        Ok(())
    }

    async fn generate_main(&self, ctx: &Ctx, generator: &FileUtil) -> teo_result::Result<()> {
        let outline = Outline::new(ctx.main_namespace, Mode::Client, ctx.main_namespace, true);
        generator.generate_file(format!("{}.kt", ctx.conf.inferred_package_name_camel_case()), KotlinMainTemplate {
            package_name: package_name_from_ctx_conf(ctx, ctx.conf.package, ctx.conf.inferred_package_name_camel_case()),
            lookup: &lookup::lookup,
            outline: &outline,
            conf: ctx.conf,
            namespace: ctx.main_namespace,
            render_namespace: &render_namespace,
        }.render().unwrap()).await?;
        Ok(())
    }
}