sql_reverse 0.1.21

Generate the multiple programming languages structure based on the MySQL/PostgresSQL table structure
use tokio::fs::create_dir;
use tokio::io::AsyncWriteExt;

const FLAG: &str = "// ***************************************以下是自定义代码区域******************************************";
const FLAG2: &str = r#"
/*
example: [
    {"skip_fields": ["updated_at", "created_at"], "filename": "table_name1"},
    {"contain_fields": ["updated_at", "created_at"], "filename": "table_name2"}
]
*/
// *************************************************************************************************"#;

use crate::error::Result;
use crate::table::Table;
use crate::template::clickhouse::CLICKHOUSE_TEMPLATE;
use crate::template::mysql::MYSQL_TEMPLATE;
use crate::template::postgres::POSTGRES_TEMPLATE;
use crate::template::sqlite::SQLITE_TEMPLATE;
use crate::template::template_type::{TEMPLATE_TYPE, TemplateType};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tera::{Context, Tera};

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FilterFields {
    pub skip_fields: Option<Vec<String>>,
    pub contain_fields: Option<Vec<String>>,
    pub filename: String,
}

async fn filter_fields(table: &Table, params: Vec<FilterFields>) -> Result<Vec<(Table, String)>> {
    let mut list = vec![];
    for field in params.iter() {
        if field.skip_fields.is_some() {
            let data = table
                .skip_fields(field.skip_fields.to_owned().unwrap())
                .await;
            list.push((data, field.filename.to_owned()));
        } else if field.contain_fields.is_some() {
            let data = table
                .contain_fields(field.contain_fields.to_owned().unwrap())
                .await;
            list.push((data, field.filename.to_owned()));
        }
    }
    Ok(list)
}

pub trait Render {
    async fn check_download_tera(template_path: &str, template_name: &str) -> Result<()> {
        let file = format!("{}{}", template_path.replace("*", ""), template_name);
        let _ = tokio::fs::create_dir(template_path.replace("*", "")).await;
        if !tokio::fs::try_exists(&file).await.unwrap_or_default() {
            let mut fs = tokio::fs::File::options()
                .create(true)
                .truncate(true)
                .write(true)
                .open(&file)
                .await?;
            match *TEMPLATE_TYPE.read().unwrap() {
                TemplateType::Mysql => {
                    let data = MYSQL_TEMPLATE.read().unwrap().as_bytes();
                    fs.write_all(data).await?;
                }
                TemplateType::Sqlite => {
                    let data = SQLITE_TEMPLATE.read().unwrap().as_bytes();
                    fs.write_all(data).await?;
                }
                TemplateType::Postgres => {
                    let data = POSTGRES_TEMPLATE.read().unwrap().as_bytes();
                    fs.write_all(data).await?;
                }
                TemplateType::Clickhouse => {
                    let data = CLICKHOUSE_TEMPLATE.read().unwrap().as_bytes();
                    fs.write_all(data).await?;
                }
            }
        }
        Ok(())
    }
    async fn render_rust(
        template_path: &str,
        template_name: &str,
        suffix: &str,
        output_dir: &str,
        tables: &Vec<Table>,
    ) -> Result<()> {
        let _ = create_dir(output_dir).await;
        let tera = Tera::new(template_path)?;
        let mut mods = vec![];
        for table in tables {
            mods.push(format!("pub mod {};\n", table.table_name));
            let (mut struct_str, mut custom, filepath) = Self::render_table(
                &tera,
                table,
                template_name,
                suffix,
                output_dir,
                &table.table_name,
            )
            .await?;
            if !custom.is_empty() {
                let data: Vec<&str> = custom.split("*/").collect();
                let data = data.first().unwrap_or(&"").to_string();
                let data = data.replace("/*", "");
                let data = data.trim();
                let params: Vec<FilterFields> = serde_json::from_str(data).unwrap_or(vec![]);
                let filters = filter_fields(table, params).await?;
                for filter in filters.iter() {
                    mods.push(format!("pub mod {};\n", filter.1));
                    let (mut struct_str, custom, filepath) = Self::render_table(
                        &tera,
                        &filter.0,
                        template_name,
                        suffix,
                        output_dir,
                        &filter.1,
                    )
                    .await?;
                    struct_str = struct_str + "\n" + FLAG + custom.as_str();
                    Self::write_to_file(&filepath, &struct_str).await?;
                }
            }
            if custom.trim() == "" {
                custom = FLAG2.to_owned();
            }
            struct_str = struct_str + "\n" + FLAG + custom.as_str();
            Self::write_to_file(&filepath, &struct_str).await?;
        }

        if suffix == "rs" {
            let mod_path = format!("{}/mod.{}", output_dir, suffix);
            Self::append_to_file(mods, &mod_path).await?;
        }

        Ok(())
    }

    async fn render_table(
        tera: &Tera,
        table: &Table,
        template_name: &str,
        suffix: &str,
        output_dir: &str,
        filename: &str,
    ) -> Result<(String, String, String)> {
        let mut context = Context::new();
        context.insert("table", table);
        let struct_str = tera.render(template_name, &context)?;
        let filepath = format!("{}/{}.{}", output_dir, filename, suffix);
        let content = tokio::fs::read_to_string(&filepath)
            .await
            .unwrap_or_default();
        let vv: Vec<&str> = content.split(FLAG).collect();
        let custom = vv.get(1).unwrap_or(&"").to_string();
        Ok((struct_str, custom, filepath))
    }

    async fn write_to_file(filepath: &str, content: &str) -> Result<()> {
        let filepath = Path::new(&filepath);
        let mut f = tokio::fs::File::options()
            .create(true)
            .truncate(true)
            .write(true)
            .open(filepath)
            .await?;
        f.write_all(content.as_bytes()).await?;
        Ok(())
    }

    async fn append_to_file(mods: Vec<String>, filepath: &str) -> Result<()> {
        if let Ok(mut fs) = tokio::fs::File::options()
            .create_new(true)
            .write(true)
            .open(filepath)
            .await
        {
            fs.write_all(TEMPLATE_TYPE.read().unwrap().to_string().as_bytes())
                .await?;
        }
        let file_content = tokio::fs::read_to_string(filepath)
            .await
            .unwrap_or_default();
        for v in mods.iter() {
            if !file_content.contains(v) {
                let mut file = tokio::fs::File::options()
                    .append(true)
                    .create(true)
                    .open(filepath)
                    .await?;
                file.write_all(v.as_bytes()).await?;
            };
        }
        Ok(())
    }
}