work on annotate

This commit is contained in:
2025-04-14 21:56:00 +04:00
parent ed640ad20d
commit 1a745ff17f
11 changed files with 170 additions and 369 deletions

View File

@@ -1,8 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DbConfig {
pub database_schema: Option<String>,
pub max_connections: u32,
pub acquire_timeout: u64,
}

View File

@@ -1,86 +0,0 @@
pub mod db;
pub mod output;
pub mod sea_orm_config;
pub mod template;
use std::path::PathBuf;
use db::DbConfig;
use output::{OutputCommentConfig, OutputConfig, OutputModelConfig};
use sea_orm_config::{
DateTimeCrate, EntityFormat, Prelude, SeaOrmConfig, SeaOrmEntityConfig,
SeaOrmExtraAttributesConfig, SeaOrmExtraDerivesConfig, SeaOrmSerdeConfig, SeaOrmTableConfig,
SerdeEnable,
};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Config {
pub db: DbConfig,
pub sea_orm: SeaOrmConfig,
pub output: OutputConfig,
pub templates: Option<Mapping>,
pub templates_dir: Option<PathBuf>,
}
impl Default for Config {
fn default() -> Self {
Self {
db: DbConfig {
database_schema: None,
max_connections: 10,
acquire_timeout: 5,
},
sea_orm: SeaOrmConfig {
prelude: Prelude::Enabled,
serde: SeaOrmSerdeConfig {
enable: SerdeEnable::None,
skip_deserializing_primary_key: false,
skip_hidden_column: false,
},
entity: SeaOrmEntityConfig {
format: EntityFormat::Compact,
tables: SeaOrmTableConfig {
include_hidden: false,
skip_seaql_migrations: true,
table_config: None,
},
extra_derives: SeaOrmExtraDerivesConfig {
model: Vec::new(),
eenum: Vec::new(),
},
extra_attributes: SeaOrmExtraAttributesConfig {
model: Vec::new(),
eenum: Vec::new(),
},
date_time_crate: DateTimeCrate::Chrono,
with_copy_enums: false,
},
},
output: OutputConfig {
path: PathBuf::from("./src/"),
models: OutputModelConfig {
comment: OutputCommentConfig {
max_width: None,
table_name: true,
column_name: true,
column_db_type: true,
column_rust_type: true,
column_attributes: true,
column_exclude_attributes: Vec::new(),
enable: true,
column_info: true,
ignore_errors: false,
},
enable: true,
entities: String::from("_entities"),
prelude: true,
path: PathBuf::from("./models"),
},
},
templates: None,
templates_dir: None,
}
}
}

View File

