From 35818ec195332e22cc0271e0ef7e33a2386188d7 Mon Sep 17 00:00:00 2001 From: danielratiu Date: Sun, 28 Jul 2024 13:46:24 +0200 Subject: [PATCH] feature/initial support for rust (#35) * initial support for parsing mps files using Rust - currently we support default persistency and file-per-root persistency --------- Co-authored-by: Micahel Langhammer --- .github/workflows/mps_cli_rs_build.yaml | 26 ++ .gitignore | 1 + Readme.md | 3 +- mps-cli-py/.idea/workspace.xml | 10 +- mps-cli-py/src/mpscli/demo.py | 5 +- mps-cli-rs/.vscode/settings.json | 7 + mps-cli-rs/Cargo.lock | 67 +++++ mps-cli-rs/Cargo.toml | 22 ++ mps-cli-rs/src/builder/mod.rs | 7 + mps-cli-rs/src/builder/node_id_utils.rs | 56 ++++ mps-cli-rs/src/builder/playground.rs | 20 ++ mps-cli-rs/src/builder/slanguage_builder.rs | 57 ++++ mps-cli-rs/src/builder/smodel_builder_base.rs | 243 ++++++++++++++++++ .../smodel_builder_default_persistency.rs | 27 ++ ...model_builder_file_per_root_persistency.rs | 101 ++++++++ .../builder/smodules_repository_builder.rs | 102 ++++++++ mps-cli-rs/src/builder/ssolution_builder.rs | 71 +++++ mps-cli-rs/src/lib.rs | 8 + mps-cli-rs/src/main.rs | 41 +++ mps-cli-rs/src/model/mod.rs | 7 + mps-cli-rs/src/model/sconcept.rs | 86 +++++++ mps-cli-rs/src/model/slanguage.rs | 18 ++ mps-cli-rs/src/model/smodel.rs | 51 ++++ mps-cli-rs/src/model/snode.rs | 89 +++++++ mps-cli-rs/src/model/snoderef.rs | 37 +++ mps-cli-rs/src/model/srepository.rs | 60 +++++ mps-cli-rs/src/model/ssolution.rs | 29 +++ mps-cli-rs/tests/default_persistency_tests.rs | 9 + .../tests/file_per_root_persistency_tests.rs | 9 + mps-cli-rs/tests/model_completeness_tests.rs | 59 +++++ ...ry_top.default_persistency.library_top.mps | 3 + .../.model | 1 + 32 files changed, 1328 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/mps_cli_rs_build.yaml create mode 100644 mps-cli-rs/.vscode/settings.json create mode 100644 mps-cli-rs/Cargo.lock create mode 100644 mps-cli-rs/Cargo.toml create mode 100644 mps-cli-rs/src/builder/mod.rs create mode 100644 mps-cli-rs/src/builder/node_id_utils.rs create mode 100644 mps-cli-rs/src/builder/playground.rs create mode 100644 mps-cli-rs/src/builder/slanguage_builder.rs create mode 100644 mps-cli-rs/src/builder/smodel_builder_base.rs create mode 100644 mps-cli-rs/src/builder/smodel_builder_default_persistency.rs create mode 100644 mps-cli-rs/src/builder/smodel_builder_file_per_root_persistency.rs create mode 100644 mps-cli-rs/src/builder/smodules_repository_builder.rs create mode 100644 mps-cli-rs/src/builder/ssolution_builder.rs create mode 100644 mps-cli-rs/src/lib.rs create mode 100644 mps-cli-rs/src/main.rs create mode 100644 mps-cli-rs/src/model/mod.rs create mode 100644 mps-cli-rs/src/model/sconcept.rs create mode 100644 mps-cli-rs/src/model/slanguage.rs create mode 100644 mps-cli-rs/src/model/smodel.rs create mode 100644 mps-cli-rs/src/model/snode.rs create mode 100644 mps-cli-rs/src/model/snoderef.rs create mode 100644 mps-cli-rs/src/model/srepository.rs create mode 100644 mps-cli-rs/src/model/ssolution.rs create mode 100644 mps-cli-rs/tests/default_persistency_tests.rs create mode 100644 mps-cli-rs/tests/file_per_root_persistency_tests.rs create mode 100644 mps-cli-rs/tests/model_completeness_tests.rs diff --git a/.github/workflows/mps_cli_rs_build.yaml b/.github/workflows/mps_cli_rs_build.yaml new file mode 100644 index 0000000..528dee3 --- /dev/null +++ b/.github/workflows/mps_cli_rs_build.yaml @@ -0,0 +1,26 @@ +name: MPS-CLI-RS_CI + +on: + push: + branches: + - 'main' + pull_request: + workflow_dispatch: + +env: + GITHUB_TOKEN: ${{ secrets.MPSCLI_GITHUB_PKG_REGISTRY }} + +jobs: + build_mps_cli_rs: + runs-on: ubuntu-latest + env: + DISPLAY: ':99' + + steps: + - uses: actions/checkout@v3 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + - name: Run tests + working-directory: ./mps-cli-rs + run: cargo test \ No newline at end of file diff --git a/.gitignore b/.gitignore index f51ed76..297a4e9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ **/venv/ **/node_modules/ **/dist/ +/mps-cli-rs/target gradle.properties **/workspace.xml diff --git a/Readme.md b/Readme.md index 2db21f7..53ced36 100644 --- a/Readme.md +++ b/Readme.md @@ -11,7 +11,8 @@ This repository contains tooling for reading MPS models from command line - with ### Repository Structure - `mps-cli-gradle-plugin` - the gradle plugin to read MPS models - `mps-cli-py` - a Python library to read MPS models -- `mps-cli-ts` - a Typescript library to read MPS models +- `mps-cli-ts` - a Typescript library to read MPS models +- `mps-cli-ts` - a Rust library to read MPS models - `demos` - examples for the use of the MPS-CLI tooling - `gradle-plugin-use` - example of the use of the `mps-cli-gradle-plugin` - `jupyter-notebook` - example of the use of the `mps-cli-py` library in a Jupyter Notebook diff --git a/mps-cli-py/.idea/workspace.xml b/mps-cli-py/.idea/workspace.xml index 120ee78..9f35e92 100644 --- a/mps-cli-py/.idea/workspace.xml +++ b/mps-cli-py/.idea/workspace.xml @@ -1,5 +1,8 @@ + + @@ -50,6 +53,7 @@ + + + + + + - diff --git a/mps-cli-py/src/mpscli/demo.py b/mps-cli-py/src/mpscli/demo.py index 951ad73..3e64eb8 100644 --- a/mps-cli-py/src/mpscli/demo.py +++ b/mps-cli-py/src/mpscli/demo.py @@ -1,6 +1,7 @@ +import sys +sys.path.insert(1, '..') from model.builder.SSolutionsRepositoryBuilder import SSolutionsRepositoryBuilder builder = SSolutionsRepositoryBuilder() -#repo = builder.build('..\\..\\mps_test_projects\\mps_cli_lanuse_file_per_root') -repo = builder.build('..\\..\\..\\..\\E3_2.0_Solution\\solutions') \ No newline at end of file +repo = builder.build('..\\..\\mps_test_projects\\mps_cli_lanuse_file_per_root') \ No newline at end of file diff --git a/mps-cli-rs/.vscode/settings.json b/mps-cli-rs/.vscode/settings.json new file mode 100644 index 0000000..868d3ca --- /dev/null +++ b/mps-cli-rs/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + "./Cargo.toml", + "./Cargo.toml" + ] +} \ No newline at end of file diff --git a/mps-cli-rs/Cargo.lock b/mps-cli-rs/Cargo.lock new file mode 100644 index 0000000..44ef65a --- /dev/null +++ b/mps-cli-rs/Cargo.lock @@ -0,0 +1,67 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "mps-cli" +version = "0.1.0" +dependencies = [ + "roxmltree", + "walkdir", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/mps-cli-rs/Cargo.toml b/mps-cli-rs/Cargo.toml new file mode 100644 index 0000000..7c8fc2f --- /dev/null +++ b/mps-cli-rs/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "mps-cli" +version = "0.1.0" +edition = "2021" +license = "Eclipse Public License - v 2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +path = "src/lib.rs" + +[features] +default = [] +std = [] + +[dependencies] +walkdir = "2.5.0" +roxmltree = "0.20.0" + +[profile.release] +lto = "fat" +codegen-units = 1 \ No newline at end of file diff --git a/mps-cli-rs/src/builder/mod.rs b/mps-cli-rs/src/builder/mod.rs new file mode 100644 index 0000000..6699f3f --- /dev/null +++ b/mps-cli-rs/src/builder/mod.rs @@ -0,0 +1,7 @@ +pub(crate) mod smodules_repository_builder; +mod ssolution_builder; +mod smodel_builder_base; +mod smodel_builder_default_persistency; +mod smodel_builder_file_per_root_persistency; +mod slanguage_builder; +mod node_id_utils; diff --git a/mps-cli-rs/src/builder/node_id_utils.rs b/mps-cli-rs/src/builder/node_id_utils.rs new file mode 100644 index 0000000..b7cdfc5 --- /dev/null +++ b/mps-cli-rs/src/builder/node_id_utils.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +struct NodeIdEncodingUtils { + my_index_chars : String, + min_char : u8, + my_char_to_value : HashMap, +} + +impl NodeIdEncodingUtils { + pub(crate) fn new() -> Self { + let my_index_chars = String::from("0123456789abcdefghijklmnopqrstuvwxyz$_ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + let min_char = '$' as u8; + let mut my_char_to_value : HashMap = HashMap::new(); + let bytes = my_index_chars.as_bytes(); + for i in 0..my_index_chars.len() { + let char_value = bytes[i] as u8; + let ii: usize = (char_value - min_char) as usize; + my_char_to_value.insert(ii, i); + } + + NodeIdEncodingUtils { + my_index_chars : my_index_chars, + min_char : min_char, + my_char_to_value : my_char_to_value, + } + } + + pub(crate) fn decode(&self, uid_string : String) -> String { + let mut res = 0; + let bytes = uid_string.as_bytes(); + let mut c = bytes[0]; + let ii : usize = (c as u8 - self.min_char) as usize; + let mut value = self.my_char_to_value[&ii]; + res = value; + for idx in 1..uid_string.len() { + res = res << 6; + c = bytes[idx]; + value = self.my_index_chars.find(c as char).unwrap(); + res = res | value; + } + return res.to_string(); + } + +} + +#[cfg(test)] +mod tests { + use crate::builder::node_id_utils::NodeIdEncodingUtils; + + #[test] + fn test_conversion() { + let node_id_utils = NodeIdEncodingUtils::new(); + + assert_eq!("5731700211660045983", node_id_utils.decode(String::from("4Yb5JA31NUv"))); + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/builder/playground.rs b/mps-cli-rs/src/builder/playground.rs new file mode 100644 index 0000000..aad8786 --- /dev/null +++ b/mps-cli-rs/src/builder/playground.rs @@ -0,0 +1,20 @@ +use std::cell::RefCell; +use std::rc::Rc; + +struct Person { + name: String, +} + +struct Company { + employees: RefCell>>, +} + +impl Company { + fn search_employee(&self, name: &str) -> Option> { + self.employees + .borrow() + .iter() + .find(|employee| employee.name == name) + .map(|employee| Rc::clone(employee)) + } +} diff --git a/mps-cli-rs/src/builder/slanguage_builder.rs b/mps-cli-rs/src/builder/slanguage_builder.rs new file mode 100644 index 0000000..a47252d --- /dev/null +++ b/mps-cli-rs/src/builder/slanguage_builder.rs @@ -0,0 +1,57 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::model::sconcept::{SConcept, SContainmentLink, SProperty, SReferenceLink}; +use crate::model::slanguage::SLanguage; + + +pub(crate) struct SLanguageBuilder { + pub concept_id_to_concept : RefCell>>, +} + +impl<'a> SLanguageBuilder { + pub(crate) fn new() -> Self { + SLanguageBuilder { + concept_id_to_concept: RefCell::new(HashMap::new()), + } + } + + pub(crate) fn get_or_create_concept(&self, language: &mut SLanguage, concept_id: &str, concept_name: &str) -> Rc { + let mut concept_id_to_concept = self.concept_id_to_concept.borrow_mut(); + let concept = concept_id_to_concept.get(concept_id); + if let Some(c) = concept { + return Rc::clone(c); + } + + let concept = SConcept::new(concept_name.to_string(), concept_id.to_string()); + let rc = Rc::new(concept); + concept_id_to_concept.insert(concept_id.to_string(), rc.clone()); + language.concepts.push(rc.clone()); + rc + } + + + pub(crate) fn get_or_create_property(&'a self, concept: Rc, property_id: String, property_name: String) -> Rc { + let mut props = concept.properties.borrow_mut(); + let property = props.entry(property_id.clone()).or_insert(Rc::new(SProperty::new(property_name, property_id))); + Rc::clone(property) + } + + pub(crate) fn get_or_create_child(&'a self, concept: Rc, child_id: String, child_name: String) -> Rc { + let mut links = concept.containment_links.borrow_mut(); + let containment_link = links.entry(child_id.clone()).or_insert(Rc::new(SContainmentLink::new(child_name, child_id))); + Rc::clone(containment_link) + } + + pub(crate) fn get_or_create_reference(&'a self, concept: Rc, reference_id: String, reference_name: String) -> Rc { + let mut refs = concept.reference_links.borrow_mut(); + let reference_link = refs.entry(reference_id.clone()).or_insert(Rc::new(SReferenceLink::new(reference_name, reference_id))); + Rc::clone(reference_link) + } + +} + +pub(crate) fn get_or_build_language<'a>(language_id: &String, language_name: &String, language_id_to_slanguage: &'a mut HashMap) -> &'a mut SLanguage { + language_id_to_slanguage.entry(language_id.to_string()).or_insert_with(|| SLanguage::new(language_name.to_string(), language_id.to_string())) +} \ No newline at end of file diff --git a/mps-cli-rs/src/builder/smodel_builder_base.rs b/mps-cli-rs/src/builder/smodel_builder_base.rs new file mode 100644 index 0000000..a4c0ef2 --- /dev/null +++ b/mps-cli-rs/src/builder/smodel_builder_base.rs @@ -0,0 +1,243 @@ +use std::borrow::BorrowMut; +use std::collections::HashMap; +use std::{cell::RefCell, rc::Rc}; +use roxmltree::{Document, Node}; + +use crate::model::slanguage::SLanguage; +use crate::model::smodel::SModel; +use crate::model::sconcept::{SConcept, SContainmentLink, SProperty, SReferenceLink}; +use crate::model::snode::SNode; + +use super::slanguage_builder::{get_or_build_language, SLanguageBuilder}; + +pub(crate) struct SModelBuilderCache { + pub index_2_concept: HashMap>, + pub index_2_property: HashMap>, + pub index_2_containment_link: HashMap>, + pub index_2_reference_link: HashMap>, + pub index_2_imported_model_uuid: HashMap, + pub index_2_model : HashMap>>, +} + +impl SModelBuilderCache { + pub(crate) fn new() -> Self { + SModelBuilderCache { + index_2_concept : HashMap::new(), + index_2_property : HashMap::new(), + index_2_containment_link : HashMap::new(), + index_2_reference_link : HashMap::new(), + index_2_imported_model_uuid : HashMap::new(), + index_2_model : HashMap::new(), + } + } + + fn get_model(&mut self, name : String, uuid : String) -> Rc> { + if let Some(model) = self.index_2_model.get(&uuid) { + Rc::clone(&model) + } else { + let temp = Rc::new(RefCell::new(SModel::new(name.clone(), uuid.clone()))); + self.index_2_model.insert(uuid, temp.clone()); + temp.clone() + } + } +} + +pub(crate) fn do_extract_model_core_info(document: &Document, model_builder_cache: &mut SModelBuilderCache, path_to_model_file: String) -> Rc> { + let model_element = document.root_element(); + let uuid_and_name = model_element.attributes().find(|a| a.name() == "ref").unwrap().value().to_string(); + + let left_parens_pos = uuid_and_name.find('(').unwrap(); + let right_parens_pos = uuid_and_name.find(')').unwrap(); + let uuid = uuid_and_name[0..left_parens_pos].to_string(); + let name = uuid_and_name[left_parens_pos + 1..right_parens_pos].to_string(); + + let mut is_do_not_generate = false; + let model_attributes_elements = model_element.children().filter(|c| (c.tag_name().name().to_string() == "attribute")); + let do_not_generate_attribute = (model_attributes_elements.clone()).find(|a| a.attributes().find(|aa| aa.value() == "doNotGenerate").is_some()); + if let Some(do_not_generate_attribute) = do_not_generate_attribute { + let do_not_generate_str = do_not_generate_attribute.attributes().find(|aa| aa.name() == "value").unwrap().value(); + if do_not_generate_str == "true" { is_do_not_generate = true; } + } + + let my_model = model_builder_cache.get_model(name, uuid); + my_model.as_ref().borrow_mut().path_to_model_file = path_to_model_file; + my_model.as_ref().borrow_mut().is_do_not_generate = is_do_not_generate; + my_model.as_ref().borrow_mut().is_file_per_root_persistency = true; + + let imports = model_element.children().find(|c| c.tag_name().name() == "imports").unwrap(); + for import in imports.children() { + let tag_name = import.tag_name(); + if tag_name.name() == "import" { + let uuid_att = import.attributes().find(|a| a.name() == "ref").unwrap().value(); + + let uuid = uuid_att.to_string()[0..uuid_att.find('(').unwrap()].to_string(); + let name = uuid_att.to_string()[uuid_att.find('(').unwrap()+1..uuid_att.find(')').unwrap()].to_string(); + let imported_model = model_builder_cache.get_model(name, uuid); + my_model.as_ref().borrow_mut().imported_models.push(imported_model.clone()); + } + } + + my_model.clone() +} + +pub(crate) fn parse_imports(document: &Document, model_builder_cache : &mut SModelBuilderCache) { + let model_element = document.root_element(); + let imports_element = model_element.children().find(|c| c.tag_name().name() == "imports"); + match imports_element { + Some(imports) => { + for import in imports.children() { + let tag_name = import.tag_name(); + if tag_name.name() == "import" { + let index = import.attributes().find(|a| a.name() == "index").unwrap().value(); + let uuid = import.attributes().find(|a| a.name() == "ref").unwrap().value(); + + let uuid = uuid.to_string()[0..uuid.find('(').unwrap()].to_string(); + model_builder_cache.index_2_imported_model_uuid.insert(index.to_string(), uuid); + } + } + }, + _ => () + } +} + +pub(crate) fn parse_registry<'a>(document: &Document, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, model_builder_cache : &mut SModelBuilderCache) { + let model_element = document.root_element(); + let registry_element = model_element.children().find(|c| c.tag_name().name() == "registry"); + match registry_element { + Some(registry) => { + for language in registry.children() { + if language.tag_name().name() != "language" { continue; } + + let language_id = language.attributes().find(|a| a.name() == "id").unwrap().value(); + let language_name = language.attributes().find(|a| a.name() == "name").unwrap().value(); + + let lang = get_or_build_language(&language_id.to_string(), &language_name.to_string(), language_id_to_slanguage); + for concept in language.children() { + if concept.tag_name().name() != "concept" { continue; } + + let concept_id = concept.attributes().find(|a| a.name() == "id").unwrap().value(); + let concept_name = concept.attributes().find(|a| a.name() == "name").unwrap().value(); + let concept_index = concept.attributes().find(|a| a.name() == "index").unwrap().value(); + let conc = language_builder.get_or_create_concept(lang, concept_id, concept_name); + model_builder_cache.index_2_concept.borrow_mut().insert(concept_index.to_string(), Rc::clone(&conc)); + + for properties_links_references in concept.children() { + if properties_links_references.tag_name().name() == "" { continue; } + + let tag_name = properties_links_references.tag_name().name(); + let id = properties_links_references.attributes().find(|a| a.name() == "id").unwrap().value(); + let name = properties_links_references.attributes().find(|a| a.name() == "name").unwrap().value(); + let index = properties_links_references.attributes().find(|a| a.name() == "index").unwrap().value(); + + if tag_name == "property" { + let prop = language_builder.get_or_create_property(Rc::clone(&conc),id.to_string(), name.to_string()); + model_builder_cache.index_2_property.borrow_mut().insert(index.to_string(), Rc::clone(&prop)); + } else if tag_name == "child" { + let child_link = language_builder.get_or_create_child(Rc::clone(&conc),id.to_string(), name.to_string()); + model_builder_cache.index_2_containment_link.borrow_mut().insert(index.to_string(), Rc::clone(&child_link)); + } else if tag_name == "reference" { + let ref_link = language_builder.get_or_create_reference(Rc::clone(&conc),id.to_string(), name.to_string()); + model_builder_cache.index_2_reference_link.borrow_mut().insert(index.to_string(), Rc::clone(&ref_link)); + } + } + } + }; + }, + _ => () + } +} + +pub(crate) fn do_build_root_nodes(document: &Document, model_builder_cache: &mut SModelBuilderCache, language_id_to_slanguage: &mut HashMap, language_builder: &mut SLanguageBuilder, crt_model: &Rc>) -> Vec> { + parse_imports(document, model_builder_cache); + parse_registry(document, language_id_to_slanguage, language_builder, model_builder_cache); + + let mut res = Vec::new(); + let nodes = document.root_element().children().filter(|it| it.tag_name().name() == "node"); + for node in nodes { + let mut parent: Option> = None; + res.push(parse_node(&mut parent, &node, language_builder, model_builder_cache, crt_model)); + } + res +} + +fn parse_node<'a>(parent_node : &mut Option>, node: &Node, language_builder : &SLanguageBuilder, model_builder_cache : &mut SModelBuilderCache, crt_model : &'a Rc>) -> Rc { + let node_attrs = node.attributes(); + let concept_index = (node_attrs.clone()).into_iter().find(|a| a.name() == "concept").unwrap().value(); + let node_id = (node_attrs.clone()).into_iter().find(|a| a.name() == "id").unwrap().value(); + let role = (node_attrs.clone()).into_iter().find(|a| a.name() == "role"); + let role = if role.is_none() { None } else { Some(role.unwrap().value().to_string()) }; + + let index_2_concept = &model_builder_cache.index_2_concept; + + let my_concept = index_2_concept.get(concept_index).unwrap(); + let role_human_readable = match role.clone() { + Some(role_string) => { + let index_2_containment_link = &model_builder_cache.index_2_containment_link; + let r = index_2_containment_link.get(&role_string).unwrap(); + Some(String::from(&r.name)) + }, + None => None, + }; + let mut current_node = SNode::new(node_id.to_string(), Rc::clone(my_concept), role_human_readable); + + let properties = node.children().filter(|it| it.tag_name().name() == "property"); + for property in properties { + let role = property.attributes().find(|a| a.name() == "role").unwrap().value(); + let value = property.attributes().find(|a| a.name() == "value").unwrap().value(); + let index_2_properties = &model_builder_cache.index_2_property; + let prop = index_2_properties.get(role).unwrap(); + current_node.add_property(prop, value.to_string()); + }; + + let refs = node.children().filter(|it| it.tag_name().name() == "ref"); + for ref_ in refs { + let role = ref_.attributes().find(|a| a.name() == "role").unwrap().value(); + let to = ref_.attributes().find(|a| a.name() == "to"); + let to: &str = if let Some(t) = to { + t.value() + } else { + ref_.attributes().find(|a| a.name() == "node").unwrap().value() + }; + let resolve = if let Some(r) = ref_.attributes().find(|a| a.name() == "resolve") { + Some(String::from(r.value())) + } else { + None + }; + + let index_2_reference_links = &model_builder_cache.index_2_reference_link; + let reference_link = index_2_reference_links.get(&role.to_string()).unwrap(); + + let model_id : String; + let node_id : String; + if let Some(index) = to.find(":") { + let model_index = &to[0..index]; + let mm = &model_builder_cache.index_2_imported_model_uuid; + model_id = String::from(mm.get(model_index).unwrap()); + node_id = String::from(&to[index + 1..to.len()]); + } else { + model_id = String::from(crt_model.borrow().uuid.as_str()); + node_id = String::from(to); + }; + + current_node.add_reference(reference_link, model_id, node_id, resolve); + }; + + let current_node_rc : Rc; + + if let Some(parent) = parent_node { + let index_2_containment_link = &model_builder_cache.index_2_containment_link; + let cl = index_2_containment_link.get(&role.unwrap()); + current_node.set_parent(Rc::clone(parent)); + current_node_rc = Rc::new(current_node); + parent.borrow_mut().add_child(Rc::clone(cl.unwrap()), Rc::clone(¤t_node_rc)); + } else { + current_node_rc = Rc::new(current_node); + }; + + let nodes = node.children().filter(|it| it.tag_name().name() == "node"); + for node in nodes { + parse_node(&mut Some(Rc::clone(¤t_node_rc)), &node, language_builder, model_builder_cache, crt_model); + }; + + Rc::clone(¤t_node_rc) +} \ No newline at end of file diff --git a/mps-cli-rs/src/builder/smodel_builder_default_persistency.rs b/mps-cli-rs/src/builder/smodel_builder_default_persistency.rs new file mode 100644 index 0000000..a049635 --- /dev/null +++ b/mps-cli-rs/src/builder/smodel_builder_default_persistency.rs @@ -0,0 +1,27 @@ +use std::{cell::RefCell, collections::HashMap, io::Read, path::PathBuf, rc::Rc}; + +use crate::model::{slanguage::SLanguage, smodel::SModel}; +use super::{slanguage_builder::SLanguageBuilder, smodel_builder_base::{do_build_root_nodes, do_extract_model_core_info, SModelBuilderCache}}; + +pub(crate) fn build_model<'a>(mps_file: PathBuf, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, model_builder_cache : &mut SModelBuilderCache) -> Rc> { + let ext = mps_file.extension(); + if !ext.is_some_and(|e| e.eq_ignore_ascii_case("mps")) { + panic!("expected file with extension .mps but found '{}'", mps_file.to_str().unwrap()); + } + + let file = std::fs::File::open(mps_file.clone()); + if file.is_err() { + panic!("file not found '{}'", mps_file.to_str().unwrap()); + } + let mut s = String::new(); + let _ = file.unwrap().read_to_string(&mut s); + let parse_res = roxmltree::Document::parse(&s); + let document = parse_res.unwrap(); + + let model: Rc> = do_extract_model_core_info(&document, model_builder_cache, mps_file.to_str().unwrap().to_string()); + + let roots = do_build_root_nodes(&document, model_builder_cache, language_id_to_slanguage, language_builder, &model); + model.as_ref().borrow_mut().root_nodes.extend(roots); + + model +} \ No newline at end of file diff --git a/mps-cli-rs/src/builder/smodel_builder_file_per_root_persistency.rs b/mps-cli-rs/src/builder/smodel_builder_file_per_root_persistency.rs new file mode 100644 index 0000000..c5d44d0 --- /dev/null +++ b/mps-cli-rs/src/builder/smodel_builder_file_per_root_persistency.rs @@ -0,0 +1,101 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::io::Read; +use roxmltree::Document; +use std::rc::Rc; +use std::cell::RefCell; + +use walkdir::{DirEntry, WalkDir}; + +use crate::model::slanguage::SLanguage; +use crate::model::smodel::SModel; +use crate::model::snode::SNode; +use super::slanguage_builder::SLanguageBuilder; +use super::smodel_builder_base::{do_build_root_nodes, do_extract_model_core_info}; +use super::smodel_builder_base::SModelBuilderCache; + + +pub(crate) fn build_model<'a>(path_to_model: PathBuf, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, model_builder_cache : &mut SModelBuilderCache) -> Rc> { + let mut model_file = path_to_model.clone(); + model_file.push(".model"); + let model: Rc> = extract_model_core_info(model_file, model_builder_cache); + + let mpsr_file_walker = WalkDir::new(path_to_model).min_depth(1).max_depth(1); + let mpsr_files = mpsr_file_walker.into_iter().filter(|entry| { + if entry.is_ok() { + let dir_entry = entry.as_ref().unwrap(); + let extension = dir_entry.path().extension(); + return dir_entry.file_type().is_file() && extension.is_some_and(|e| e.eq_ignore_ascii_case("mpsr")); + } + return false; + }); + + let mut roots = vec!(); + for mpsr_file in mpsr_files.into_iter() { + let file = mpsr_file.unwrap(); + let r= build_root_node_from_file(file, language_id_to_slanguage, language_builder, model_builder_cache, &model); + roots.extend(r); + }; + model.as_ref().borrow_mut().root_nodes.extend(roots); + + return model; +} + +fn extract_model_core_info<'a>(path_to_model: PathBuf, model_builder_cache : &mut SModelBuilderCache) -> Rc> { + let path_to_model_file = path_to_model.to_str().unwrap().to_string(); + + let file = std::fs::File::open(path_to_model_file.clone()); + if file.is_err() { + panic!("file not found '{}'", path_to_model_file); + } + let mut s = String::new(); + let _ = file.unwrap().read_to_string(&mut s); + let parse_res = Document::parse(&s); + let document = parse_res.unwrap(); + + do_extract_model_core_info(&document, model_builder_cache, path_to_model_file) +} + + +fn build_root_node_from_file<'a>(dir_entry: DirEntry, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, model_builder_cache : &mut SModelBuilderCache, crt_model : &Rc>) -> Vec> { + let file = std::fs::File::open(dir_entry.path().as_os_str()); + + let mut s = String::new(); + let _ = file.unwrap().read_to_string(&mut s); + let parse_res = roxmltree::Document::parse(&s); + + let document = parse_res.unwrap(); + do_build_root_nodes(&document, model_builder_cache, language_id_to_slanguage, language_builder, crt_model) +} + + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::builder::smodel_builder_file_per_root_persistency::{SModelBuilderCache, extract_model_core_info}; + + #[test] + fn test_model_extract_core_info() { + // given + let path = "../mps_test_projects/mps_cli_lanuse_file_per_root/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/.model"; + let path_to_model_file = PathBuf::from(path); + + //when + let mut model_builder_cache = SModelBuilderCache::new(); + let temp = extract_model_core_info(path_to_model_file, &mut model_builder_cache); + let model = temp.borrow(); + + //assert + assert_eq!(model.name, "mps.cli.lanuse.library_top.library_top"); + assert_eq!(model.uuid, "r:a96b23f6-56db-490c-a218-d40d11be7f1e"); + assert_eq!(model.path_to_model_file, path); + assert_eq!(model.is_do_not_generate, true); + assert!(model.is_file_per_root_persistency); + assert_eq!(model.imported_models.len(), 1); + let import = model.imported_models.first().unwrap(); + assert_eq!(import.borrow_mut().name, "mps.cli.lanuse.library_top.authors_top"); + assert_eq!(import.borrow_mut().uuid, "r:ec5f093b-9d83-43a1-9b41-b5952da8b1ed"); + } + +} diff --git a/mps-cli-rs/src/builder/smodules_repository_builder.rs b/mps-cli-rs/src/builder/smodules_repository_builder.rs new file mode 100644 index 0000000..78b20ad --- /dev/null +++ b/mps-cli-rs/src/builder/smodules_repository_builder.rs @@ -0,0 +1,102 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::time::Instant; + +use walkdir::WalkDir; + +use crate::builder::slanguage_builder::SLanguageBuilder; +use crate::builder::smodel_builder_base::SModelBuilderCache; +use crate::builder::ssolution_builder::build_solution; +use crate::model::srepository::SRepository; +use crate::model::ssolution::SSolution; +use crate::model::slanguage::SLanguage; + +pub(crate) fn build_repo_from_directory<'a>(source_dir: String) -> SRepository { + let mut all_solutions : Vec = Vec::new(); + let mut language_builder = SLanguageBuilder::new(); + let mut language_id_to_slanguage : HashMap = HashMap::new(); + + build_solutions_from(source_dir, &mut language_id_to_slanguage, &mut language_builder, & mut all_solutions); + + let all_languages = language_id_to_slanguage.into_iter().map(|(_k, lan)| lan).collect(); + SRepository::new(all_solutions, all_languages) +} + +fn build_solutions_from<'a>(source_dir: String, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, solutions : &'a mut Vec) { + let now = Instant::now(); + collect_modules_from_sources(source_dir.clone(), language_id_to_slanguage, language_builder, solutions); + let elapsed = now.elapsed(); + println!("{} milli seconds for handling {}", elapsed.as_millis(), source_dir); +} + +fn collect_modules_from_sources<'a>(source_dir: String, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, solutions : &'a mut Vec) { + let mut model_builder_cache = SModelBuilderCache::new(); + + let msd_files = find_msd_files(&source_dir, 3); + msd_files.iter() + .for_each(|msd_file| { + let s = build_solution(msd_file, language_id_to_slanguage, language_builder, &mut model_builder_cache); + solutions.push(s); + }); +} + +fn find_msd_files(source_dir: &String, start_depth: usize) -> Vec { + let walk_dir: WalkDir = WalkDir::new(source_dir).max_depth(start_depth); + let mut msd_files = Vec::new(); + for entry in walk_dir.into_iter() { + let dir_entry = entry.unwrap(); + let path_buf = dir_entry.into_path(); + if let Some(ext) = path_buf.extension() { + if ext == "msd" { + msd_files.push(path_buf) + } + } + } + return msd_files; +} + + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::rc::Rc; + + use crate::builder::smodules_repository_builder::{build_repo_from_directory, find_msd_files}; + use crate::model::smodel::SModel; + + #[test] + fn test_find_msd_files() { + //given + let src_dir = "../mps_test_projects/mps_cli_lanuse_file_per_root/".to_string(); + //when + let msd_files = find_msd_files(&src_dir, 3); + + //then + assert_eq!(msd_files.len(), 2); + } + + #[test] + fn smoke_test_build_repo_from() { + //given + let src_dir = "../mps_test_projects/mps_cli_lanuse_file_per_root/".to_string(); + + use std::time::Instant; + let now = Instant::now(); + //when + let repository = build_repo_from_directory(src_dir); + + //then + let required_time = now.elapsed().as_millis(); + let models: Vec<&Rc>> = repository.solutions.iter().flat_map(|solution| &solution.models).collect(); + let do_not_gen_models: Vec<&&Rc>> = models.iter().filter(|&model| model.as_ref().borrow().is_do_not_generate).collect(); + let number_of_solutions = repository.solutions.len(); + println!("Found {} solutions with {} models (out of which {} are set to do not generate) in {} ms", number_of_solutions, models.len(), do_not_gen_models.len(), required_time); + assert_eq!(number_of_solutions, 2); + assert_eq!(models.len(), 3); + assert_eq!(do_not_gen_models.len(), 1); + + assert!(repository.find_solution_by_name("mps.cli.lanuse.library_top").is_some()); + + //assert!(repository.get_model_by_uuid("r:ec5f093b-9d83-43a1-9b41-b5952da8b1ed").is_some()); + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/builder/ssolution_builder.rs b/mps-cli-rs/src/builder/ssolution_builder.rs new file mode 100644 index 0000000..fd40143 --- /dev/null +++ b/mps-cli-rs/src/builder/ssolution_builder.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use std::time::Instant; + +use std::io::Read; +use walkdir::WalkDir; + +use crate::builder::smodel_builder_file_per_root_persistency; +use crate::builder::smodel_builder_default_persistency; +use crate::builder::slanguage_builder::SLanguageBuilder; +use crate::model::slanguage::SLanguage; +use crate::model::ssolution::SSolution; + +use super::smodel_builder_base::SModelBuilderCache; + +pub(crate) fn build_solution<'a>(path_buf_to_msd_file: &PathBuf, language_id_to_slanguage: &'a mut HashMap, language_builder : &mut SLanguageBuilder, model_builder_cache : &mut SModelBuilderCache) -> SSolution { + let now = Instant::now(); + + let path_to_msd_file = path_buf_to_msd_file.to_str().unwrap().to_string(); + let mut solution: SSolution = extract_solution_core_info(path_to_msd_file); + let solution_dir = path_buf_to_msd_file.parent().unwrap(); + let model_dir = solution_dir.to_path_buf().to_str().unwrap().to_string() + "/models"; + + let model_dir = WalkDir::new(model_dir).min_depth(1).max_depth(1); + let mut models = vec![]; + for model_entry in model_dir.into_iter() { + let path = model_entry.unwrap().into_path(); + let model = if path.is_dir() { + smodel_builder_file_per_root_persistency::build_model(path, language_id_to_slanguage, language_builder, model_builder_cache) + } else { + smodel_builder_default_persistency::build_model(path, language_id_to_slanguage, language_builder, model_builder_cache) + }; + models.push(model) + } + + solution.models.extend(models); + println!("Building from solution {} - {}ms", solution.name, now.elapsed().as_millis()); + + return solution; +} + +fn extract_solution_core_info<'a>(path_to_msd_file: String) -> SSolution { + let file = std::fs::File::open(path_to_msd_file.clone()); + let mut s = String::new(); + let _ = file.unwrap().read_to_string(&mut s); + let parse_res = roxmltree::Document::parse(&s); + let document = parse_res.unwrap(); + + let model_element = document.root_element(); + let name = model_element.attributes().find(|a| a.name() == "name").unwrap().value().to_string(); + let uuid = model_element.attributes().find(|a| a.name() == "uuid").unwrap().value().to_string(); + return SSolution::new(name, uuid, path_to_msd_file.clone()); +} + +#[cfg(test)] +mod tests { + use crate::builder::ssolution_builder::extract_solution_core_info; + + #[test] + fn test_extract_core_info() { + // given + let path_to_msd_file = "../mps_test_projects/mps_cli_lanuse_file_per_root/solutions/mps.cli.lanuse.library_top/mps.cli.lanuse.library_top.msd".to_string(); + + //when + let solution = extract_solution_core_info(path_to_msd_file); + + //assert + assert_eq!(solution.name, "mps.cli.lanuse.library_top"); + assert_eq!(solution.uuid, "f1017d72-b2a4-4f19-9b27-1327f37f5b09"); + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/lib.rs b/mps-cli-rs/src/lib.rs new file mode 100644 index 0000000..ec3ce1c --- /dev/null +++ b/mps-cli-rs/src/lib.rs @@ -0,0 +1,8 @@ +pub mod model; +mod builder; + +use crate::model::srepository::SRepository; + +pub fn build_repo_from_directory(source_dir: String) -> SRepository { + crate::builder::smodules_repository_builder::build_repo_from_directory(source_dir) +} \ No newline at end of file diff --git a/mps-cli-rs/src/main.rs b/mps-cli-rs/src/main.rs new file mode 100644 index 0000000..2eecbff --- /dev/null +++ b/mps-cli-rs/src/main.rs @@ -0,0 +1,41 @@ +mod model; +mod builder; + +use crate::builder::smodules_repository_builder::build_repo_from_directory; + +fn main() { + let repository = build_repo_from_directory(String::from("C:\\work\\E3_2.0_Solution\\solutions")); + println!("number of solutions: {}", repository.solutions.len()); +} + + +/* +use std::{path::PathBuf, time::Instant}; +use std::io::Read; +use roxmltree::{Document, Node, ParsingOptions}; +use std::rc::Rc; +use std::cell::RefCell; + +use walkdir::{DirEntry, WalkDir}; + +fn main() { + let repo_dir = WalkDir::new("C:\\work\\E3_2.0_Solution\\solutions").min_depth(1).max_depth(10); + + let initial_timestamp = Instant::now(); + let mpsr_files : Vec = repo_dir.into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.clone().into_path().is_file()) + .filter(|f| f.file_name().to_str().unwrap().ends_with(".mpsr")) + .collect(); + let files_collected_timestamp = Instant::now(); + println!("{} files collected in {}ms", mpsr_files.len(), files_collected_timestamp.duration_since(initial_timestamp).as_millis()); + + for file in mpsr_files.iter() { + let file = std::fs::File::open(file.path()); + let mut s = String::new(); + let _ = file.unwrap().read_to_string(&mut s); + let parse_res = roxmltree::Document::parse(&s); + }; + println!("{} files parsed in {}ms", mpsr_files.len(), Instant::now().duration_since(files_collected_timestamp).as_millis()); + +}*/ \ No newline at end of file diff --git a/mps-cli-rs/src/model/mod.rs b/mps-cli-rs/src/model/mod.rs new file mode 100644 index 0000000..e03e1b8 --- /dev/null +++ b/mps-cli-rs/src/model/mod.rs @@ -0,0 +1,7 @@ +pub mod sconcept; +pub mod slanguage; +pub mod smodel; +pub mod snode; +pub mod snoderef; +pub mod ssolution; +pub mod srepository; \ No newline at end of file diff --git a/mps-cli-rs/src/model/sconcept.rs b/mps-cli-rs/src/model/sconcept.rs new file mode 100644 index 0000000..a0891a6 --- /dev/null +++ b/mps-cli-rs/src/model/sconcept.rs @@ -0,0 +1,86 @@ +use std::collections::HashMap; + +use std::cell::RefCell; +use std::rc::Rc; + +#[derive(Debug, PartialEq)] +pub struct SConcept { + pub name: String, + pub id: String, + pub properties: RefCell>>, + pub containment_links: RefCell>>, + pub reference_links: RefCell>>, +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct SProperty { + pub(crate) name: String, + pub id: String, +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct SContainmentLink { + pub(crate) name: String, + id: String, +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct SReferenceLink { + pub(crate) name: String, + id: String, +} + +impl SConcept { + pub fn new(name: String, id: String) -> Self { + SConcept { + name: name.to_string(), + id, + properties: RefCell::new(HashMap::new()), + containment_links: RefCell::new(HashMap::new()), + reference_links: RefCell::new(HashMap::new()), + } + } + + #[allow(dead_code)] + pub fn print_concept_details(&self) { + let properties_string_vector: Vec = self.properties.borrow().iter().map(|prop| format!("{} {}", prop.0, prop.1.name)).collect(); + let properties_info = properties_string_vector.join(", "); + let children_info = self.containment_links.borrow().iter().map(|child| { format!("{} {}", child.0, child.1.name) }).collect::>().join(", "); + let reference_info = self.reference_links.borrow().iter().map(|reference| format!("{} {}", reference.0, reference.1.name)).collect::>().join(", "); + println!("concept {}\n\ + properties: \n\ + \t\t{}\n\ + children: \n\ + \t\t{}\n\ + references: \n\ + \t\t{}\n\ + <<<", self.name, properties_info, children_info, reference_info); + } +} + +impl SProperty { + pub fn new(name: String, id: String) -> Self { + SProperty { + name, + id, + } + } +} + +impl SContainmentLink { + pub fn new(name: String, id: String) -> Self { + SContainmentLink { + name, + id, + } + } +} + +impl SReferenceLink { + pub fn new(name: String, id: String) -> Self { + SReferenceLink { + name, + id, + } + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/model/slanguage.rs b/mps-cli-rs/src/model/slanguage.rs new file mode 100644 index 0000000..92cf695 --- /dev/null +++ b/mps-cli-rs/src/model/slanguage.rs @@ -0,0 +1,18 @@ +use crate::model::sconcept::SConcept; +use std::rc::Rc; + +pub struct SLanguage { + pub name: String, + pub id: String, + pub concepts: Vec>, +} + +impl SLanguage { + pub fn new(name: String, id: String) -> Self { + SLanguage { + name, + id, + concepts: vec![], + } + } +} diff --git a/mps-cli-rs/src/model/smodel.rs b/mps-cli-rs/src/model/smodel.rs new file mode 100644 index 0000000..77b10aa --- /dev/null +++ b/mps-cli-rs/src/model/smodel.rs @@ -0,0 +1,51 @@ +use crate::model::snode::SNode; +use std::{cell::RefCell, rc::Rc}; + +pub struct SModel { + pub name: String, + pub uuid: String, + pub root_nodes: Vec>, + pub path_to_model_file: String, + pub is_do_not_generate: bool, + pub is_file_per_root_persistency: bool, + pub imported_models: Vec>> +} + +impl SModel { + pub fn new(name: String, uuid: String) -> Self { + SModel { + name, + uuid, + root_nodes: vec![], + path_to_model_file : String::from(""), + imported_models : vec![], + is_do_not_generate : false, + is_file_per_root_persistency : false, + } + } + + pub fn get_nodes(&self) -> Vec> { + let mut nodes = Vec::new(); + for root in self.root_nodes.iter() { + nodes.extend(SNode::get_descendants(Rc::clone(root), true)); + } + return nodes; + } + + pub fn get_node_by_id(&self, id: &str) -> Option> { + let nodes = self.get_nodes(); + let n = nodes.iter().find(|node| node.id.eq(id)); + if let Some(nn) = n { return Some(Rc::clone(nn)); } + None + } + + pub fn find_root(&self, name : &str) -> Option> { + let root = self.root_nodes.iter().find(|r| if let Some(n) = r.get_property("name") { n == name } else { false }); + if let Some(rn) = root { + Some(Rc::clone(rn)) + } else { + None + } + } +} + diff --git a/mps-cli-rs/src/model/snode.rs b/mps-cli-rs/src/model/snode.rs new file mode 100644 index 0000000..e3d1293 --- /dev/null +++ b/mps-cli-rs/src/model/snode.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; + +use crate::model::sconcept::{SConcept, SContainmentLink, SProperty, SReferenceLink}; +use crate::model::snoderef::SNodeRef; +use std::rc::Rc; +use std::cell::RefCell; + +pub struct SNode { + pub id: String, + pub concept: Rc, + pub role_in_parent: Option, + properties: HashMap, String>, + children: RefCell, Vec>>>, + references: HashMap, Rc>, + pub parent: Option>, +} + +impl SNode { + pub fn new(id: String, concept: Rc, role_in_parent: Option) -> Self { + SNode { + id, + concept, + role_in_parent, + properties: HashMap::new(), + children: RefCell::new(HashMap::new()), + references: HashMap::new(), + parent: None, + } + } + + pub fn add_property(&mut self, property: &Rc, value: String) { + self.properties.insert(Rc::clone(property), value); + } + + pub fn get_property(&self, property_name: &str) -> Option { + let entry = self.properties.iter().find(|it| it.0.name.eq(property_name)); + return match entry { + Some(key_val) => { Some(String::clone(key_val.1)) } + None => None + } + } + + pub fn add_reference(&mut self, reference_link: &Rc, model_id : String, node_id : String, resolve : Option) { + self.references.insert(Rc::clone(reference_link), Rc::new(SNodeRef::new(model_id, node_id, resolve.unwrap_or("".to_string())))); + } + + pub fn get_reference(&self, ref_role_name: &str) -> Option> { + let entry = self.references.iter().find(|it| it.0.name.eq(ref_role_name)); + match entry { + Some(e) => Some(e.1.clone()), + None => None + } + } + + pub fn add_child(&self, cl: Rc, node: Rc) { + let mut children = self.children.borrow_mut(); + let vec = children.entry(Rc::clone(&cl)).or_insert(Vec::new()); + vec.push(Rc::clone(&node)); + } + + pub fn get_children(&self, child_role_name: &str) -> Vec> { + let children = self.children.borrow(); + let entry = children.iter().find(|it| it.0.name.eq(child_role_name)); + match entry { + Some(e) => e.1.clone(), + None => Vec::new() + } + } + + pub fn set_parent(&mut self, parent : Rc) { + self.parent = Some(parent); + } + + pub fn get_descendants(node : Rc, include_self: bool) -> Vec> { + let mut descendants: Vec> = Vec::new(); + if include_self { descendants.push(Rc::clone(&node)) } + Self::get_descendants_internal(node, &mut descendants); + return descendants; + } + + fn get_descendants_internal(node : Rc, descendants: &mut Vec>) { + let children = node.children.borrow(); + let vectors_of_children : Vec<&Rc> = children.values().flatten().collect(); + for child in vectors_of_children { + descendants.push(Rc::clone(child)); + Self::get_descendants_internal(Rc::clone(child), descendants); + } + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/model/snoderef.rs b/mps-cli-rs/src/model/snoderef.rs new file mode 100644 index 0000000..224aad6 --- /dev/null +++ b/mps-cli-rs/src/model/snoderef.rs @@ -0,0 +1,37 @@ +use crate::model::snode::SNode; +use crate::model::srepository::SRepository; +use std::rc::Rc; + +pub struct SNodeRef { + pub model_id: String, + pub node_id: String, + pub resolve_info: String, + pub referenced_node : Option>, +} + +impl SNodeRef { + pub fn new(model_id: String, node_id: String, resolve_info: String) -> Self { + SNodeRef { + model_id, + node_id, + resolve_info, + referenced_node : None, + } + } + + pub fn resolve(&self, repository: &SRepository) -> Option> { + if self.referenced_node.is_some() { + return self.referenced_node.clone(); + } + + return match repository.get_model_by_uuid(self.model_id.as_str()) { + None => None, + Some(model) => + if let Some(n) = model.borrow().get_node_by_id(self.node_id.as_str()) { + return Some(n); + } else { + return None; + } + }; + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/model/srepository.rs b/mps-cli-rs/src/model/srepository.rs new file mode 100644 index 0000000..cb1bc8e --- /dev/null +++ b/mps-cli-rs/src/model/srepository.rs @@ -0,0 +1,60 @@ +use crate::model::slanguage::SLanguage; +use crate::model::smodel::SModel; +use crate::model::ssolution::SSolution; +use std::rc::Rc; +use std::cell::RefCell; + +pub struct SRepository { + pub solutions: Vec, + pub languages: Vec, +} + +impl SRepository { + pub fn new(solutions: Vec, languages: Vec) -> Self { + SRepository { + solutions : solutions, + languages : languages, + } + } + + #[allow(dead_code)] + pub fn find_solution_by_name(&self, name: &str) -> Option<&SSolution> { + self.solutions.iter().find(|&ssolution| ssolution.name.eq(name)) + } + + #[allow(dead_code)] + pub fn find_model_by_name(&self, name: &str) -> Option>> { + for s in self.solutions.iter() { + for m in s.models.iter() { + if let Ok(model) = m.clone().try_borrow() { + println!("model {}", model.name); + if model.name == name { return Some(Rc::clone(m)); } + } + } + } + None + } + + #[allow(dead_code)] + pub fn get_all_models(&self) -> Vec>> { + let mut res : Vec>> = Vec::new(); + for s in self.solutions.iter() { + for m in s.models.iter() { + res.push(Rc::clone(m)); + } + } + res + } + + #[allow(dead_code)] + pub fn get_model_by_uuid(&self, uuid: &str) -> Option>> { + for sol in self.solutions.iter() { + for m in sol.models.iter() { + if (**m).borrow().uuid.eq(uuid) { + return Some(Rc::clone(m)); + } + } + } + None + } +} \ No newline at end of file diff --git a/mps-cli-rs/src/model/ssolution.rs b/mps-cli-rs/src/model/ssolution.rs new file mode 100644 index 0000000..663565c --- /dev/null +++ b/mps-cli-rs/src/model/ssolution.rs @@ -0,0 +1,29 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::model::smodel::SModel; + +pub struct SSolution { + pub name: String, + pub uuid: String, + pub path_to_module_file: String, + pub models: Vec>>, +} + +impl SSolution { + pub fn new(name: String, uuid: String, path_to_module_file: String) -> Self { + SSolution { + name, + uuid, + path_to_module_file, + models: vec![], + } + } + + pub fn find_model(&self, name : &str) -> Option>> { + let model = self.models.iter().find(|m| m.borrow().name == name); + if let Some(model) = model { + return Some(Rc::clone(model)); + } + None + } +} \ No newline at end of file diff --git a/mps-cli-rs/tests/default_persistency_tests.rs b/mps-cli-rs/tests/default_persistency_tests.rs new file mode 100644 index 0000000..598878d --- /dev/null +++ b/mps-cli-rs/tests/default_persistency_tests.rs @@ -0,0 +1,9 @@ +mod model_completeness_tests; +use mps_cli::build_repo_from_directory; + +#[test] +fn test_build_repository() { + let path = "../mps_test_projects/mps_cli_lanuse_default_persistency/"; + let repo = build_repo_from_directory(path.to_string()); + model_completeness_tests::check_model_completeness(&repo, "mps.cli.lanuse.library_top.default_persistency", "mps.cli.lanuse.library_second.default_persistency"); +} \ No newline at end of file diff --git a/mps-cli-rs/tests/file_per_root_persistency_tests.rs b/mps-cli-rs/tests/file_per_root_persistency_tests.rs new file mode 100644 index 0000000..9b97504 --- /dev/null +++ b/mps-cli-rs/tests/file_per_root_persistency_tests.rs @@ -0,0 +1,9 @@ +mod model_completeness_tests; +use mps_cli::build_repo_from_directory; + +#[test] +fn test_build_repository() { + let path = "../mps_test_projects/mps_cli_lanuse_file_per_root/"; + let repo = build_repo_from_directory(path.to_string()); + model_completeness_tests::check_model_completeness(&repo, "mps.cli.lanuse.library_top", "mps.cli.lanuse.library_second"); +} \ No newline at end of file diff --git a/mps-cli-rs/tests/model_completeness_tests.rs b/mps-cli-rs/tests/model_completeness_tests.rs new file mode 100644 index 0000000..86b26a3 --- /dev/null +++ b/mps-cli-rs/tests/model_completeness_tests.rs @@ -0,0 +1,59 @@ + +use std::{cell::Ref, rc::Rc}; +use mps_cli::model::{smodel::SModel, srepository::SRepository, snode::SNode}; + +#[cfg(test)] +pub (crate) fn check_model_completeness(repo : &SRepository, library_top_solution_name : &str, library_second_solution_name : &str) { + //library_first_solution + let library_first_solution = repo.find_solution_by_name(library_top_solution_name).unwrap(); + assert_eq!(library_first_solution.models.len(), 2); + assert!(library_first_solution.find_model((library_top_solution_name.to_owned() + ".authors_top").as_str()).is_some()); + assert!(library_first_solution.find_model((library_top_solution_name.to_owned() + ".library_top").as_str()).is_some()); + + let library_top_model = repo.find_model_by_name((library_top_solution_name.to_owned() + ".library_top").as_str()).unwrap(); + let library_top_model : Ref = library_top_model.try_borrow().ok().unwrap(); + assert_eq!(library_top_model.root_nodes.len(), 2); + let munich_library_root = library_top_model.root_nodes.iter().find(|r| r.get_property("name") == Some(String::from("munich_library"))); + assert!(munich_library_root.is_some()); + let munich_library_root = munich_library_root.unwrap(); + assert_eq!(SNode::get_descendants(Rc::clone(munich_library_root), true).len(), 8); + assert_eq!(munich_library_root.get_children("entities").len(), 4); + + assert_eq!(library_top_model.get_nodes().len(), 9); + assert_eq!(library_top_model.get_node_by_id("4Yb5JA31NUC").unwrap().get_property("name").unwrap(), "munich_library"); + + + let munich_library_entities = munich_library_root.get_children("entities"); + assert_eq!(munich_library_entities.len(), 4); + let tom_sawyer = munich_library_entities.first().unwrap(); + assert_eq!(tom_sawyer.get_property("name"), Some(String::from("Tom Sawyer"))); + assert_eq!(tom_sawyer.role_in_parent, Some(String::from("entities"))); + assert_eq!(tom_sawyer.get_property("publicationDate"), Some(String::from("1876"))); + assert_eq!(tom_sawyer.get_property("isbn"), Some(String::from("4323r2"))); + assert_eq!(tom_sawyer.get_property("available"), Some(String::from("true"))); + assert_eq!(tom_sawyer.concept.name, String::from("mps.cli.landefs.library.structure.Book")); + + let authors = tom_sawyer.get_children("authors"); + let mark_twain = authors.first().unwrap().get_reference("person").unwrap(); + assert_eq!(mark_twain.resolve_info, "Mark Twain"); + if library_top_solution_name.contains("default_persistency") { + assert_eq!(mark_twain.model_id, "r:ca00da79-915e-4bdb-9c30-11a341daf779"); + } else { + assert_eq!(mark_twain.model_id, "r:ec5f093b-9d83-43a1-9b41-b5952da8b1ed"); + } + assert_eq!(mark_twain.node_id, "4Yb5JA31NUv"); + assert!(mark_twain.resolve(repo).is_some()); + + // library_second_solution + let library_second_solution = repo.find_solution_by_name(library_second_solution_name).unwrap(); + assert_eq!(library_second_solution.models.len(), 1); + + // languages + let languages = &repo.languages; + assert_eq!(languages.len(), 3); + assert!(languages.iter().find(|l| l.name.eq("mps.cli.landefs.library")).is_some()); + let people_lan = languages.iter().find(|l| l.name.eq("mps.cli.landefs.people")); + assert!(people_lan.is_some()); + assert_eq!(people_lan.unwrap().id, "a7aaae55-aa5e-4a05-b2d0-013745658efa"); + assert!(languages.iter().find(|l| l.name.eq("jetbrains.mps.lang.core")).is_some()); +} \ No newline at end of file diff --git a/mps_test_projects/mps_cli_lanuse_default_persistency/solutions/mps.cli.lanuse.library_top.default_persistency/models/mps.cli.lanuse.library_top.default_persistency.library_top.mps b/mps_test_projects/mps_cli_lanuse_default_persistency/solutions/mps.cli.lanuse.library_top.default_persistency/models/mps.cli.lanuse.library_top.default_persistency.library_top.mps index f47c6c2..3717327 100644 --- a/mps_test_projects/mps_cli_lanuse_default_persistency/solutions/mps.cli.lanuse.library_top.default_persistency/models/mps.cli.lanuse.library_top.default_persistency.library_top.mps +++ b/mps_test_projects/mps_cli_lanuse_default_persistency/solutions/mps.cli.lanuse.library_top.default_persistency/models/mps.cli.lanuse.library_top.default_persistency.library_top.mps @@ -71,5 +71,8 @@ + + + diff --git a/mps_test_projects/mps_cli_lanuse_file_per_root/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/.model b/mps_test_projects/mps_cli_lanuse_file_per_root/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/.model index 5ecb8bf..4266157 100644 --- a/mps_test_projects/mps_cli_lanuse_file_per_root/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/.model +++ b/mps_test_projects/mps_cli_lanuse_file_per_root/solutions/mps.cli.lanuse.library_top/models/mps.cli.lanuse.library_top.library_top/.model @@ -2,6 +2,7 @@ +