1use std::{
2 collections::BTreeMap,
3 fmt::{Display, Formatter},
4};
5
6use config::{Config, ConfigError};
7use heck::{
8 ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
9 ToUpperCamelCase,
10};
11use regex::Regex;
12use serde::Serialize;
13use serde_json::Value;
14use std::fmt::Result as FmtResult;
15use tinytemplate::{format_unescaped, TinyTemplate};
16use tokio::io::AsyncWriteExt;
17
18use crate::mysql::MySqlParse;
19
20#[derive(Clone, PartialEq, Eq)]
21pub struct DataField {
22 pub field_name: String,
23 pub type_name: String,
24 pub is_null: bool,
25 pub is_pk: bool,
26 pub default: Option<String>,
27 pub comment: String,
28}
29
30pub struct DataValue {
31 pub table_name: String,
32 pub columns: Vec<DataField>,
33}
34
35#[derive(Serialize)]
36pub struct RenderField {
37 field_name: String,
38 column_name: String,
39 type_name: String,
40 default: String,
41 is_pk: bool,
42 is_null: bool,
43 comment: String,
44}
45#[derive(Serialize, Clone)]
46pub struct RenderTableInfo {
47 model_name: String,
48 table_name: String,
49 multi_pk: bool,
50}
51#[derive(Serialize)]
52pub struct RenderBody {
53 table: RenderTableInfo,
54 field_data: Vec<RenderField>,
55}
56#[derive(Serialize)]
57pub struct RenderMergeItem {
58 render_data: String,
59 table: RenderTableInfo,
60 field_data: Vec<RenderField>,
61}
62#[derive(Serialize)]
63pub struct RenderMergeBody {
64 items: Vec<RenderMergeItem>,
65}
66
67#[derive(Debug)]
68pub enum ConfigParseError {
69 Config(String),
70 Regex(String),
71 Tpl(String),
72 Io(String),
73}
74impl Display for ConfigParseError {
75 fn fmt(&self, f: &mut Formatter) -> FmtResult {
76 write!(f, "{:?}", self)
77 }
78}
79impl From<ConfigError> for ConfigParseError {
80 fn from(err: ConfigError) -> Self {
81 ConfigParseError::Config(format!("{}", err))
82 }
83}
84impl From<regex::Error> for ConfigParseError {
85 fn from(err: regex::Error) -> Self {
86 ConfigParseError::Regex(format!("{}", err))
87 }
88}
89impl From<tinytemplate::error::Error> for ConfigParseError {
90 fn from(err: tinytemplate::error::Error) -> Self {
91 ConfigParseError::Tpl(format!("{}", err))
92 }
93}
94impl From<std::io::Error> for ConfigParseError {
95 fn from(err: std::io::Error) -> Self {
96 ConfigParseError::Io(format!("{}", err))
97 }
98}
99impl From<sqlx::Error> for ConfigParseError {
100 fn from(err: sqlx::Error) -> Self {
101 ConfigParseError::Io(format!("{}", err))
102 }
103}
104
105#[async_trait::async_trait]
106pub trait TableParseData {
107 type OUT: TableParseData;
108 async fn new(uri: &str) -> Result<Self::OUT, ConfigParseError>;
109 async fn list_tables(&self) -> Result<Vec<String>, ConfigParseError>;
110 async fn parse_table_column(&self, table: &str) -> Result<Vec<DataField>, ConfigParseError>;
111}
112
113pub struct ConfigParse {
114 type_defalut: String,
115 type_map: Vec<(String, Vec<String>)>,
116 default_none: String,
117 default_null: String,
118 type_transform: bool,
119 defalut_map: Vec<(String, String)>,
120 uri: String,
121 table_name_rule: (String, String),
122 model_name_rule: (String, String, String),
123 column_name_rule: (String, String, String),
124 outfile_name_rule: (String, String, String),
125 tables: Vec<String>,
126 outfile_name: String,
127 outfile_merge_tpl: String,
128 outfile_merge: bool,
129 outfile_overwrite: bool,
130 tpl_body: String,
131 table_rule: Option<String>,
132}
133impl ConfigParse {
134 pub async fn run(config_file: &str) -> Result<(), ConfigParseError> {
135 let settings = Config::builder()
136 .add_source(config::File::with_name(config_file))
137 .build()?;
138 let config_parse = ConfigParse::parse(&settings)?;
139 if config_parse.uri.starts_with("mysql") {
140 config_parse
141 .table_runder(MySqlParse::new(config_parse.uri.as_str()).await?)
142 .await?
143 } else {
144 return Err(ConfigParseError::Config(
145 "database type not support".to_string(),
146 ));
147 }
148 Ok(())
149 }
150 fn add_formatter<'t>(&self, mut tpl: TinyTemplate<'t>) -> TinyTemplate<'t> {
151 tpl.add_formatter("rmln", |v, s: &mut String| match v {
152 Value::String(ts) => {
153 use core::fmt::Write;
154 let mut rs = ts.replace('\n', "");
155 rs = rs.replace('\r', "");
156 write!(s, "{}", rs)?;
157 Ok(())
158 }
159 _ => Err(tinytemplate::error::Error::GenericError {
160 msg: "line format only support in string.".to_string(),
161 }),
162 });
163 tpl
164 }
165 async fn table_runder<PT>(&self, db_parse: PT) -> Result<(), ConfigParseError>
166 where
167 PT: TableParseData,
168 {
169 let mut tpl = TinyTemplate::new();
170 tpl.set_default_formatter(&format_unescaped);
171 tpl = self.add_formatter(tpl);
172 tpl.add_template("body", self.tpl_body.as_str())?;
173 if !self.outfile_name.is_empty() {
174 tpl.add_template("path", self.outfile_name.as_str())?;
175 }
176 if !self.outfile_merge_tpl.is_empty() {
177 tpl.add_template("body_merge", self.outfile_merge_tpl.as_str())?;
178 }
179 let mut tables = vec![];
180 match &self.table_rule {
181 Some(table_rule) => {
182 let regex = Regex::new(table_rule)?;
183 for t in db_parse.list_tables().await?.into_iter() {
184 if regex.is_match(t.as_str()) {
185 tables.push(t);
186 }
187 }
188 }
189 None => {
190 tables = self.tables.clone();
191 }
192 }
193 if tables.is_empty() {
194 return Err(ConfigParseError::Config(
195 "No table matching the record was found".to_string(),
196 ));
197 }
198 let mut merge_item = vec![];
199 for table in tables.iter() {
200 let columns = db_parse.parse_table_column(table).await?;
201 let table_pk_num = columns.iter().filter(|e| e.is_pk).count();
202 let table_name =
203 self.replace_name((&self.table_name_rule.0, &self.table_name_rule.1), table);
204 let data = DataValue {
205 table_name,
206 columns,
207 };
208 let model_name = self.parse_name(&self.model_name_rule, table);
209 let mut name = RenderTableInfo {
210 model_name,
211 multi_pk: table_pk_num > 1,
212 table_name: data.table_name.clone(),
213 };
214 let render_body = self.render_data(name.clone(), data).await?;
215 let body_str = tpl.render("body", &render_body)?;
216 if !self.outfile_merge && !self.outfile_name.is_empty() {
217 name.model_name = self.parse_name(&self.outfile_name_rule, table);
218 let res = tpl.render("path", &name)?;
219 self.write_file(Some(res), body_str).await?;
220 } else {
221 merge_item.push(RenderMergeItem {
222 render_data: body_str,
223 table: render_body.table,
224 field_data: render_body.field_data,
225 });
226 }
227 }
228 let outbody = if !self.outfile_merge_tpl.is_empty() {
229 tpl.render("body_merge", &RenderMergeBody { items: merge_item })?
230 } else {
231 merge_item
232 .into_iter()
233 .map(|e| e.render_data)
234 .collect::<Vec<String>>()
235 .join("\n")
236 };
237 if self.outfile_name.is_empty() {
238 self.write_file(None, outbody).await?;
239 } else if self.outfile_merge {
240 let res = tpl.render("path", &Value::Null)?;
241 self.write_file(Some(res), outbody).await?;
242 }
243 Ok(())
244 }
245 async fn write_file(
246 &self,
247 out_put: Option<String>,
248 body: String,
249 ) -> Result<(), ConfigParseError> {
250 match out_put {
251 Some(file_path) => match tokio::fs::File::open(file_path.as_str()).await {
252 Result::Err(e) => match e.kind() {
253 tokio::io::ErrorKind::NotFound => {
254 let mut file = tokio::fs::File::create(file_path.as_str()).await?;
255 file.write_all(body.as_bytes()).await?;
256 }
257 _ => {
258 return Err(ConfigParseError::Io(
259 e.to_string() + " save in path:" + file_path.as_str(),
260 ));
261 }
262 },
263 Result::Ok(_) => {
264 if self.outfile_overwrite {
265 let mut file = tokio::fs::File::create(file_path.as_str()).await.unwrap();
266 file.write_all(body.as_bytes()).await.map_err(|e| {
267 ConfigParseError::Io(
268 e.to_string() + " write in file:" + file_path.as_str(),
269 )
270 })?;
271 file.sync_all().await.map_err(|e| {
272 ConfigParseError::Io(
273 e.to_string() + " sync in file:" + file_path.as_str(),
274 )
275 })?;
276 }
277 }
278 },
279 _ => {
280 println!("{}", body);
281 }
282 }
283 Ok(())
284 }
285 pub fn parse(settings: &Config) -> Result<Self, ConfigParseError> {
286 let db_url = settings.get_string("db_url")?;
287 let tpl_body = settings.get_string("tpl_body")?;
288 let outfile_name = settings.get_string("outfile_name").unwrap_or_default();
289 let outfile_merge = settings.get_bool("outfile_merge").unwrap_or(false);
290 let outfile_merge_tpl = settings.get_string("outfile_merge_tpl").unwrap_or_default();
291 let outfile_overwrite = settings.get_bool("outfile_overwrite").unwrap_or(false);
292 let type_transform = settings.get_bool("type_transform").unwrap_or(true);
293 let type_defalut = settings.get_string("type_default").unwrap_or_default();
294 let model_name_rule = settings.get_string("model_name_rule").unwrap_or_default();
295 let model_name_start_replace = settings
296 .get_string("model_name_start_replace")
297 .unwrap_or_default();
298 let model_name_end_replace = settings
299 .get_string("model_name_end_replace")
300 .unwrap_or_default();
301 let table_name_start_replace = settings
302 .get_string("table_name_start_replace")
303 .unwrap_or_default();
304 let table_name_end_replace = settings
305 .get_string("table_name_end_replace")
306 .unwrap_or_default();
307 let column_name_rule = settings.get_string("column_name_rule").unwrap_or_default();
308 let column_name_start_replace = settings
309 .get_string("column_name_start_replace")
310 .unwrap_or_default();
311 let column_name_end_replace = settings
312 .get_string("column_name_end_replace")
313 .unwrap_or_default();
314 let outfile_name_rule = settings.get_string("outfile_name_rule").unwrap_or_default();
315 let outfile_name_start_replace = settings
316 .get_string("outfile_name_start_replace")
317 .unwrap_or_default();
318 let outfile_name_end_replace = settings
319 .get_string("outfile_name_end_replace")
320 .unwrap_or_default();
321
322 let mut type_map = vec![];
323 if let Result::Ok(map_arr) = settings.get_table("type_map") {
324 for (_, map_list) in map_arr
325 .into_iter()
326 .map(|(i, c)| (-i.parse::<i32>().unwrap_or(0), c))
327 .collect::<BTreeMap<_, _>>()
328 {
329 for (out_type, val) in map_list.into_table()?.into_iter() {
330 let mut tmap = vec![];
331 for aval in val.into_array()? {
332 tmap.push(aval.into_string()?);
333 }
334 type_map.push((out_type, tmap));
335 }
336 }
337 }
338
339 let default_none = settings.get_string("default_none").unwrap_or_default();
340 let default_null = settings.get_string("default_null").unwrap_or_default();
341
342 let mut defalut_map = vec![];
343 if let Result::Ok(set_defalut_map) = settings.get_table("default_map") {
344 for (_, map_list) in set_defalut_map
345 .into_iter()
346 .map(|(i, c)| (-i.parse::<i32>().unwrap_or(0), c))
347 .collect::<BTreeMap<_, _>>()
348 {
349 for (key, val) in map_list.into_table()?.into_iter() {
350 let out_type = val.into_string()?;
351 defalut_map.push((key, out_type));
352 }
353 }
354 }
355 let mut table_rule = None;
356 let mut tables = vec![];
357 if let Result::Ok(table) = settings.get_string("tables") {
358 table_rule = Some(table);
359 } else {
360 let table = settings.get_array("tables")?;
361 for map in table.into_iter() {
362 tables.push(map.into_string()?);
363 }
364 }
365 let config_parse = ConfigParse {
366 outfile_name,
367 outfile_merge,
368 outfile_merge_tpl,
369 outfile_overwrite,
370 tpl_body,
371 type_defalut,
372 type_map,
373 default_none,
374 type_transform,
375 default_null,
376 defalut_map,
377 table_name_rule: (table_name_start_replace, table_name_end_replace),
378 model_name_rule: (
379 model_name_rule,
380 model_name_start_replace,
381 model_name_end_replace,
382 ),
383 column_name_rule: (
384 column_name_rule,
385 column_name_start_replace,
386 column_name_end_replace,
387 ),
388 outfile_name_rule: (
389 outfile_name_rule,
390 outfile_name_start_replace,
391 outfile_name_end_replace,
392 ),
393 uri: db_url,
394 table_rule,
395 tables,
396 };
397 Ok(config_parse)
398 }
399 fn parse_column_type(&self, db_field: &str) -> Result<String, ConfigParseError> {
400 for (out_type, out_reg) in self.type_map.iter() {
401 for reg_str in out_reg {
402 let regex = Regex::new(reg_str)?;
403 if regex.is_match(db_field) {
404 return Ok(out_type.to_owned());
405 }
406 }
407 }
408 Ok(self.type_defalut.clone())
409 }
410 fn replace_name(&self, rule: (&String, &String), table_name: &String) -> String {
411 let mut out_name = table_name.to_owned();
412 if !rule.0.is_empty() {
413 if let Some(data) = table_name.strip_prefix(rule.0.as_str()) {
414 out_name = data.to_owned();
415 }
416 }
417 if !rule.1.is_empty() {
418 if let Some(data) = table_name.strip_prefix(rule.1.as_str()) {
419 out_name = data.to_owned();
420 }
421 }
422 out_name
423 }
424 fn parse_name(&self, rule: &(String, String, String), table_name: &String) -> String {
425 let out_name = self.replace_name((&rule.1, &rule.2), table_name);
426 match rule.0.as_str() {
427 "lower" => out_name.to_lowercase(),
428 "snake" => out_name.to_snake_case(),
429 "upper" => out_name.to_uppercase(),
430 "shouty_snake" => out_name.to_shouty_snake_case(),
431 "shouty_kebab" => out_name.to_shouty_kebab_case(),
432 "kebab" => out_name.to_kebab_case(),
433 "upper_camel" => out_name.to_upper_camel_case(),
434 "lower_camel" => out_name.to_lower_camel_case(),
435 _ => out_name,
436 }
437 }
438 pub async fn render_data(
439 &self,
440 name: RenderTableInfo,
441 columndata: DataValue,
442 ) -> Result<RenderBody, ConfigParseError> {
443 let mut field_datas = vec![];
444 for field in columndata.columns {
445 let column_name = self.parse_name(&self.column_name_rule, &field.field_name);
446 let ty = if self.type_transform {
447 self.parse_column_type(field.type_name.as_str())?
448 } else {
449 field.type_name
450 };
451 let def = match field.default {
452 Some(mut ts) => {
453 for (in_reg, out_data) in self.defalut_map.iter() {
454 let reg = Regex::new(in_reg)?;
455 if reg.is_match(ts.as_str()) {
456 ts = reg
457 .replace(ts.as_str(), regex::NoExpand(out_data))
458 .to_string();
459 }
460 }
461 ts
462 }
463 None => {
464 if field.is_null {
465 self.default_null.clone()
466 } else {
467 self.default_none.clone()
468 }
469 }
470 };
471 let field_data = RenderField {
472 field_name: field.field_name,
473 column_name,
474 type_name: ty,
475 is_null: field.is_null,
476 default: def,
477 is_pk: field.is_pk,
478 comment: field.comment,
479 };
480 field_datas.push(field_data);
481 }
482 Ok(RenderBody {
483 table: name,
484 field_data: field_datas,
485 })
486 }
487}