@@ -1,32 +0,0 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct OutputConfig {
pub path: PathBuf,
pub models: OutputModelConfig,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct OutputModelConfig {
pub prelude: bool,
pub enable: bool,
pub path: PathBuf,
pub comment: OutputCommentConfig,
pub entities: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct OutputCommentConfig {
pub enable: bool,
pub max_width: Option<u16>,
pub table_name: bool,
pub column_info: bool,
pub column_name: bool,
pub column_db_type: bool,
pub column_rust_type: bool,
pub column_attributes: bool,
pub column_exclude_attributes: Vec<String>,
pub ignore_errors: bool,
}

View File

@@ -1,233 +0,0 @@
use serde::{Deserialize, Deserializer, Serialize};
use serde_yaml::Value;
use sea_orm_codegen::{
DateTimeCrate as CodegenDateTimeCrate, EntityWriterContext, WithPrelude, WithSerde,
};
use super::Config;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EntityFormat {
Expanded,
Compact,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(untagged)]
pub enum TableConfig {
Specific { specific: Vec<String> },
Exclude { exclude: Vec<String> },
}
#[derive(Debug, Clone)]
pub enum SerdeEnable {
Both,
Serialize,
Deserialize,
None,
}
#[derive(Debug, Clone)]
pub enum Prelude {
Enabled,
Disabled,
AllowUnusedImports,
}
impl<'de> Deserialize<'de> for SerdeEnable {
fn deserialize<D>(deserializer: D) -> Result<SerdeEnable, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
match value {
Value::String(s) if s == "serialize" => Ok(SerdeEnable::Serialize),
Value::String(s) if s == "deserialize" => Ok(SerdeEnable::Deserialize),
Value::Bool(true) => Ok(SerdeEnable::Both),
Value::Bool(false) => Ok(SerdeEnable::None),
_ => Err(serde::de::Error::custom(
"expected 'serialize', 'deserialize', 'true' or 'false'",
)),
}
}
}
impl Serialize for SerdeEnable {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
SerdeEnable::Both => serializer.serialize_bool(true),
SerdeEnable::Serialize => serializer.serialize_str("serialize"),
SerdeEnable::Deserialize => serializer.serialize_str("deserialize"),
SerdeEnable::None => serializer.serialize_bool(false),
}
}
}
impl<'de> Deserialize<'de> for Prelude {
fn deserialize<D>(deserializer: D) -> Result<Prelude, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
match value {
Value::Bool(true) => Ok(Prelude::Enabled),
Value::Bool(false) => Ok(Prelude::Disabled),
Value::String(s) if s == "allow_unused_imports" => Ok(Prelude::AllowUnusedImports),
_ => Err(serde::de::Error::custom(
"expected 'true', 'false', or 'allow_unused_imports'",
)),
}
}
}
impl Serialize for Prelude {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Prelude::Enabled => serializer.serialize_bool(true),
Prelude::Disabled => serializer.serialize_bool(false),
Prelude::AllowUnusedImports => serializer.serialize_str("allow_unused_imports"),
}
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SeaOrmConfig {
pub prelude: Prelude,
pub serde: SeaOrmSerdeConfig,
pub entity: SeaOrmEntityConfig,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SeaOrmSerdeConfig {
pub enable: SerdeEnable,
pub skip_deserializing_primary_key: bool,
pub skip_hidden_column: bool,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SeaOrmEntityConfig {
pub format: EntityFormat,
pub tables: SeaOrmTableConfig,
pub extra_derives: SeaOrmExtraDerivesConfig,
pub extra_attributes: SeaOrmExtraAttributesConfig,
pub date_time_crate: DateTimeCrate,
pub with_copy_enums: bool,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SeaOrmTableConfig {
pub include_hidden: bool,
pub skip_seaql_migrations: bool,
#[serde(flatten)]
pub table_config: Option<TableConfig>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SeaOrmExtraDerivesConfig {
pub model: Vec<String>,
#[serde(rename = "enum")]
pub eenum: Vec<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct SeaOrmExtraAttributesConfig {
pub model: Vec<String>,
#[serde(rename = "enum")]
pub eenum: Vec<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum DateTimeCrate {
Time,
Chrono,
}
impl From<DateTimeCrate> for CodegenDateTimeCrate {
fn from(date_time_crate: DateTimeCrate) -> CodegenDateTimeCrate {
match date_time_crate {
DateTimeCrate::Chrono => CodegenDateTimeCrate::Chrono,
DateTimeCrate::Time => CodegenDateTimeCrate::Time,
}
}
}
impl SeaOrmTableConfig {
pub fn get_filter(&self) -> Box<dyn Fn(&String) -> bool> {
let include_hidden = self.include_hidden;
if let Some(table) = &self.table_config {
match table {
TableConfig::Specific { specific } => {
let specific = specific.clone();
Box::new(move |table: &String| {
(include_hidden || !table.starts_with('_')) && specific.contains(table)
})
}
TableConfig::Exclude { exclude } => {
let exclude = exclude.clone();
Box::new(move |table: &String| {
(include_hidden || !table.starts_with('_')) && !exclude.contains(table)
})
}
}
} else if self.skip_seaql_migrations {
Box::new(move |table: &String| {
(include_hidden || !table.starts_with('_'))
&& !table.starts_with("seaql_migrations")
})
} else {
Box::new(move |table: &String| (include_hidden || !table.starts_with('_')))
}
}
}
impl EntityFormat {
pub fn is_expanded(&self) -> bool {
matches!(self, EntityFormat::Expanded)
}
}
impl From<Prelude> for WithPrelude {
fn from(val: Prelude) -> Self {
match val {
Prelude::Enabled => WithPrelude::All,
Prelude::Disabled => WithPrelude::None,
Prelude::AllowUnusedImports => WithPrelude::AllAllowUnusedImports,
}
}
}
impl From<SerdeEnable> for WithSerde {
fn from(val: SerdeEnable) -> Self {
match val {
SerdeEnable::Both => WithSerde::Both,
SerdeEnable::Serialize => WithSerde::Serialize,
SerdeEnable::Deserialize => WithSerde::Deserialize,
SerdeEnable::None => WithSerde::None,
}
}
}
impl From<Config> for EntityWriterContext {
fn from(val: Config) -> Self {
EntityWriterContext::new(
val.sea_orm.entity.format.is_expanded(),
val.sea_orm.prelude.into(),
val.sea_orm.serde.enable.into(),
val.sea_orm.entity.with_copy_enums,
val.sea_orm.entity.date_time_crate.into(),
val.db.database_schema,
false,
val.sea_orm.serde.skip_deserializing_primary_key,
val.sea_orm.serde.skip_hidden_column,
val.sea_orm.entity.extra_derives.model,
val.sea_orm.entity.extra_attributes.model,
val.sea_orm.entity.extra_derives.eenum,
val.sea_orm.entity.extra_attributes.eenum,
false,
false,
)
}
}

View File

@@ -1 +0,0 @@

View File

@@ -0,0 +1,101 @@
use color_eyre::Result;
use minijinja::Environment;
use crate::generator::modules::{
discovery::{db::DbType, table::Table},
sea_orm::config::DateTimeCrate,
};
use comfy_table::{Cell, ContentArrangement, Table as CTable};
use super::{AnnotateCommentConfig, COMMENTBODY, COMMENTHEAD, COMMENTTAIL};
pub fn generate_comment(
table: &Table,
config: &AnnotateCommentConfig,
environment: &Environment<'static>,
db_type: &DbType,
date_time_crate: &DateTimeCrate,
) -> Result<String> {
let mut column_info_table = CTable::new();
let mut header = Vec::new();
if config.column_name.unwrap() {
header.push("Name");
}
if config.column_db_type.unwrap() {
header.push("DbType");
}
if config.column_rust_type.unwrap() {
header.push("RsType");
}
if config.column_attributes.unwrap() {
header.push("Attrs");
}
column_info_table
.load_preset(" -+=++ + ++")
.set_content_arrangement(ContentArrangement::Dynamic)
.set_header(header);
if let Some(width) = config.max_wdith {
column_info_table.set_width(width);
}
for column in &table.columns {
let mut row = Vec::new();
if config.column_name.unwrap() {
row.push(Cell::new(column.name.clone()))
}
if config.column_db_type.unwrap() {
let column_type = column.get_db_type(db_type);
row.push(Cell::new(column_type));
}
if config.column_rust_type.unwrap() {
let column_type = column.get_rust_type(date_time_crate);
row.push(Cell::new(column_type));
}
if config.column_attributes.unwrap() {
let attrs_string = column.attrs_to_string();
row.push(Cell::new(attrs_string));
}
column_info_table.add_row(row);
}
// column_info_table.to_string()
// let config_part = match parsed_settings {
// Some(settings) => {
// let settings_str = serde_yaml::to_string(&settings)?;
// let settings_str = settings_str
// .lines()
// .map(|line| format!(" {}", line))
// .collect::<Vec<_>>()
// .join("\n");
// format!(
// "{SETTINGSDELIMITER}\n{}\n{SETTINGSDELIMITER}\n\n",
// settings_str
// )
// }
// None => String::new(),
// };
// let table_name = &table.name;
// let table_name_str = if config.table_name {
// format!("Table: {}\n", table_name)
// } else {
// String::new()
// };
// let string = format!("{HEADER}\n{config_part}{table_name_str}\n{column_info_table}");
// let padded_string = Self::pad_comment(&string);
Ok(String::new())
}
pub fn pad_comment(s: &str) -> String {
let parts = s.split('\n').collect::<Vec<_>>();
let mut padded = String::new();
for (index, part) in parts.iter().enumerate() {
let first = index == 0;
let comment = match first {
true => COMMENTHEAD.to_string(),
false => COMMENTBODY.to_string(),
};
let padded_part = format!("{} {}\n", comment, part);
padded.push_str(&padded_part);
}
padded.push_str(COMMENTTAIL);
padded
}

View File

@@ -1,21 +1,55 @@
use super::{discovery::DiscoveredSchema, models::ModelsConfig, Module, ModulesContext};
pub mod comment;
use super::{
discovery::DiscoveredSchema, models::ModelsConfig, sea_orm::SeaOrmConfig, Module,
ModulesContext,
};
use color_eyre::Result;
use minijinja::Environment;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
const HEADER: &str = r#"== Schema Information"#;
const COMMENTHEAD: &str = r#"/*"#;
const COMMENTBODY: &str = r#" *"#;
const COMMENTTAIL: &str = r#"*/"#;
const SETTINGSDELIMITER: &str = r#"```"#;
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
#[derive(Default)]
pub struct AnnotateConfig {
pub enable: bool,
pub comment: AnnotateCommentConfig,
}
impl Default for AnnotateConfig {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AnnotateCommentConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub max_wdith: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub table_name: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_name: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_db_type: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_rust_type: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_attributes: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column_exclude_attributes: Option<Vec<String>>,
}
impl Default for AnnotateCommentConfig {
fn default() -> Self {
Self { enable: false }
Self {
max_wdith: Some(80),
table_name: Some(true),
column_name: Some(true),
column_db_type: Some(true),
column_rust_type: Some(true),
column_attributes: Some(true),
column_exclude_attributes: Some(vec![]),
}
}
}
@@ -39,12 +73,35 @@ impl Module for AnnotateModule {
}
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
let map = ctx.get_anymap();
let file_manager = ctx.get_file_manager();
if let (Some(config), Some(environment), Some(schema)) = (
map.get::<AnnotateConfig>(),
map.get::<Environment<'static>>(),
map.get::<DiscoveredSchema>(),
) {
if let (Some(models_config), Some(sea_orm_config)) =
(map.get::<ModelsConfig>(), map.get::<SeaOrmConfig>())
{
if models_config.enable {
let path = models_config.path.clone().unwrap();
for table in &schema.tables {
let path = path.join(format!("{}.rs", table.name));
let content = file_manager.get(&path);
if content.is_some() {
// generate default comment and insert
let comment = comment::generate_comment(
table,
&config.comment,
environment,
&schema.database_type,
&sea_orm_config.entity.date_time_crate,
);
} else {
// file must already exist therefor read and process it
}
}
}
}
Ok(())
} else {
Ok(())

View File

@@ -174,7 +174,7 @@ impl Column {
}
write_db_type(&self.col_type, db_type)
}
pub fn get_rs_type(&self, date_time_crate: &DateTimeCrate) -> String {
pub fn get_rust_type(&self, date_time_crate: &DateTimeCrate) -> String {
fn write_rs_type(col_type: &ColumnType, date_time_crate: &DateTimeCrate) -> String {
#[allow(unreachable_patterns)]
match col_type {

View File

@@ -83,7 +83,10 @@ impl ModulesContext {
pub fn get_anymap_mut(&mut self) -> &mut AnyCloneMap {
&mut self.anymap
}
pub fn get_file_manager(&mut self) -> &mut FileManager {
pub fn get_file_manager(&self) -> &FileManager {
&self.file_manager
}
pub fn get_file_manager_mut(&mut self) -> &mut FileManager {
&mut self.file_manager
}
}

View File

@@ -169,7 +169,7 @@ impl Module for ModelsModule {
// One or both keys are missing
}
tracing::info!(?files, "Generated model files");
let file_manager = ctx.get_file_manager();
let file_manager = ctx.get_file_manager_mut();
for (output_path, content) in files {
file_manager.insert(&output_path, &content, None)?;
}

View File

@@ -98,7 +98,7 @@ impl Module for SeaOrmModule {
}));
}
let file_manager = ctx.get_file_manager();
let file_manager = ctx.get_file_manager_mut();
for (output_path, content) in outputs {
file_manager.insert(&output_path, &content, None)?;
}