1
0

reformat code

This commit is contained in:
Rokas Puzonas 2023-03-05 13:47:52 +02:00
parent bb52e3e84f
commit 004560ca3d
12 changed files with 686 additions and 449 deletions

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
hard_tabs = true

View File

@ -1,18 +1,18 @@
use std::cell::RefCell;
use std::io::Cursor;
use std::collections::{HashMap, self};
use std::rc::Rc;
use gloo::console::{console_dbg, console};
use gloo::console::{console, console_dbg};
use gloo::file::callbacks::FileReader;
use gloo::file::File;
use gloo::storage::{LocalStorage, Storage};
use std::cell::RefCell;
use std::collections::{self, HashMap};
use std::io::Cursor;
use std::rc::Rc;
use web_sys::{DragEvent, Event, FileList, HtmlInputElement, MouseEvent};
use yew::html::TargetCast;
use yew::{html, Callback, Component, Context, Html};
use crate::generate_sql::{SQLValueGuess, generate_table_guessess, generate_fake_entries};
use crate::magicdraw_parser::{parse_project, SQLTableCollection, SQLTable};
use crate::components::sql_column_info::SQLTableColumnInfo;
use crate::generate_sql::{generate_fake_entries, generate_table_guessess, SQLValueGuess};
use crate::magicdraw_parser::{parse_project, SQLTable, SQLTableCollection};
const COLLECTION_STORE_KEY: &str = "current_collection";
const DEFAULT_ROWS_PER_TABLE: u32 = 20;
@ -37,7 +37,7 @@ pub struct App {
currently_shown_table: usize,
all_good_confirmed: bool,
generated_sql: Option<String>,
rows_per_table: u32
rows_per_table: u32,
}
impl Component for App {
@ -63,7 +63,7 @@ impl Component for App {
all_good_confirmed: true, // TODO: make this false, by default
generated_sql: None,
current_guessess,
rows_per_table: DEFAULT_ROWS_PER_TABLE
rows_per_table: DEFAULT_ROWS_PER_TABLE,
}
}
@ -93,16 +93,13 @@ impl Component for App {
gloo::file::callbacks::read_as_bytes(&file, move |res| {
// TODO: show error message
link.send_message(Msg::Loaded(
file_name,
res.expect("failed to read file"),
))
link.send_message(Msg::Loaded(file_name, res.expect("failed to read file")))
})
};
self.active_readers.insert(file_name, task);
true
},
}
Msg::Noop => false,
Msg::UpdateCurrentProject(collection) => {
if let Some(collection) = collection {
@ -115,38 +112,40 @@ impl Component for App {
let guess = generate_table_guessess(table);
self.current_guessess.push(Rc::new(RefCell::new(guess)));
}
self.current_collection = Some(collection.tables.into_iter().map(Rc::new).collect());
self.current_collection =
Some(collection.tables.into_iter().map(Rc::new).collect());
} else {
LocalStorage::delete(COLLECTION_STORE_KEY);
self.current_collection = None
}
true
},
}
Msg::ShowNextTable => {
if let Some(collection) = &self.current_collection {
self.currently_shown_table = (self.currently_shown_table + 1).min(collection.len()-1);
self.currently_shown_table =
(self.currently_shown_table + 1).min(collection.len() - 1);
return true;
}
false
},
}
Msg::ShowPrevTable => {
if self.currently_shown_table > 0 {
self.currently_shown_table = self.currently_shown_table - 1;
return true;
}
false
},
}
Msg::AllGoodConfirmation => {
self.all_good_confirmed = true;
true
},
}
Msg::UpdateGenarator(column, generator) => {
let mut guessess = self.current_guessess[self.currently_shown_table].borrow_mut();
let entry = guessess.get_mut(&column).unwrap();
*entry = generator;
true
},
}
Msg::GenerateSQL => {
let tables = self.current_collection.as_ref().unwrap();
let guessess = self.current_guessess.iter().map(|v| v.borrow()).collect();
@ -157,7 +156,7 @@ impl Component for App {
self.generated_sql = None
}
true
},
}
Msg::UpdateRowsPerTable(rows_per_table) => {
self.rows_per_table = rows_per_table;
false

View File

@ -1,16 +1,17 @@
use std::{collections::HashMap, str::FromStr};
use yew::{Html, html, Callback, TargetCast, AttrValue};
use web_sys::{Event, HtmlInputElement};
use yew::{html, AttrValue, Callback, Html, TargetCast};
use crate::{generate_sql::{SQLValueGuess, SQLStringValueGuess, SQLBoolValueGuess, SQLTimeValueGuess, SQLIntValueGuess}, magicdraw_parser::{SQLColumn, SQLCheckConstraint}};
use crate::{
generate_sql::{
SQLBoolValueGuess, SQLIntValueGuess, SQLStringValueGuess, SQLTimeValueGuess, SQLValueGuess,
},
magicdraw_parser::{SQLCheckConstraint, SQLColumn},
};
fn show_dropdown_picker(
selected: &str,
options: &[AttrValue],
onchange: Callback<String>
) -> Html {
html!{
fn show_dropdown_picker(selected: &str, options: &[AttrValue], onchange: Callback<String>) -> Html {
html! {
<select onchange={Callback::from(move |e: Event| {
let value = e.target_unchecked_into::<HtmlInputElement>().value();
onchange.emit(value);
@ -27,24 +28,30 @@ fn show_dropdown_picker(
fn show_enum_dropdown<T: PartialEq + Clone + 'static>(
selected: &T,
options: HashMap<AttrValue, T>,
onchange: Callback<T>
) -> Html {
onchange: Callback<T>,
) -> Html {
let keys = options.keys().map(AttrValue::clone).collect::<Vec<_>>();
let guess_str = options.iter().find(|(_, v)| v.eq(&selected)).unwrap().0.clone();
let guess_str = options
.iter()
.find(|(_, v)| v.eq(&selected))
.unwrap()
.0
.clone();
show_dropdown_picker(&guess_str, &keys, onchange.reform(move |value_str: String| {
options.get(value_str.as_str()).unwrap().clone()
}))
show_dropdown_picker(
&guess_str,
&keys,
onchange.reform(move |value_str: String| options.get(value_str.as_str()).unwrap().clone()),
)
}
fn show_range_picker<T: FromStr + ToString + Clone + 'static>(
min: T,
max: T,
default_min: T,
default_max: T,
onchange: Callback<(T, T)>,
) -> Html {
min: T,
max: T,
default_min: T,
default_max: T,
onchange: Callback<(T, T)>,
) -> Html {
let onchange_min = {
let onchange = onchange.clone();
let default_min = default_min.clone();
@ -91,13 +98,13 @@ fn show_range_picker<T: FromStr + ToString + Clone + 'static>(
pub fn generator_picker(
column: &SQLColumn,
value: &SQLValueGuess,
onchange: Callback<SQLValueGuess>
onchange: Callback<SQLValueGuess>,
) -> Html {
// TODO: Refacotr 'time', 'datetime', and 'date'. They are very similar
match value {
SQLValueGuess::Int(guess) => {
if column.primary_key {
return html!("Auto increment")
return html!("Auto increment");
}
let mut min = 0;
@ -108,59 +115,73 @@ pub fn generator_picker(
}
// TODO: Disallow entering floating point numbers
show_range_picker(min, max, 0, 100, onchange.reform(|(min, max)| {
SQLValueGuess::Int(SQLIntValueGuess::Range(min, max))
}))
},
SQLValueGuess::Float(min, max) => {
show_range_picker(*min, *max, 0.0, 100.0, onchange.reform(|(min, max)| {
SQLValueGuess::Float(min, max)
}))
},
show_range_picker(
min,
max,
0,
100,
onchange.reform(|(min, max)| SQLValueGuess::Int(SQLIntValueGuess::Range(min, max))),
)
}
SQLValueGuess::Float(min, max) => show_range_picker(
*min,
*max,
0.0,
100.0,
onchange.reform(|(min, max)| SQLValueGuess::Float(min, max)),
),
SQLValueGuess::Date(guess) => {
let options = HashMap::from([
("Now".into() , SQLTimeValueGuess::Now),
("Now".into(), SQLTimeValueGuess::Now),
("Future".into(), SQLTimeValueGuess::Future),
("Past".into() , SQLTimeValueGuess::Past),
("Past".into(), SQLTimeValueGuess::Past),
]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| {
SQLValueGuess::Date(enum_value)
}))
},
show_enum_dropdown(
guess,
options,
onchange.reform(|enum_value| SQLValueGuess::Date(enum_value)),
)
}
SQLValueGuess::Time(guess) => {
let options = HashMap::from([
("Now".into() , SQLTimeValueGuess::Now),
("Now".into(), SQLTimeValueGuess::Now),
("Future".into(), SQLTimeValueGuess::Future),
("Past".into() , SQLTimeValueGuess::Past),
("Past".into(), SQLTimeValueGuess::Past),
]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| {
SQLValueGuess::Time(enum_value)
}))
},
show_enum_dropdown(
guess,
options,
onchange.reform(|enum_value| SQLValueGuess::Time(enum_value)),
)
}
SQLValueGuess::Datetime(guess) => {
let options = HashMap::from([
("Now".into() , SQLTimeValueGuess::Now),
("Now".into(), SQLTimeValueGuess::Now),
("Future".into(), SQLTimeValueGuess::Future),
("Past".into() , SQLTimeValueGuess::Past),
("Past".into(), SQLTimeValueGuess::Past),
]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| {
SQLValueGuess::Datetime(enum_value)
}))
show_enum_dropdown(
guess,
options,
onchange.reform(|enum_value| SQLValueGuess::Datetime(enum_value)),
)
}
SQLValueGuess::Bool(guess) => {
let options = HashMap::from([
("Random".into(), SQLBoolValueGuess::Random),
("True".into() , SQLBoolValueGuess::True),
("False".into() , SQLBoolValueGuess::False),
("True".into(), SQLBoolValueGuess::True),
("False".into(), SQLBoolValueGuess::False),
]);
show_enum_dropdown(guess, options, onchange.reform(|enum_value| {
SQLValueGuess::Bool(enum_value)
}))
},
show_enum_dropdown(
guess,
options,
onchange.reform(|enum_value| SQLValueGuess::Bool(enum_value)),
)
}
SQLValueGuess::String(max_size, guess) => {
if let Some(constraint) = &column.check_constraint {
if let SQLCheckConstraint::OneOf(_) = constraint {
@ -169,22 +190,24 @@ pub fn generator_picker(
}
let options = HashMap::from([
("Lorem Ipsum".into() , SQLStringValueGuess::LoremIpsum),
("First Name".into() , SQLStringValueGuess::FirstName),
("Last Name".into() , SQLStringValueGuess::LastName),
("Full Name".into() , SQLStringValueGuess::FullName),
("Empty".into() , SQLStringValueGuess::Empty),
("Phone number".into() , SQLStringValueGuess::PhoneNumber),
("City name".into() , SQLStringValueGuess::CityName),
("Address".into() , SQLStringValueGuess::Address),
("Email".into() , SQLStringValueGuess::Email),
("URL".into() , SQLStringValueGuess::URL),
("Lorem Ipsum".into(), SQLStringValueGuess::LoremIpsum),
("First Name".into(), SQLStringValueGuess::FirstName),
("Last Name".into(), SQLStringValueGuess::LastName),
("Full Name".into(), SQLStringValueGuess::FullName),
("Empty".into(), SQLStringValueGuess::Empty),
("Phone number".into(), SQLStringValueGuess::PhoneNumber),
("City name".into(), SQLStringValueGuess::CityName),
("Address".into(), SQLStringValueGuess::Address),
("Email".into(), SQLStringValueGuess::Email),
("URL".into(), SQLStringValueGuess::URL),
]);
let max_size = *max_size;
show_enum_dropdown(guess, options, onchange.reform(move |enum_value| {
SQLValueGuess::String(max_size, enum_value)
}))
},
show_enum_dropdown(
guess,
options,
onchange.reform(move |enum_value| SQLValueGuess::String(max_size, enum_value)),
)
}
}
}

View File

@ -1,2 +1,2 @@
pub mod sql_column_info;
pub mod generator_picker;
pub mod sql_column_info;

View File

@ -1,61 +1,68 @@
use std::{rc::Rc, collections::HashMap, cell::RefCell};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use yew::{Properties, html, function_component, Html, Callback};
use yew::{function_component, html, Callback, Html, Properties};
use crate::{magicdraw_parser::SQLTable, generate_sql::SQLValueGuess, components::generator_picker::generator_picker};
use crate::{
components::generator_picker::generator_picker, generate_sql::SQLValueGuess,
magicdraw_parser::SQLTable,
};
#[derive(Properties, PartialEq)]
pub struct SQLTableColumnInfoProps {
pub table: Rc<SQLTable>,
pub guessess: Rc<RefCell<HashMap<String, SQLValueGuess>>>,
pub onchange: Callback<(String, SQLValueGuess)>
pub onchange: Callback<(String, SQLValueGuess)>,
}
const CHECK_MARK: &str = "✔️";
const CROSS_MARK: &str = "";
fn bool_to_mark(value: bool) -> &'static str {
if value { CHECK_MARK } else { CROSS_MARK }
if value {
CHECK_MARK
} else {
CROSS_MARK
}
}
#[function_component]
pub fn SQLTableColumnInfo(props: &SQLTableColumnInfoProps) -> Html {
let table = &props.table;
let rows = table.columns.iter()
.map(|col| {
let guessess = &props.guessess.borrow();
let generator = guessess.get(&col.name);
let rows = table.columns.iter().map(|col| {
let guessess = &props.guessess.borrow();
let generator = guessess.get(&col.name);
let foreign_key;
if let Some((table_name, prop_name)) = &col.foreign_key {
foreign_key = format!("{} {}", table_name, prop_name);
} else {
foreign_key = CROSS_MARK.into();
}
let name = col.name.clone();
let onchange = props.onchange.reform(move |value: SQLValueGuess| (name.clone(), value));
html! {
<tr>
<td> { &col.name } </td>
<td> { &col.sql_type } </td>
<td> {
if let Some(generator) = generator {
generator_picker(col, generator, onchange)
} else {
html!(CROSS_MARK)
}
} </td>
<td> { bool_to_mark(col.primary_key) } </td>
<td> { bool_to_mark(col.nullable) } </td>
<td> { foreign_key } </td>
</tr>
}
let foreign_key;
if let Some((table_name, prop_name)) = &col.foreign_key {
foreign_key = format!("{} {}", table_name, prop_name);
} else {
foreign_key = CROSS_MARK.into();
}
);
html!{
let name = col.name.clone();
let onchange = props
.onchange
.reform(move |value: SQLValueGuess| (name.clone(), value));
html! {
<tr>
<td> { &col.name } </td>
<td> { &col.sql_type } </td>
<td> {
if let Some(generator) = generator {
generator_picker(col, generator, onchange)
} else {
html!(CROSS_MARK)
}
} </td>
<td> { bool_to_mark(col.primary_key) } </td>
<td> { bool_to_mark(col.nullable) } </td>
<td> { foreign_key } </td>
</tr>
}
});
html! {
<div
class="table-column-info flex-column inline-block"
border="solid dark100 0.2rem collapse"

View File

@ -1,26 +1,40 @@
use std::{rc::Rc, collections::{HashSet, HashMap}, cell::Ref};
use std::{
cell::Ref,
collections::{HashMap, HashSet},
rc::Rc,
};
use anyhow::{Result, bail};
use anyhow::{bail, Result};
use chrono::{Days, Local, NaiveDateTime};
use fake::{
faker::{
address::en::{CityName, StreetName},
company::en::BsNoun,
internet::en::{DomainSuffix, FreeEmail},
lorem::en::*,
name::en::{FirstName, LastName, Name},
phone_number::en::PhoneNumber,
},
Fake,
};
use gloo::console::console_dbg;
use rand::{seq::SliceRandom, Rng, rngs::ThreadRng};
use chrono::{Local, NaiveDateTime, Days};
use fake::{faker::{lorem::en::*, name::en::{FirstName, LastName, Name}, phone_number::en::PhoneNumber, internet::en::{DomainSuffix, FreeEmail}, company::en::BsNoun, address::{en::{CityName, StreetName}}}, Fake};
use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};
use crate::magicdraw_parser::{SQLTable, SQLColumn, SQLType, SQLCheckConstraint};
use crate::magicdraw_parser::{SQLCheckConstraint, SQLColumn, SQLTable, SQLType};
const INDENT: &str = " ";
#[derive(Debug, PartialEq, Clone)]
pub enum SQLIntValueGuess {
Range(i32, i32),
AutoIncrement
AutoIncrement,
}
#[derive(Debug, PartialEq, Clone)]
pub enum SQLTimeValueGuess {
Now,
Future,
Past
Past,
}
#[derive(Debug, PartialEq, Clone)]
@ -58,10 +72,10 @@ pub enum SQLValueGuess {
// TODO: Check primary key constraint
pub fn generate_fake_entries(
tables: &[Rc<SQLTable>],
value_guessess: &Vec<Ref<HashMap<String, SQLValueGuess>>>,
rows_per_table: u32
) -> Result<String> {
tables: &[Rc<SQLTable>],
value_guessess: &Vec<Ref<HashMap<String, SQLValueGuess>>>,
rows_per_table: u32,
) -> Result<String> {
let mut lines = vec![];
let mut rng = rand::thread_rng();
@ -78,11 +92,13 @@ pub fn generate_fake_entries(
let mut foreign_columns = vec![];
for (i, column) in table.columns.iter().enumerate() {
if let Some((table_name, column_name)) = &column.foreign_key {
let (table_idx, table) = tables.iter()
let (table_idx, table) = tables
.iter()
.enumerate()
.find(|(_, table)| table.name.eq(table_name))
.expect("Foreign table not found");
let (column_idx, _) = table.columns
let (column_idx, _) = table
.columns
.iter()
.enumerate()
.find(|(_, column)| column.name.eq(column_name))
@ -109,7 +125,11 @@ pub fn generate_fake_entries(
.get(column.name.as_str())
.expect("Failed to get column guess");
for entry_idx in 0..(rows_per_table as usize) {
entries[entry_idx].push(generate_value(&mut rng, &value_guess, &mut auto_increment_counter));
entries[entry_idx].push(generate_value(
&mut rng,
&value_guess,
&mut auto_increment_counter,
));
}
}
}
@ -120,19 +140,29 @@ pub fn generate_fake_entries(
let before_retain = entries_with_foreign_keys.len();
entries_with_foreign_keys.retain(|(table_idx, entry_idx)| {
for (column_idx, foreign_table_idx, foreign_column_idx) in &all_foreign_columns[*table_idx] {
for (column_idx, foreign_table_idx, foreign_column_idx) in
&all_foreign_columns[*table_idx]
{
let available_values: Vec<&str>;
// If the foreign column, is also a foreign of the other table, ...
// Then we need to filter out available options which have not been filled in
if all_foreign_columns[*foreign_table_idx].iter().find(|(idx, _, _)| idx == foreign_column_idx).is_some() {
available_values = all_entries[*foreign_table_idx].iter()
if all_foreign_columns[*foreign_table_idx]
.iter()
.find(|(idx, _, _)| idx == foreign_column_idx)
.is_some()
{
available_values = all_entries[*foreign_table_idx]
.iter()
.enumerate()
.filter(|(i, _)| entries_with_foreign_keys_copy.contains(&(*foreign_table_idx, *i)))
.filter(|(i, _)| {
entries_with_foreign_keys_copy.contains(&(*foreign_table_idx, *i))
})
.map(|(_, entry)| entry[*foreign_column_idx].as_str())
.collect();
} else {
available_values = all_entries[*foreign_table_idx].iter()
available_values = all_entries[*foreign_table_idx]
.iter()
.map(|entry| entry[*foreign_column_idx].as_str())
.collect();
}
@ -165,7 +195,8 @@ pub fn generate_fake_entries(
lines.push(format!("INSERT INTO {}", table.name));
lines.push(format!("{}({})", INDENT, column_names.join(", ")));
lines.push("VALUES".into());
let entries_str = entries.iter()
let entries_str = entries
.iter()
.map(|entry| format!("{}({})", INDENT, entry.join(", ")))
.collect::<Vec<_>>()
.join(",\n");
@ -183,7 +214,7 @@ fn generate_time_value(rng: &mut ThreadRng, guess: &SQLTimeValueGuess) -> NaiveD
SQLTimeValueGuess::Future => {
let days = rng.gen_range(1..=30);
now.checked_add_days(Days::new(days)).unwrap()
},
}
SQLTimeValueGuess::Past => {
let days = rng.gen_range(7..=365);
now.checked_sub_days(Days::new(days)).unwrap()
@ -191,93 +222,77 @@ fn generate_time_value(rng: &mut ThreadRng, guess: &SQLTimeValueGuess) -> NaiveD
}
}
fn generate_value(rng: &mut ThreadRng, guess: &SQLValueGuess, auto_increment_counter: &mut u32) -> String {
fn generate_value(
rng: &mut ThreadRng,
guess: &SQLValueGuess,
auto_increment_counter: &mut u32,
) -> String {
match guess {
SQLValueGuess::Int(int_guess) => {
match int_guess {
SQLIntValueGuess::Range(min, max) => {
rng.gen_range((*min)..=(*max)).to_string()
},
SQLIntValueGuess::AutoIncrement => {
let str = auto_increment_counter.to_string();
*auto_increment_counter += 1;
str
},
SQLValueGuess::Int(int_guess) => match int_guess {
SQLIntValueGuess::Range(min, max) => rng.gen_range((*min)..=(*max)).to_string(),
SQLIntValueGuess::AutoIncrement => {
let str = auto_increment_counter.to_string();
*auto_increment_counter += 1;
str
}
},
SQLValueGuess::Date(time_gues) => {
SQLValueGuess::Date(time_gues) => {
let datetime = generate_time_value(rng, &time_gues);
format!("'{}'", datetime.format("%Y-%m-%d"))
},
SQLValueGuess::Time(time_gues) => {
}
SQLValueGuess::Time(time_gues) => {
let datetime = generate_time_value(rng, &time_gues);
format!("'{}'", datetime.format("%H:%M:%S"))
},
SQLValueGuess::Datetime(time_gues) => {
}
SQLValueGuess::Datetime(time_gues) => {
let datetime = generate_time_value(rng, &time_gues);
format!("'{}'", datetime.format("%Y-%m-%d %H:%M:%S"))
}
SQLValueGuess::Bool(bool_guess) => match bool_guess {
SQLBoolValueGuess::True => "1".into(),
SQLBoolValueGuess::False => "0".into(),
SQLBoolValueGuess::Random => rng.gen_range(0..=1).to_string(),
},
SQLValueGuess::Bool(bool_guess) => {
match bool_guess {
SQLBoolValueGuess::True => "1".into(),
SQLBoolValueGuess::False => "0".into(),
SQLBoolValueGuess::Random => rng.gen_range(0..=1).to_string(),
}
},
SQLValueGuess::Float(min, max) => {
SQLValueGuess::Float(min, max) => {
let value = rng.gen_range((*min)..(*max));
((value * 100.0 as f32).round() / 100.0).to_string()
},
SQLValueGuess::String(max_size, string_guess) => {
}
SQLValueGuess::String(max_size, string_guess) => {
let mut str = match string_guess {
SQLStringValueGuess::LoremIpsum => {
let mut current_len = 0;
let mut text = vec![];
let words: Vec<String> = Words(3..10).fake_with_rng(rng);
for word in words {
current_len += word.len() + 1;
text.push(word);
if current_len > *max_size { break; }
if current_len > *max_size {
break;
}
}
text.join(" ").to_string()
},
SQLStringValueGuess::FirstName => {
FirstName().fake_with_rng(rng)
},
SQLStringValueGuess::LastName => {
LastName().fake_with_rng(rng)
},
SQLStringValueGuess::FullName => {
Name().fake_with_rng(rng)
},
SQLStringValueGuess::PhoneNumber => {
PhoneNumber().fake_with_rng(rng)
},
SQLStringValueGuess::CityName => {
CityName().fake_with_rng(rng)
},
SQLStringValueGuess::Address => {
StreetName().fake_with_rng(rng)
},
SQLStringValueGuess::Email => {
FreeEmail().fake_with_rng(rng)
},
}
SQLStringValueGuess::FirstName => FirstName().fake_with_rng(rng),
SQLStringValueGuess::LastName => LastName().fake_with_rng(rng),
SQLStringValueGuess::FullName => Name().fake_with_rng(rng),
SQLStringValueGuess::PhoneNumber => PhoneNumber().fake_with_rng(rng),
SQLStringValueGuess::CityName => CityName().fake_with_rng(rng),
SQLStringValueGuess::Address => StreetName().fake_with_rng(rng),
SQLStringValueGuess::Email => FreeEmail().fake_with_rng(rng),
SQLStringValueGuess::URL => {
let suffix: String = DomainSuffix().fake_with_rng(rng);
let noun: String = BsNoun().fake_with_rng(rng);
let noun: String = noun.to_lowercase()
let noun: String = noun
.to_lowercase()
.chars()
.map(|c| if c.is_whitespace() { '-' } else { c })
.collect();
format!("www.{}.{}", noun, suffix)
},
}
SQLStringValueGuess::RandomEnum(options) => {
options.choose(rng).unwrap().to_string()
},
SQLStringValueGuess::Empty => {
"".into()
}
SQLStringValueGuess::Empty => "".into(),
};
str.truncate(*max_size);
@ -289,74 +304,70 @@ fn generate_value(rng: &mut ThreadRng, guess: &SQLValueGuess, auto_increment_cou
fn generate_string_guess(column: &SQLColumn) -> SQLStringValueGuess {
if let Some(constraint) = &column.check_constraint {
if let SQLCheckConstraint::OneOf(options) = constraint {
return SQLStringValueGuess::RandomEnum(options.clone())
return SQLStringValueGuess::RandomEnum(options.clone());
} else {
return SQLStringValueGuess::LoremIpsum
return SQLStringValueGuess::LoremIpsum;
}
}
let name = column.name.to_lowercase();
if name.contains("first") && name.contains("name") {
SQLStringValueGuess::FirstName
SQLStringValueGuess::FirstName
} else if (name.contains("last") && name.contains("name")) || name.contains("surname") {
SQLStringValueGuess::LastName
SQLStringValueGuess::LastName
} else if name.contains("phone") && name.contains("number") {
SQLStringValueGuess::PhoneNumber
SQLStringValueGuess::PhoneNumber
} else if name.contains("city") {
SQLStringValueGuess::CityName
SQLStringValueGuess::CityName
} else if name.contains("address") {
SQLStringValueGuess::Address
SQLStringValueGuess::Address
} else if name.contains("email") {
SQLStringValueGuess::Email
SQLStringValueGuess::Email
} else if name.contains("homepage") || name.contains("website") || name.contains("url") {
SQLStringValueGuess::URL
SQLStringValueGuess::URL
} else {
SQLStringValueGuess::LoremIpsum
SQLStringValueGuess::LoremIpsum
}
}
pub fn generate_guess(column: &SQLColumn) -> SQLValueGuess {
match column.sql_type {
SQLType::Int => {
SQLType::Int => {
if column.primary_key {
SQLValueGuess::Int(SQLIntValueGuess::AutoIncrement)
} else {
SQLValueGuess::Int(SQLIntValueGuess::Range(0, 100))
}
},
SQLType::Float | SQLType::Decimal => {
SQLValueGuess::Float(0.0, 100.0)
},
SQLType::Date => {
}
SQLType::Float | SQLType::Decimal => SQLValueGuess::Float(0.0, 100.0),
SQLType::Date => {
let name = column.name.to_lowercase();
if name.contains("create") || name.contains("update") {
SQLValueGuess::Date(SQLTimeValueGuess::Past)
} else {
SQLValueGuess::Date(SQLTimeValueGuess::Now)
}
},
SQLType::Time => {
}
SQLType::Time => {
let name = column.name.to_lowercase();
if name.contains("create") || name.contains("update") {
SQLValueGuess::Time(SQLTimeValueGuess::Past)
} else {
SQLValueGuess::Time(SQLTimeValueGuess::Now)
}
},
SQLType::Datetime => {
}
SQLType::Datetime => {
let name = column.name.to_lowercase();
if name.contains("create") || name.contains("update") {
SQLValueGuess::Datetime(SQLTimeValueGuess::Past)
} else {
SQLValueGuess::Datetime(SQLTimeValueGuess::Now)
}
},
SQLType::Bool => {
SQLValueGuess::Bool(SQLBoolValueGuess::Random)
},
}
SQLType::Bool => SQLValueGuess::Bool(SQLBoolValueGuess::Random),
SQLType::Varchar(max_size) => {
SQLValueGuess::String(max_size as usize, generate_string_guess(column))
},
}
SQLType::Char(max_size) => {
SQLValueGuess::String(max_size as usize, generate_string_guess(column))
}
@ -364,7 +375,9 @@ pub fn generate_guess(column: &SQLColumn) -> SQLValueGuess {
}
pub fn generate_table_guessess(table: &SQLTable) -> HashMap<String, SQLValueGuess> {
table.columns.iter()
table
.columns
.iter()
.filter(|column| column.foreign_key.is_none())
.map(|column| (column.name.clone(), generate_guess(column)))
.collect()

View File

@ -1,31 +1,31 @@
use std::io::{Read, Seek};
use anyhow::{Context, Ok, Result};
use xml::attribute::OwnedAttribute;
use xml::name::OwnedName;
use xml::{EventReader, reader::XmlEvent};
use xml::{reader::XmlEvent, EventReader};
use zip::ZipArchive;
use anyhow::{Result, Context, Ok};
use crate::magicdraw_parser::utils::get_attribute;
use super::utils::{check_name, check_attribute, MyEventReader, parse_element};
use super::utils::{check_attribute, check_name, parse_element, MyEventReader};
#[derive(Debug)]
pub struct DDLClass {
pub class_id: String,
pub property_ids: Vec<String>
pub property_ids: Vec<String>,
}
#[derive(Debug)]
pub struct DDLScript {
pub script_id: String,
pub classess: Vec<DDLClass>
pub classess: Vec<DDLClass>,
}
#[derive(Debug)]
pub struct DDLProject {
pub model_id: String,
pub scripts: Vec<DDLScript>
pub scripts: Vec<DDLScript>,
}
fn get_id_from_href(attrs: &[OwnedAttribute]) -> Option<String> {
@ -34,16 +34,21 @@ fn get_id_from_href(attrs: &[OwnedAttribute]) -> Option<String> {
Some(parts.1.to_string())
}
fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<DDLClass> {
fn parse_class<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<DDLClass> {
let mut property_ids = vec![];
let mut class_id = None;
fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Class")
check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Class")
}
fn is_property_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Property")
check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Property")
}
parse_element(parser, &mut |p, name, attrs| {
@ -57,20 +62,30 @@ fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute])
Ok(DDLClass {
class_id: class_id.context("Missing class id")?,
property_ids
property_ids,
})
}
fn parse_script<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<DDLScript> {
fn parse_script<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<DDLScript> {
let mut classess = vec![];
let mut script_id = None;
fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Component")
check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Component")
}
fn is_class_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "objects") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.rt.objects:RTClassObject")
check_name(name, None, "objects")
&& check_attribute(
&attributes,
Some("xsi"),
"type",
"md.ce.rt.objects:RTClassObject",
)
}
parse_element(parser, &mut |p, name, attrs| {
@ -84,20 +99,30 @@ fn parse_script<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]
Ok(DDLScript {
script_id: script_id.context("Missing script id")?,
classess
classess,
})
}
fn parse_project<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<DDLProject> {
fn parse_project<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<DDLProject> {
let mut scripts = vec![];
let mut model_id = None;
fn is_model_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "modelElement") && check_attribute(&attributes, Some("xsi"), "type", "uml:Model")
check_name(name, None, "modelElement")
&& check_attribute(&attributes, Some("xsi"), "type", "uml:Model")
}
fn is_component_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "objects") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.rt.objects:RTComponent")
check_name(name, None, "objects")
&& check_attribute(
&attributes,
Some("xsi"),
"type",
"md.ce.rt.objects:RTComponent",
)
}
parse_element(parser, &mut |p, name, attrs| {
@ -111,28 +136,39 @@ fn parse_project<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute
Ok(DDLProject {
model_id: model_id.context("Missing model id")?,
scripts
scripts,
})
}
pub fn parse_ddl_scripts<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<Vec<DDLProject>> {
let mut ddl_scripts = vec![];
let file = project.by_name("personal-com.nomagic.magicdraw.ce.dmn.personaldmncodeengineering")?;
let file =
project.by_name("personal-com.nomagic.magicdraw.ce.dmn.personaldmncodeengineering")?;
let mut parser: MyEventReader<_> = EventReader::new(file).into();
fn is_project_element(name: &OwnedName, attributes: &[OwnedAttribute]) -> bool {
check_name(name, None, "contents") && check_attribute(&attributes, Some("xsi"), "type", "md.ce.ddl.rt.objects:DDLProjectObject")
check_name(name, None, "contents")
&& check_attribute(
&attributes,
Some("xsi"),
"type",
"md.ce.ddl.rt.objects:DDLProjectObject",
)
}
loop {
match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => {
XmlEvent::StartElement {
name, attributes, ..
} => {
if is_project_element(&name, &attributes) {
ddl_scripts.push(parse_project(&mut parser, &attributes)?);
}
},
XmlEvent::EndDocument => { break; },
}
XmlEvent::EndDocument => {
break;
}
_ => {}
}
}

View File

@ -1,17 +1,28 @@
mod utils;
mod uml_model_parser;
mod ddl_parser;
mod sql_types_parser;
use serde::{Serialize, Deserialize};
mod uml_model_parser;
mod utils;
use serde::{Deserialize, Serialize};
use std::{io::{Read, Seek}, collections::HashSet, fmt::Display};
use anyhow::{Result, Context};
use anyhow::{Context, Result};
use lazy_regex::regex_captures;
use std::{
collections::HashSet,
fmt::Display,
io::{Read, Seek},
};
use zip::ZipArchive;
use crate::unwrap_opt_continue;
use self::{uml_model_parser::{parse_uml_model, UMLModel, UMLClass, UMLModifier, UMLNullableModifier, UMLPrimaryKeyModifier, UMLTypeModifier, UMLForeignKeyModifier}, ddl_parser::parse_ddl_scripts, sql_types_parser::{parse_sql_types, SQLTypeName}};
use self::{
ddl_parser::parse_ddl_scripts,
sql_types_parser::{parse_sql_types, SQLTypeName},
uml_model_parser::{
parse_uml_model, UMLClass, UMLForeignKeyModifier, UMLModel, UMLModifier,
UMLNullableModifier, UMLPrimaryKeyModifier, UMLTypeModifier,
},
};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum SQLType {
@ -29,15 +40,15 @@ pub enum SQLType {
impl Display for SQLType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SQLType::Int => write!(f, "INT"),
SQLType::Decimal => write!(f, "DECIMAL"),
SQLType::Date => write!(f, "DATE"),
SQLType::Time => write!(f, "TIME"),
SQLType::Datetime => write!(f, "DATETIME"),
SQLType::Float => write!(f, "FLOAT"),
SQLType::Bool => write!(f, "BOOL"),
SQLType::Char(size) => write!(f, "CHAR({})", size),
SQLType::Varchar(size) => write!(f, "VARCHAR({})", size),
SQLType::Int => write!(f, "INT"),
SQLType::Decimal => write!(f, "DECIMAL"),
SQLType::Date => write!(f, "DATE"),
SQLType::Time => write!(f, "TIME"),
SQLType::Datetime => write!(f, "DATETIME"),
SQLType::Float => write!(f, "FLOAT"),
SQLType::Bool => write!(f, "BOOL"),
SQLType::Char(size) => write!(f, "CHAR({})", size),
SQLType::Varchar(size) => write!(f, "VARCHAR({})", size),
}
}
}
@ -45,7 +56,7 @@ impl Display for SQLType {
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum SQLCheckConstraint {
OneOf(Vec<String>),
Freeform(String)
Freeform(String),
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
@ -55,7 +66,7 @@ pub struct SQLColumn {
pub primary_key: bool,
pub nullable: bool,
pub foreign_key: Option<(String, String)>,
pub check_constraint: Option<SQLCheckConstraint>
pub check_constraint: Option<SQLCheckConstraint>,
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
@ -66,7 +77,7 @@ pub struct SQLTable {
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct SQLTableCollection {
pub tables: Vec<SQLTable>
pub tables: Vec<SQLTable>,
}
fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass> {
@ -82,7 +93,11 @@ fn find_class_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLClass
fn is_nullabe(modifiers: &[UMLModifier], property: &str) -> bool {
for modifier in modifiers {
if let UMLModifier::Nullable(UMLNullableModifier { property_id, nullable }) = modifier {
if let UMLModifier::Nullable(UMLNullableModifier {
property_id,
nullable,
}) = modifier
{
if property_id.eq(property) {
return *nullable;
}
@ -95,7 +110,7 @@ fn is_primary_key(modifiers: &[UMLModifier], property: &str) -> bool {
for modifier in modifiers {
if let UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }) = modifier {
if property_id.eq(property) {
return true
return true;
}
}
}
@ -104,9 +119,13 @@ fn is_primary_key(modifiers: &[UMLModifier], property: &str) -> bool {
fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option<&'a str> {
for modifier in modifiers {
if let UMLModifier::Type(UMLTypeModifier { property_id, modifier }) = modifier {
if let UMLModifier::Type(UMLTypeModifier {
property_id,
modifier,
}) = modifier
{
if property_id.eq(property) {
return Some(modifier)
return Some(modifier);
}
}
}
@ -115,19 +134,27 @@ fn get_type_modifier<'a>(modifiers: &'a [UMLModifier], property: &str) -> Option
fn get_foreign_key_constraint<'a>(modifiers: &'a [UMLModifier], from_id: &str) -> Option<&'a str> {
for modifier in modifiers {
if let UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id }) = modifier {
if let UMLModifier::ForeignKey(UMLForeignKeyModifier {
from_property_id,
to_property_id,
}) = modifier
{
if from_property_id.eq(from_id) {
return Some(&to_property_id)
return Some(&to_property_id);
}
}
}
None
}
fn get_foreign_key(modifiers: &[UMLModifier], classess: &[&UMLClass], property: &str) -> Result<Option<(String, String)>> {
fn get_foreign_key(
modifiers: &[UMLModifier],
classess: &[&UMLClass],
property: &str,
) -> Result<Option<(String, String)>> {
let to_id = get_foreign_key_constraint(modifiers, property);
if to_id.is_none() {
return Ok(None)
return Ok(None);
}
let to_id = to_id.unwrap();
@ -156,12 +183,14 @@ fn parse_check_constraint(str: &str) -> SQLCheckConstraint {
Some(SQLCheckConstraint::OneOf(variants))
}
try_parse_one_of(str)
.unwrap_or(SQLCheckConstraint::Freeform(str.to_string()))
try_parse_one_of(str).unwrap_or(SQLCheckConstraint::Freeform(str.to_string()))
}
// TODO: Refactor this function, less nesting would be good
fn get_sql_check_constraint<'a>(models: &'a [UMLModel], property_name: &str) -> Option<SQLCheckConstraint> {
fn get_sql_check_constraint<'a>(
models: &'a [UMLModel],
property_name: &str,
) -> Option<SQLCheckConstraint> {
for model in models {
for package in &model.packages {
for class in &package.classess {
@ -179,14 +208,18 @@ fn get_sql_check_constraint<'a>(models: &'a [UMLModel], property_name: &str) ->
None
}
fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &str) -> Result<SQLType> {
fn get_sql_type(
modifiers: &[UMLModifier],
type_name: SQLTypeName,
property: &str,
) -> Result<SQLType> {
Ok(match type_name {
SQLTypeName::Int => SQLType::Int,
SQLTypeName::Date => SQLType::Date,
SQLTypeName::Float => SQLType::Float,
SQLTypeName::Bool => SQLType::Bool,
SQLTypeName::Decimal => SQLType::Decimal,
SQLTypeName::Char => {
SQLTypeName::Int => SQLType::Int,
SQLTypeName::Date => SQLType::Date,
SQLTypeName::Float => SQLType::Float,
SQLTypeName::Bool => SQLType::Bool,
SQLTypeName::Decimal => SQLType::Decimal,
SQLTypeName::Char => {
if let Some(type_modifier) = get_type_modifier(modifiers, property) {
let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier)
.context("Type modifier doesn't match format")?;
@ -196,8 +229,8 @@ fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &st
// For now just pick a defautl arbitrarily
SQLType::Char(31)
}
},
SQLTypeName::Varchar => {
}
SQLTypeName::Varchar => {
if let Some(type_modifier) = get_type_modifier(modifiers, property) {
let (_, size) = regex_captures!(r#"^\((\d+)\)$"#, type_modifier)
.context("Type modifier doesn't match format")?;
@ -207,12 +240,13 @@ fn get_sql_type(modifiers: &[UMLModifier], type_name: SQLTypeName, property: &st
// For now just pick a defautl arbitrarily
SQLType::Varchar(255)
}
},
}
})
}
fn get_used_types<'a>(models: &'a [UMLModel]) -> HashSet<&'a String> {
models.iter()
models
.iter()
.flat_map(|model| &model.packages)
.flat_map(|package| &package.classess)
.flat_map(|class| &class.properties)
@ -232,26 +266,43 @@ pub fn parse_project<R: Read + Seek>(project_file: R) -> Result<Vec<SQLTableColl
for ddl_script in ddl_project.scripts {
let mut tables = vec![];
let model_properties = ddl_script.classess.iter()
.flat_map(|class| class.property_ids.iter().map(|prop| (&class.class_id, prop)))
let model_properties = ddl_script
.classess
.iter()
.flat_map(|class| {
class
.property_ids
.iter()
.map(|prop| (&class.class_id, prop))
})
.collect::<Vec<_>>();
let mut model_classess = vec![];
for ddl_class in &ddl_script.classess {
let model_class = find_class_by_id(&models, &ddl_class.class_id).context("UML class not found")?;
let model_class = find_class_by_id(&models, &ddl_class.class_id)
.context("UML class not found")?;
model_classess.push(model_class);
}
for (ddl_class, model_class) in ddl_script.classess.iter().zip(&model_classess) {
let name = model_class.name.clone().context("UML class name not found")?;
let name = model_class
.name
.clone()
.context("UML class name not found")?;
let mut columns = vec![];
for property_id in &ddl_class.property_ids {
let property = model_class.properties.iter().find(|p| p.id.eq(property_id)).context("Property not found")?;
let property = model_class
.properties
.iter()
.find(|p| p.id.eq(property_id))
.context("Property not found")?;
let prop_name = unwrap_opt_continue!(&property.name).clone();
let type_href = unwrap_opt_continue!(&property.type_href);
let type_name = sql_type_names.get(type_href).context("Proerty type name conversion not found")?;
let type_name = sql_type_names
.get(type_href)
.context("Proerty type name conversion not found")?;
let check_constraint = get_sql_check_constraint(&models, &prop_name);
let foreign_key = get_foreign_key(&modifiers, &model_classess, property_id)?;
@ -266,10 +317,7 @@ pub fn parse_project<R: Read + Seek>(project_file: R) -> Result<Vec<SQLTableColl
})
}
tables.push(SQLTable {
name,
columns
})
tables.push(SQLTable { name, columns })
}
collections.push(SQLTableCollection { tables })
}

View File

@ -1,18 +1,21 @@
use std::{io::{Read, Seek}, collections::{HashMap, HashSet}};
use std::{
collections::{HashMap, HashSet},
io::{Read, Seek},
};
use xml::{EventReader, reader::XmlEvent, attribute::OwnedAttribute, name::OwnedName};
use anyhow::{bail, Context, Result};
use xml::{attribute::OwnedAttribute, name::OwnedName, reader::XmlEvent, EventReader};
use zip::ZipArchive;
use anyhow::{Result, Context, bail};
use crate::unwrap_opt_continue;
use super::utils::{MyEventReader, check_name, parse_element, get_attribute, check_attribute};
use super::utils::{check_attribute, check_name, get_attribute, parse_element, MyEventReader};
#[derive(Debug)]
struct UsedPackage {
share_point_id: String,
name: String,
needed_types: Vec<String>
needed_types: Vec<String>,
}
#[derive(Debug, Clone, Copy)]
@ -23,7 +26,7 @@ pub enum SQLTypeName {
Float,
Bool,
Char,
Varchar
Varchar,
}
fn get_used_project_name(attrs: &[OwnedAttribute]) -> Option<&str> {
@ -31,14 +34,20 @@ fn get_used_project_name(attrs: &[OwnedAttribute]) -> Option<&str> {
project_uri.split("/").last()
}
fn parse_used_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute], needed_types: &[&str]) -> Result<UsedPackage> {
fn parse_used_package<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
needed_types: &[&str],
) -> Result<UsedPackage> {
let mut share_point_id = None;
let project_uri = get_attribute(&attrs, None, "usedProjectURI")?;
let name = project_uri.split("/").last().unwrap();
parse_element(parser, &mut |p, name, attrs| {
if share_point_id.is_none() && check_name(&name, None, "mountPoints") {
share_point_id = get_attribute(&attrs, None, "sharePointID").ok().map(str::to_string);
share_point_id = get_attribute(&attrs, None, "sharePointID")
.ok()
.map(str::to_string);
}
Ok(())
})?;
@ -46,32 +55,47 @@ fn parse_used_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttr
Ok(UsedPackage {
name: name.to_string(),
share_point_id: share_point_id.context("Share point id not found")?,
needed_types: needed_types.iter().map(|s| s.to_string()).collect()
needed_types: needed_types.iter().map(|s| s.to_string()).collect(),
})
}
fn list_used_packages<R: Read>(file: R, needed_types: &HashSet<&String>) -> Result<Vec<UsedPackage>> {
fn list_used_packages<R: Read>(
file: R,
needed_types: &HashSet<&String>,
) -> Result<Vec<UsedPackage>> {
let mut packages = vec![];
let mut needed_types_per_package = HashMap::new();
for needed_type in needed_types.iter() {
let (package_name, type_id) = unwrap_opt_continue!(needed_type.split_once("#"));
let ids = needed_types_per_package.entry(package_name).or_insert(vec![]);
let ids = needed_types_per_package
.entry(package_name)
.or_insert(vec![]);
ids.push(type_id);
}
let mut parser: MyEventReader<_> = EventReader::new(file).into();
loop {
match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => {
XmlEvent::StartElement {
name, attributes, ..
} => {
if check_name(&name, None, "projectUsages") {
let project_name = unwrap_opt_continue!(get_used_project_name(&attributes));
if let Some(needed_types_for_package) = needed_types_per_package.get(&project_name) {
packages.push(parse_used_package(&mut parser, &attributes, needed_types_for_package)?);
if let Some(needed_types_for_package) =
needed_types_per_package.get(&project_name)
{
packages.push(parse_used_package(
&mut parser,
&attributes,
needed_types_for_package,
)?);
}
}
},
XmlEvent::EndDocument => { break; },
}
XmlEvent::EndDocument => {
break;
}
_ => {}
}
}
@ -93,15 +117,18 @@ fn parse_type_name(str: &str) -> Result<SQLTypeName> {
"Integer" | "integer" | "int" => Int,
"date" => Date,
"Boolean" => Bool,
_ => bail!("Unknown SQL type: '{}'", str)
_ => bail!("Unknown SQL type: '{}'", str),
})
}
fn parse_types_package<R: Read>(parser: &mut MyEventReader<R>) -> Result<Vec<(String, SQLTypeName)>> {
fn parse_types_package<R: Read>(
parser: &mut MyEventReader<R>,
) -> Result<Vec<(String, SQLTypeName)>> {
let mut types = vec![];
fn is_primitive_type_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(&name, None, "packagedElement") && check_attribute(&attrs, Some("xsi"), "type", "uml:PrimitiveType")
check_name(&name, None, "packagedElement")
&& check_attribute(&attrs, Some("xsi"), "type", "uml:PrimitiveType")
}
parse_element(parser, &mut |p, name, attrs| {
@ -109,8 +136,8 @@ fn parse_types_package<R: Read>(parser: &mut MyEventReader<R>) -> Result<Vec<(St
let type_name = get_attribute(&attrs, None, "name")?;
if !type_name.eq("StructuredExpression") {
types.push((
get_attribute(&attrs, Some("xmi"), "id")?.to_string(),
parse_type_name(type_name)?
get_attribute(&attrs, Some("xmi"), "id")?.to_string(),
parse_type_name(type_name)?,
));
}
}
@ -120,26 +147,37 @@ fn parse_types_package<R: Read>(parser: &mut MyEventReader<R>) -> Result<Vec<(St
Ok(types)
}
fn parse_primitive_types<R: Read>(reader: R, used_packages: &[UsedPackage]) -> Result<Vec<(String, SQLTypeName)>> {
fn parse_primitive_types<R: Read>(
reader: R,
used_packages: &[UsedPackage],
) -> Result<Vec<(String, SQLTypeName)>> {
let mut types = vec![];
let mut parser: MyEventReader<_> = EventReader::new(reader).into();
loop {
match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => {
XmlEvent::StartElement {
name, attributes, ..
} => {
if check_name(&name, Some("uml"), "Package") {
if let Some(id) = get_attribute(&attributes, None, "ID").ok() {
if let Some(package) = used_packages.iter().find(|p| p.share_point_id.eq(id)) {
if let Some(package) =
used_packages.iter().find(|p| p.share_point_id.eq(id))
{
let package_types = parse_types_package(&mut parser)?
.into_iter()
.filter(|t| package.needed_types.contains(&t.0))
.map(|(id, type_name)| (format!("{}#{}", package.name, id), type_name));
.map(|(id, type_name)| {
(format!("{}#{}", package.name, id), type_name)
});
types.extend(package_types);
}
}
}
},
XmlEvent::EndDocument => { break; },
}
XmlEvent::EndDocument => {
break;
}
_ => {}
}
}
@ -147,13 +185,17 @@ fn parse_primitive_types<R: Read>(reader: R, used_packages: &[UsedPackage]) -> R
Ok(types)
}
pub fn parse_sql_types<R: Read + Seek>(project: &mut ZipArchive<R>, needed_types: &HashSet<&String>) -> Result<HashMap<String, SQLTypeName>> {
pub fn parse_sql_types<R: Read + Seek>(
project: &mut ZipArchive<R>,
needed_types: &HashSet<&String>,
) -> Result<HashMap<String, SQLTypeName>> {
let mut type_names = HashMap::new();
let meta_model_file = project.by_name("com.nomagic.ci.metamodel.project")?;
let used_packages = list_used_packages(meta_model_file, needed_types)?;
let snapshot_files = project.file_names()
let snapshot_files = project
.file_names()
.filter(|f| is_umodel_snapshot_file(f))
.map(|f| f.to_string())
.collect::<Vec<_>>();

View File

@ -1,19 +1,22 @@
use std::io::{Read, Seek};
use xml::{EventReader, reader::XmlEvent, attribute::OwnedAttribute, name::OwnedName};
use anyhow::{Context, Result};
use xml::{attribute::OwnedAttribute, name::OwnedName, reader::XmlEvent, EventReader};
use zip::ZipArchive;
use anyhow::{Result, Context};
use crate::{unwrap_err_continue, unwrap_opt_continue};
use super::utils::{check_name, parse_element, get_attribute, check_attribute, MyEventReader, ParseProjectError, get_element_characters};
use super::utils::{
check_attribute, check_name, get_attribute, get_element_characters, parse_element,
MyEventReader, ParseProjectError,
};
#[derive(Debug)]
pub struct UMLProperty {
pub id: String,
pub name: Option<String>,
pub is_id: bool,
pub type_href: Option<String>
pub type_href: Option<String>,
}
// TODO: Make this an enum? Because from what I have seen there were only 2 cases,
@ -33,32 +36,32 @@ pub struct UMLClass {
pub id: String,
pub name: Option<String>,
pub properties: Vec<UMLProperty>,
pub constraints: Vec<UMLConstraint>
pub constraints: Vec<UMLConstraint>,
}
#[derive(Debug)]
pub struct UMLPackage {
pub id: String,
pub name: Option<String>,
pub classess: Vec<UMLClass>
pub classess: Vec<UMLClass>,
}
#[derive(Debug)]
pub struct UMLModel {
pub id: String,
pub name: String,
pub packages: Vec<UMLPackage>
pub packages: Vec<UMLPackage>,
}
#[derive(Debug)]
pub struct UMLPrimaryKeyModifier {
pub property_id: String
pub property_id: String,
}
#[derive(Debug)]
pub struct UMLNullableModifier {
pub property_id: String,
pub nullable: bool
pub nullable: bool,
}
#[derive(Debug)]
@ -69,13 +72,13 @@ pub struct UMLForeignKeyModifier {
#[derive(Debug)]
pub struct UMLUniqueModifier {
pub property_id: String
pub property_id: String,
}
#[derive(Debug)]
pub struct UMLTypeModifier {
pub property_id: String,
pub modifier: String
pub modifier: String,
}
#[derive(Debug)]
@ -84,13 +87,18 @@ pub enum UMLModifier {
PirmaryKey(UMLPrimaryKeyModifier),
Nullable(UMLNullableModifier),
ForeignKey(UMLForeignKeyModifier),
Type(UMLTypeModifier)
Type(UMLTypeModifier),
}
fn parse_property<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLProperty> {
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
let is_id = get_attribute(attrs, None, "isID").unwrap_or("false").eq("true");
fn parse_property<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLProperty> {
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
let is_id = get_attribute(attrs, None, "isID")
.unwrap_or("false")
.eq("true");
let mut type_href = None;
parse_element(parser, &mut |p, name, attrs| {
@ -103,22 +111,27 @@ fn parse_property<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribut
})?;
Ok(UMLProperty {
id,
name,
is_id,
type_href
id,
name,
is_id,
type_href,
})
}
fn parse_constraint<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<Option<UMLConstraint>> {
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
fn parse_constraint<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<Option<UMLConstraint>> {
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let mut constrainted_element_id = None;
let mut language = None;
let mut body = None;
parse_element(parser, &mut |p, name, attrs| {
if check_name(&name, None, "constrainedElement") && constrainted_element_id.is_none() {
constrainted_element_id = get_attribute(&attrs, Some("xmi"), "idref").ok().map(str::to_string);
constrainted_element_id = get_attribute(&attrs, Some("xmi"), "idref")
.ok()
.map(str::to_string);
} else if check_name(&name, None, "body") && body.is_none() {
let contents = get_element_characters(p)?;
if contents.len() > 0 {
@ -137,32 +150,37 @@ fn parse_constraint<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttrib
class_id: Some(constrainted_element_id.context("Missing class id")?),
body: Some(format!("in {}", check_body)),
property_id: None,
property_name: Some(prop_name.into())
}))
property_name: Some(prop_name.into()),
}));
}
}
return Ok(Some(UMLConstraint {
id,
id,
property_id: Some(constrainted_element_id.context("Missing property id")?),
body: None,
body: None,
class_id: None,
property_name: None
}))
property_name: None,
}));
}
fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLClass> {
fn parse_class<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLClass> {
let mut properties = vec![];
let mut consraints = vec![];
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
fn is_property_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "ownedAttribute") && check_attribute(&attrs, Some("xmi"), "type", "uml:Property")
check_name(name, None, "ownedAttribute")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Property")
}
fn is_constraint_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "ownedRule") && check_attribute(&attrs, Some("xmi"), "type", "uml:Constraint")
check_name(name, None, "ownedRule")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Constraint")
}
parse_element(parser, &mut |p, name, attrs| {
@ -177,20 +195,24 @@ fn parse_class<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute])
})?;
Ok(UMLClass {
id,
name,
properties,
constraints: consraints,
id,
name,
properties,
constraints: consraints,
})
}
fn parse_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLPackage> {
fn parse_package<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLPackage> {
let mut classess = vec![];
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name").ok().map(str::to_string);
fn is_class_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "packagedElement") && check_attribute(&attrs, Some("xmi"), "type", "uml:Class")
check_name(name, None, "packagedElement")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Class")
}
parse_element(parser, &mut |p, name, attrs| {
@ -200,20 +222,20 @@ fn parse_package<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute
Ok(())
})?;
Ok(UMLPackage {
id,
name,
classess
})
Ok(UMLPackage { id, name, classess })
}
fn parse_model<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute]) -> Result<UMLModel> {
fn parse_model<R: Read>(
parser: &mut MyEventReader<R>,
attrs: &[OwnedAttribute],
) -> Result<UMLModel> {
let mut packages = vec![];
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let id = get_attribute(attrs, Some("xmi"), "id")?.into();
let name = get_attribute(attrs, None, "name")?.into();
fn is_package_element(name: &OwnedName, attrs: &[OwnedAttribute]) -> bool {
check_name(name, None, "packagedElement") && check_attribute(&attrs, Some("xmi"), "type", "uml:Package")
check_name(name, None, "packagedElement")
&& check_attribute(&attrs, Some("xmi"), "type", "uml:Package")
}
parse_element(parser, &mut |p, name, attrs| {
@ -223,11 +245,7 @@ fn parse_model<R: Read>(parser: &mut MyEventReader<R>, attrs: &[OwnedAttribute])
Ok(())
})?;
Ok(UMLModel {
id,
name,
packages
})
Ok(UMLModel { id, name, packages })
}
fn find_constraint_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UMLConstraint> {
@ -246,7 +264,9 @@ fn find_constraint_by_id<'a>(models: &'a [UMLModel], id: &str) -> Option<&'a UML
None
}
pub fn parse_uml_model<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<(Vec<UMLModel>, Vec<UMLModifier>)> {
pub fn parse_uml_model<R: Read + Seek>(
project: &mut ZipArchive<R>,
) -> Result<(Vec<UMLModel>, Vec<UMLModifier>)> {
let mut models = vec![];
let mut modifiers = vec![];
@ -255,37 +275,64 @@ pub fn parse_uml_model<R: Read + Seek>(project: &mut ZipArchive<R>) -> Result<(V
loop {
match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => {
XmlEvent::StartElement {
name, attributes, ..
} => {
if check_name(&name, Some("uml"), "Model") {
models.push(parse_model(&mut parser, &attributes)?);
} else if check_name(&name, Some("SQLProfile"), "PrimaryKey") {
let constraint_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Constraint"));
let constraint = unwrap_opt_continue!(find_constraint_by_id(&models, constraint_id));
let constraint_id =
unwrap_err_continue!(get_attribute(&attributes, None, "base_Constraint"));
let constraint =
unwrap_opt_continue!(find_constraint_by_id(&models, constraint_id));
let property_id = unwrap_opt_continue!(&constraint.property_id).clone();
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }));
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier {
property_id,
}));
} else if check_name(&name, Some("SQLProfile"), "PKMember") {
let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Property")).to_string();
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier { property_id }));
let property_id =
unwrap_err_continue!(get_attribute(&attributes, None, "base_Property"))
.to_string();
modifiers.push(UMLModifier::PirmaryKey(UMLPrimaryKeyModifier {
property_id,
}));
} else if check_name(&name, Some("SQLProfile"), "Column") {
let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Property")).to_string();
let nullable = unwrap_err_continue!(get_attribute(&attributes, None, "nullable")).eq("true");
modifiers.push(UMLModifier::Nullable(UMLNullableModifier { property_id, nullable }));
let property_id =
unwrap_err_continue!(get_attribute(&attributes, None, "base_Property"))
.to_string();
let nullable =
unwrap_err_continue!(get_attribute(&attributes, None, "nullable"))
.eq("true");
modifiers.push(UMLModifier::Nullable(UMLNullableModifier {
property_id,
nullable,
}));
} else if check_name(&name, Some("MagicDraw_Profile"), "typeModifier") {
let property_id = unwrap_err_continue!(get_attribute(&attributes, None, "base_Element")).into();
let modifier = unwrap_err_continue!(get_attribute(&attributes, None, "typeModifier")).into();
modifiers.push(UMLModifier::Type(UMLTypeModifier { property_id, modifier }));
let property_id =
unwrap_err_continue!(get_attribute(&attributes, None, "base_Element"))
.into();
let modifier =
unwrap_err_continue!(get_attribute(&attributes, None, "typeModifier"))
.into();
modifiers.push(UMLModifier::Type(UMLTypeModifier {
property_id,
modifier,
}));
} else if check_name(&name, Some("SQLProfile"), "FK") {
let from_property_id = unwrap_err_continue!(get_attribute(&attributes, None, "members")).into();
let to_property_id = unwrap_err_continue!(get_attribute(&attributes, None, "referencedMembers")).into();
modifiers.push(UMLModifier::ForeignKey(UMLForeignKeyModifier { from_property_id, to_property_id }));
let from_property_id =
unwrap_err_continue!(get_attribute(&attributes, None, "members")).into();
let to_property_id =
unwrap_err_continue!(get_attribute(&attributes, None, "referencedMembers"))
.into();
modifiers.push(UMLModifier::ForeignKey(UMLForeignKeyModifier {
from_property_id,
to_property_id,
}));
}
},
XmlEvent::EndDocument => { break; },
}
XmlEvent::EndDocument => {
break;
}
_ => {}
}
}

View File

@ -1,12 +1,12 @@
use std::io::Read;
use xml::{EventReader, reader::XmlEvent, name::OwnedName, attribute::OwnedAttribute};
use anyhow::Result;
use thiserror::Error;
use xml::{attribute::OwnedAttribute, name::OwnedName, reader::XmlEvent, EventReader};
pub struct MyEventReader<R: Read> {
depth: u32,
event_reader: EventReader<R>
event_reader: EventReader<R>,
}
impl<R: Read> MyEventReader<R> {
@ -27,12 +27,12 @@ impl<R: Read> MyEventReader<R> {
}
impl<R: Read> From<EventReader<R>> for MyEventReader<R> {
fn from(event_reader: EventReader<R>) -> Self {
MyEventReader {
depth: 0,
event_reader
}
}
fn from(event_reader: EventReader<R>) -> Self {
MyEventReader {
depth: 0,
event_reader,
}
}
}
#[derive(Error, Debug, PartialEq)]
@ -41,7 +41,7 @@ pub enum ParseProjectError {
AttributeNotFound(Option<String>, String),
#[error("Unexpected end of XML document")]
EndOfDocument
EndOfDocument,
}
fn format_name_from_parts(prefix: &Option<String>, local_name: &str) -> String {
@ -60,17 +60,27 @@ pub fn check_name(name: &OwnedName, prefix: Option<&str>, local_name: &str) -> b
name.local_name.eq(local_name) && name.prefix_ref().eq(&prefix)
}
pub fn get_attribute<'a>(attributes: &'a [OwnedAttribute], prefix: Option<&str>, name: &str) -> Result<&'a str, ParseProjectError> {
Ok(attributes.iter()
pub fn get_attribute<'a>(
attributes: &'a [OwnedAttribute],
prefix: Option<&str>,
name: &str,
) -> Result<&'a str, ParseProjectError> {
Ok(attributes
.iter()
.find(|attr| check_name(&attr.name, prefix, name))
.map(|attr| &attr.value[..])
.ok_or_else(
|| ParseProjectError::AttributeNotFound(prefix.map(|s| s.to_owned()), name.to_owned()),
)?)
.ok_or_else(|| {
ParseProjectError::AttributeNotFound(prefix.map(|s| s.to_owned()), name.to_owned())
})?)
}
#[inline(always)]
pub fn check_attribute(attributes: &[OwnedAttribute], prefix: Option<&str>, name: &str, expected_value: &str) -> bool {
pub fn check_attribute(
attributes: &[OwnedAttribute],
prefix: Option<&str>,
name: &str,
expected_value: &str,
) -> bool {
if let Ok(attr) = get_attribute(attributes, prefix, name) {
return attr.eq(expected_value);
}
@ -84,10 +94,10 @@ pub fn get_element_characters<R: Read>(parser: &mut MyEventReader<R>) -> Result<
match parser.next()? {
XmlEvent::Characters(text) => {
parts.push(text);
},
}
XmlEvent::EndElement { name } => {
break;
},
}
_ => {}
}
}
@ -95,23 +105,30 @@ pub fn get_element_characters<R: Read>(parser: &mut MyEventReader<R>) -> Result<
Ok(parts.join(" "))
}
pub fn parse_element<R: Read, F>(parser: &mut MyEventReader<R>, process_element: &mut F) -> Result<()>
where F: FnMut(&mut MyEventReader<R>, OwnedName, Vec<OwnedAttribute>) -> Result<()>
pub fn parse_element<R: Read, F>(
parser: &mut MyEventReader<R>,
process_element: &mut F,
) -> Result<()>
where
F: FnMut(&mut MyEventReader<R>, OwnedName, Vec<OwnedAttribute>) -> Result<()>,
{
let starting_depth = parser.depth();
loop {
match parser.next()? {
XmlEvent::StartElement { name, attributes, .. } => {
XmlEvent::StartElement {
name, attributes, ..
} => {
process_element(parser, name, attributes)?;
if parser.depth() == starting_depth-1 { break; }
if parser.depth() == starting_depth - 1 {
break;
}
}
XmlEvent::EndElement { name } => {
if parser.depth() == starting_depth-1 { break; }
},
XmlEvent::EndDocument => {
Err(ParseProjectError::EndOfDocument)?
},
if parser.depth() == starting_depth - 1 {
break;
}
}
XmlEvent::EndDocument => Err(ParseProjectError::EndOfDocument)?,
_ => {}
}
}
@ -121,20 +138,24 @@ pub fn parse_element<R: Read, F>(parser: &mut MyEventReader<R>, process_element:
#[macro_export]
macro_rules! unwrap_err_continue {
($res:expr) => {
match $res {
Ok(val) => val,
Err(e) => { continue; }
}
};
($res:expr) => {
match $res {
Ok(val) => val,
Err(e) => {
continue;
}
}
};
}
#[macro_export]
macro_rules! unwrap_opt_continue {
($res:expr) => {
match $res {
Some(val) => val,
None => { continue; }
}
};
($res:expr) => {
match $res {
Some(val) => val,
None => {
continue;
}
}
};
}

View File

@ -2,10 +2,10 @@ use anyhow::Result;
use app::App;
mod magicdraw_parser;
mod app;
mod components;
mod generate_sql;
mod magicdraw_parser;
// TODO: Make this work with enumation lookup tables
// TODO: Dark theme switch button