replace handlebars with jinja

This commit is contained in:
2025-04-12 14:06:12 +04:00
parent fe423a199b
commit 68d77a23e4
19 changed files with 144 additions and 200 deletions

139
Cargo.lock generated
View File

@@ -413,7 +413,6 @@ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim",
"syn", "syn",
] ]
@@ -439,37 +438,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]] [[package]]
name = "detect-lang" name = "detect-lang"
version = "0.1.5" version = "0.1.5"
@@ -755,22 +723,6 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "handlebars"
version = "6.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
dependencies = [
"derive_builder",
"log",
"num-order",
"pest",
"pest_derive",
"serde",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.2"
@@ -1170,6 +1122,23 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memo-map"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"
[[package]]
name = "minijinja"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98642a6dfca91122779a307b77cd07a4aa951fbe32232aaf5bad9febc66be754"
dependencies = [
"memo-map",
"self_cell",
"serde",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.4" version = "0.7.4"
@@ -1258,21 +1227,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-modular"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
[[package]]
name = "num-order"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
dependencies = [
"num-modular",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1372,51 +1326,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@@ -1653,11 +1562,11 @@ dependencies = [
"color-eyre", "color-eyre",
"comfy-table", "comfy-table",
"comment-parser", "comment-parser",
"handlebars",
"heck 0.5.0", "heck 0.5.0",
"include_dir", "include_dir",
"indicatif", "indicatif",
"inquire", "inquire",
"minijinja",
"path-clean", "path-clean",
"pathdiff", "pathdiff",
"quote", "quote",
@@ -1734,6 +1643,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "self_cell"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.219"
@@ -2380,12 +2295,6 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.18" version = "0.3.18"

View File

@@ -10,11 +10,11 @@ clap = { version = "4.5.32", features = ["derive", "env"] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
comfy-table = { version = "7.1.4", default-features = false } comfy-table = { version = "7.1.4", default-features = false }
comment-parser = "0.1.0" comment-parser = "0.1.0"
handlebars = "6.3.2"
heck = "0.5.0" heck = "0.5.0"
include_dir = "0.7.4" include_dir = "0.7.4"
indicatif = "0.17.11" indicatif = "0.17.11"
inquire = "0.7.5" inquire = "0.7.5"
minijinja = { version = "2.9.0", features = ["loader"] }
path-clean = "1.0.1" path-clean = "1.0.1"
pathdiff = "0.2.3" pathdiff = "0.2.3"
quote = "1.0.40" quote = "1.0.40"

View File

@@ -3,7 +3,7 @@
processes = { processes = {
frontend = { frontend = {
command = '' command = ''
RUST_LOG=debug,sqlx=warn ${pkgs.cargo-watch}/bin/cargo-watch -i tests/src -x 'run' RUST_LOG=debug,sqlx=warn ${pkgs.cargo-watch}/bin/cargo-watch -i tests/src -x 'run && cat tests/src/models/user.rs'
''; '';
}; };
}; };

View File

@@ -1,7 +1,6 @@
pub mod file; pub mod file;
pub mod modules; pub mod modules;
use color_eyre::Result; use color_eyre::Result;
use handlebars::Handlebars;
use toml_edit::DocumentMut; use toml_edit::DocumentMut;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DatabaseUrl(String); pub struct DatabaseUrl(String);

View File

@@ -1,14 +1,17 @@
use color_eyre::{eyre::ContextCompat, Result}; use color_eyre::{eyre::ContextCompat, Result};
use heck::ToUpperCamelCase; use heck::ToUpperCamelCase;
use sea_schema::sea_query::{ColumnDef, ColumnSpec, ColumnType, IndexCreateStatement}; use sea_schema::sea_query::{ColumnDef, ColumnSpec, ColumnType, IndexCreateStatement};
use serde::{Deserialize, Serialize};
use crate::generator::modules::sea_orm::config::DateTimeCrate; use crate::generator::modules::sea_orm::config::DateTimeCrate;
use super::db::DbType; use super::db::DbType;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
pub struct Column { pub struct Column {
pub name: String, pub name: String,
#[serde(skip_serializing)]
pub col_type: ColumnType, pub col_type: ColumnType,
#[serde(skip_serializing)]
pub attrs: Vec<ColumnSpec>, pub attrs: Vec<ColumnSpec>,
} }

View File

@@ -1,8 +1,9 @@
use super::column::Column; use super::column::Column;
use color_eyre::{eyre::eyre, Result}; use color_eyre::{eyre::eyre, Result};
use sea_schema::sea_query::{self, TableCreateStatement}; use sea_schema::sea_query::{self, TableCreateStatement};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize)]
pub struct Table { pub struct Table {
pub name: String, pub name: String,
pub columns: Vec<Column>, pub columns: Vec<Column>,

View File

@@ -3,17 +3,19 @@ use std::path::PathBuf;
use crate::generator::file::pathbuf_to_rust_path; use crate::generator::file::pathbuf_to_rust_path;
use super::{ use super::{
discovery::DiscoveredSchema, sea_orm::SeaOrmConfig, templates::TemplateConfig, Module, discovery::{table::Table, DiscoveredSchema},
ModulesContext, sea_orm::SeaOrmConfig,
templates::TemplateConfig,
Module, ModulesContext,
}; };
use color_eyre::{ use color_eyre::{
eyre::{eyre, Context, ContextCompat}, eyre::{eyre, Context, ContextCompat},
Result, Result,
}; };
use handlebars::Handlebars;
use heck::ToPascalCase; use heck::ToPascalCase;
use minijinja::Environment;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct ModelsConfig { pub struct ModelsConfig {
pub enable: bool, pub enable: bool,
@@ -33,27 +35,27 @@ impl Default for ModelsConfig {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct ModelTemplateContext { pub struct ModelTemplateContext {
entities_path: String, entities_path: Option<String>,
model_path: String, tables: Option<Vec<Table>>,
model_name: String, prelude_path: Option<String>,
entity_name: String, table_name: Option<String>,
active_model_name: String, config: ModelsConfig,
prelude_path: String,
} }
impl ModelTemplateContext { impl ModelTemplateContext {
pub fn new(entities_path: String, model_name: String, prelude_path: String) -> Self { pub fn new(
let model_path = model_name.clone(); entities_path: Option<String>,
let active_model_name = format!("{}ActiveModel", model_name).to_pascal_case(); prelude_path: Option<String>,
let model_name = format!("{}Model", model_name).to_pascal_case(); tables: Option<Vec<Table>>,
let entity_name = model_path.clone().to_pascal_case(); table_name: Option<String>,
config: ModelsConfig,
) -> Self {
Self { Self {
entities_path, entities_path,
model_path, tables,
model_name,
active_model_name,
prelude_path, prelude_path,
entity_name, table_name,
config,
} }
} }
} }
@@ -103,7 +105,7 @@ impl Module for ModelsModule {
if let (Some(config), Some(templates), Some(sea_orm_config), Some(schema)) = ( if let (Some(config), Some(templates), Some(sea_orm_config), Some(schema)) = (
map.get::<ModelsConfig>(), map.get::<ModelsConfig>(),
map.get::<Handlebars<'static>>(), map.get::<Environment<'static>>(),
map.get::<SeaOrmConfig>(), map.get::<SeaOrmConfig>(),
map.get::<DiscoveredSchema>(), map.get::<DiscoveredSchema>(),
) { ) {
@@ -111,46 +113,58 @@ impl Module for ModelsModule {
tracing::info!(?models_path, "Models path"); tracing::info!(?models_path, "Models path");
let entities_path = sea_orm_config.path.clone().unwrap(); let entities_path = sea_orm_config.path.clone().unwrap();
let mod_path = models_path.join("mod.rs"); let mod_path = models_path.join("mod.rs");
let relative_entities_path = pathdiff::diff_paths(&entities_path, &mod_path)
.context("Failed to calculate relative path")?;
let relative_entities_rust_path = pathbuf_to_rust_path(relative_entities_path);
let context = ModelTemplateContext::new(
Some(relative_entities_rust_path.clone()),
None,
Some(schema.tables.clone()),
None,
config.clone(),
);
if config.prelude { if config.prelude {
files.push((mod_path.clone(), "pub mod prelude;".to_string())); let prelude = templates
.get_template("model_prelude")?
.render(&context)
.context("Failed to render model prelude part")?;
files.push((models_path.join("prelude.rs"), prelude));
} }
let mod_content = templates
.get_template("model_mod")?
.render(&context)
.context("Failed to render model mod")?;
files.push((mod_path.clone(), mod_content));
for table in &schema.tables { for table in &schema.tables {
tracing::debug!(?table, "Generating model for table"); tracing::debug!(?table, "Generating model for table");
let path = models_path.join(format!("{}.rs", table.name)); let path = models_path.join(format!("{}.rs", table.name));
let relative_entities_path = pathdiff::diff_paths(&entities_path, &path)
.context("Failed to calculate relative path")?;
let relative_entities_rust_path = pathbuf_to_rust_path(relative_entities_path);
let context = ModelTemplateContext::new( let context = ModelTemplateContext::new(
relative_entities_rust_path.clone(), Some(relative_entities_rust_path.clone()),
table.name.clone(),
"super::prelude".to_string(),
);
if config.prelude { if config.prelude {
let prelude_part = templates Some("super::prelude".to_string())
.render("model_prelude_part", &context)
.context("Failed to render model prelude part")?;
files.push((models_path.join("prelude.rs"), prelude_part.clone()));
}
files.push((mod_path.clone(), format!("pub mod {};", table.name)));
if path.exists() {
tracing::debug!(?path, "Model file already exists");
continue;
}
if config.prelude {
let content = templates
.render("model_prelude", &context)
.context("Failed to render model prelude")?;
files.push((path.clone(), content.clone()));
} else { } else {
None
},
None,
Some(table.name.clone()),
config.clone(),
);
// files.push((mod_path.clone(), format!("pub mod {};", table.name)));
// if path.exists() {
// tracing::debug!(?path, "Model file already exists");
// continue;
// }
let content = templates let content = templates
.render("model", &context) .get_template("model")?
.render(&context)
.context("Failed to render model")?; .context("Failed to render model")?;
files.push((path.clone(), content.clone())); files.push((path.clone(), content.clone()));
} }
}
} else { } else {
// One or both keys are missing // One or both keys are missing
} }

View File

@@ -1,12 +1,12 @@
use std::{collections::HashMap, path::PathBuf};
use super::{Module, ModulesContext}; use super::{Module, ModulesContext};
use color_eyre::{ use color_eyre::{
eyre::{eyre, ContextCompat}, eyre::{eyre, ContextCompat},
Result, Result,
}; };
use handlebars::Handlebars; use heck::ToPascalCase;
use minijinja::Environment;
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf};
use include_dir::{include_dir, Dir, DirEntry}; use include_dir::{include_dir, Dir, DirEntry};
use tokio::fs; use tokio::fs;
@@ -107,19 +107,21 @@ impl Module for TemplateModule {
} }
} }
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> { async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
let mut registry: Handlebars<'static> = Handlebars::new(); let mut env: Environment<'static> = Environment::new();
registry.set_strict_mode(true); env.add_function("pascalCase", |f: String| f.to_pascal_case());
// registry.set_strict_mode(true);
if let Some(config) = ctx.get_anymap().get::<TemplateConfig>() { if let Some(config) = ctx.get_anymap().get::<TemplateConfig>() {
for (templates, path) in config.to_paths() { for (template, path) in config.to_paths() {
tracing::debug!(?templates, ?path, "Registering template"); tracing::debug!(?template, ?path, "Registering template");
let content = fs::read_to_string(path).await?; let content = fs::read_to_string(path).await?;
registry env.add_template_owned(template, content)?;
.register_template_string(templates.as_str(), content) // registry
.map_err(|_| eyre!("Failed to register template"))?; // .register_template_string(templates.as_str(), content)
// .map_err(|_| eyre!("Failed to register template"))?;
} }
Self::register_default_templates(TEMPLATE_DIR.entries(), &mut registry).await?; Self::register_default_templates(TEMPLATE_DIR.entries(), &mut env).await?;
} }
ctx.get_anymap_mut().insert(registry); ctx.get_anymap_mut().insert(env);
Ok(()) Ok(())
} }
} }
@@ -127,12 +129,12 @@ impl Module for TemplateModule {
impl TemplateModule { impl TemplateModule {
async fn register_default_templates<'a>( async fn register_default_templates<'a>(
entries: &[DirEntry<'a>], entries: &[DirEntry<'a>],
handlebars: &mut Handlebars<'a>, env: &mut Environment<'a>,
) -> Result<()> { ) -> Result<()> {
for entry in entries.iter().filter(|file| { for entry in entries.iter().filter(|file| {
file.path() file.path()
.extension() .extension()
.is_some_and(|f| f.to_str().is_some_and(|f| f == "hbs")) .is_some_and(|f| f.to_str().is_some_and(|f| f == "jinja"))
|| file.as_dir().is_some() || file.as_dir().is_some()
}) { }) {
match entry { match entry {
@@ -144,17 +146,18 @@ impl TemplateModule {
.replace("/", "."); .replace("/", ".");
let content = file let content = file
.contents_utf8() .contents_utf8()
.context(format!("Template {} failed to parse", name))?; .context(format!("Template {} failed to parse", name))?
.to_owned();
tracing::debug!(?name, "Registering template"); tracing::debug!(?name, "Registering template");
if !handlebars.has_template(&name) { if env.get_template(name.as_str()).is_err() {
handlebars.register_template_string(&name, content)?; env.add_template_owned(name, content)?;
} else { } else {
tracing::debug!(?name, "Template already registered, skipping"); tracing::debug!(?name, "Template already registered, skipping");
} }
} }
DirEntry::Dir(dir) => { DirEntry::Dir(dir) => {
Box::pin(Self::register_default_templates(dir.entries(), handlebars)).await?; Box::pin(Self::register_default_templates(dir.entries(), env)).await?;
} }
} }
} }

View File

@@ -4,7 +4,6 @@ mod templates;
use clap::Parser; use clap::Parser;
use color_eyre::{eyre::eyre, Result}; use color_eyre::{eyre::eyre, Result};
use handlebars::Handlebars;
use tokio::{fs, io::AsyncWriteExt, process::Command}; use tokio::{fs, io::AsyncWriteExt, process::Command};
use toml_edit::DocumentMut; use toml_edit::DocumentMut;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

View File

@@ -1,5 +1,4 @@
use color_eyre::eyre::{ContextCompat, Result}; use color_eyre::eyre::{ContextCompat, Result};
use handlebars::Handlebars;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::fs; use tokio::fs;

1
templates/model.jinja Normal file
View File

@@ -0,0 +1 @@
use {{entities_path}}::{{table_name}}::{ActiveModel, Model, Entity};

View File

@@ -0,0 +1,9 @@
{% if config.prelude %}
pub mod prelude;
{% endif%}
{% if entities_path == "super::_entities" %}
pub mod _entities;
{% endif %}
{% for table in tables %}
pub mod {{table.name}};
{% endfor %}

View File

@@ -0,0 +1,4 @@
{% for table in tables %}
pub use {{ entities_path }}::{{ table.name }}::{ActiveModel as {{pascalCase(table.name)}}ActiveModel, Model as {{pascalCase(table.name)}}Model, Entity as {{pascalCase(table.name)}}};
{# pub use {{entities_path}}::{{table.name}}::{ActiveModel as {% call upperCamelCase(table.name) %}ActiveModel, Model as {% call upperCamelCase(table.name) %}Model, Entity as {% call upperCamelCase(table.name) %}}; #}
{% endfor %}

View File

@@ -1 +1,3 @@
{{#each tables}}
pub use {{entities_path}}::{{model_name}}::{ActiveModel as {{active_model_name}}, Model as {{model_name}}, Entity as {{entity_name}}}; pub use {{entities_path}}::{{model_name}}::{ActiveModel as {{active_model_name}}, Model as {{model_name}}, Entity as {{entity_name}}};
{{/each}}

View File

@@ -1 +1,8 @@
pub mod prelude;pub mod user;
pub mod prelude;
pub mod _entities;
pub mod user;

View File

@@ -1 +1,3 @@
pub use super::_entities::UserModel::{ActiveModel as UserActiveModel, Model as UserModel, Entity as User};
pub use super::_entities::user::{ActiveModel as UserActiveModel, Model as UserModel, Entity as User};

View File

@@ -1,9 +1 @@
use super::prelude::*; use super::_entities::user::{ActiveModel, Model, Entity};
use sea_orm::ActiveModelBehavior;
#[async_trait::async_trait]
impl ActiveModelBehavior for UserActiveModel {}
impl UserModel {}
impl UserActiveModel {}