diff --git a/src/analytics/dependencies.rs b/src/analytics/dependencies.rs new file mode 100644 index 0000000..ae2bfc0 --- /dev/null +++ b/src/analytics/dependencies.rs @@ -0,0 +1,58 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::{file::webx::WXModule, reporting::error::{exit_error, ERROR_CIRCULAR_DEPENDENCY}}; + + +type DependencyTree = HashMap>; + +fn detect_circular_dependencies(tree: &DependencyTree) -> Vec { + let mut circular_dependencies = Vec::new(); + for (_, dependents) in tree { + for dependent in dependents { + if tree.contains_key(dependent) { + circular_dependencies.push(dependent.clone()); + } + } + } + circular_dependencies +} + +/// Construct a dependency tree from a list of WebX files. +/// The tree is a hashmap where the keys are the dependencies and the values are the files that +/// depend on them. +/// If a circular dependency is detected, an error is returned. +/// +/// ## Arguments +/// - `files` - The list of WebX files. +/// +/// ## Returns +/// The dependency tree. +fn construct_dependency_tree(files: &Vec) -> DependencyTree { + let mut tree = DependencyTree::new(); + for file in files.iter() { + // Insert dependencies into the tree as keys and the file path as the value. + for dependency in file.scope.includes.iter() { + let dependency_target = file.path.join(dependency); + tree.entry(dependency_target) + .or_insert(Vec::new()) + .push(file.path.clone()); + } + } + tree +} + +/// Analyse the dependencies of a list of WebX modules. +/// If a circular dependency is detected, an error is reported and the program exits. +pub fn analyse_module_deps(modules: &Vec) { + let dependency_tree = construct_dependency_tree(modules); + let circular_dependencies = detect_circular_dependencies(&dependency_tree); + if !circular_dependencies.is_empty() { + exit_error( + format!( + "Circular dependencies detected:\n{:?}", + circular_dependencies + ), + ERROR_CIRCULAR_DEPENDENCY, + ); + } +} \ No newline at end of file diff --git a/src/analytics/mod.rs b/src/analytics/mod.rs new file mode 100644 index 0000000..0fbd273 --- /dev/null +++ b/src/analytics/mod.rs @@ -0,0 +1 @@ +pub mod dependencies; \ No newline at end of file diff --git a/src/engine/runner.rs b/src/engine/runner.rs index fed89f1..64817c3 100644 --- a/src/engine/runner.rs +++ b/src/engine/runner.rs @@ -1,11 +1,8 @@ use std::path::Path; -use crate::file::parser::parse_webx_file; -use crate::file::webx::WXFile; -use crate::project::{ - construct_dependency_tree, detect_circular_dependencies, load_project_config, locate_webx_files, -}; -use crate::reporting::error::{exit_error, ERROR_CIRCULAR_DEPENDENCY, ERROR_READ_WEBX_FILES}; +use crate::analytics::dependencies::analyse_module_deps; +use crate::file::webx::WXModule; +use crate::project::{load_modules, load_project_config}; const PROJECT_CONFIG_FILE_NAME: &str = "webx.config.json"; @@ -13,40 +10,14 @@ pub fn run(root: &Path, prod: bool) { let config_file = root.join(PROJECT_CONFIG_FILE_NAME); let config = load_project_config(&config_file); let source_root = root.join(&config.src); - let files = locate_webx_files(&source_root); - let webx_modules = files.iter().map(|f| parse_webx_file(f)).collect::>(); - let errors = webx_modules - .iter() - .filter(|m| m.is_err()) - .map(|m| m.as_ref().unwrap_err()) - .collect::>(); - if !errors.is_empty() { - exit_error( - format!("Failed to parse webx files:\n{:?}", errors), - ERROR_READ_WEBX_FILES, - ); - } - let webx_modules = webx_modules - .into_iter() - .map(|m| m.unwrap()) - .collect::>(); - let dependency_tree = construct_dependency_tree(&webx_modules); - let circular_dependencies = detect_circular_dependencies(&dependency_tree); - if !circular_dependencies.is_empty() { - exit_error( - format!( - "Circular dependencies detected:\n{:?}", - circular_dependencies - ), - ERROR_CIRCULAR_DEPENDENCY, - ); - } + let webx_modules = load_modules(&source_root); + analyse_module_deps(&webx_modules); println!( "Webx modules: {:?}", webx_modules .iter() - .map(WXFile::module_name) + .map(WXModule::module_name) .collect::>() .join(", ") ); diff --git a/src/file/parser.rs b/src/file/parser.rs index ff4d190..88058b5 100644 --- a/src/file/parser.rs +++ b/src/file/parser.rs @@ -1,5 +1,5 @@ use crate::{ - file::webx::WXFile, + file::webx::WXModule, reporting::error::{ exit_error, exit_error_expected_any_of_but_found, exit_error_expected_but_found, exit_error_unexpected, exit_error_unexpected_char, ERROR_PARSE_IO, ERROR_SYNTAX, @@ -750,15 +750,15 @@ impl<'a> WebXFileParser<'a> { Ok(scope) } - fn parse_module(&mut self) -> Result { - Ok(WXFile { + fn parse_module(&mut self) -> Result { + Ok(WXModule { path: self.file.clone(), - module_scope: self.parse_scope(true, WXROOT_PATH)?, + scope: self.parse_scope(true, WXROOT_PATH)?, }) } } -pub fn parse_webx_file(file: &PathBuf) -> Result { +pub fn parse_webx_file(file: &PathBuf) -> Result { let file_contents = std::fs::read_to_string(file).map_err(|e| e.to_string())?; let mut parser = WebXFileParser::new(file, &file_contents); Ok(parser.parse_module()?) diff --git a/src/main.rs b/src/main.rs index de6fde7..1fb8144 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod engine; mod project; +mod analytics; mod reporting; mod file; diff --git a/src/project.rs b/src/project.rs index 84fe84a..4914751 100644 --- a/src/project.rs +++ b/src/project.rs @@ -1,14 +1,13 @@ use serde::{Deserialize, Serialize}; use std::{ - collections::HashMap, fs, path::{Path, PathBuf}, }; use crate::{ - file::webx::WXFile, + file::{webx::WXModule, parser::parse_webx_file}, reporting::{ - error::{exit_error, ERROR_CIRCULAR_DEPENDENCY, ERROR_READ_WEBX_FILES}, + error::{exit_error, ERROR_READ_WEBX_FILES}, warning::warning, }, }; @@ -123,7 +122,7 @@ pub fn load_project_config(config_file: &PathBuf) -> ProjectConfig { /// /// ## Errors /// If the source directory does not exist, an error is returned. -pub fn locate_webx_files(src: &Path) -> Vec { +pub fn locate_files(src: &Path) -> Vec { let src = src.to_path_buf(); if !src.exists() { exit_error( @@ -137,7 +136,7 @@ pub fn locate_webx_files(src: &Path) -> Vec { let path = entry.unwrap().path(); if path.is_dir() { // Recursively find all .webx files in the directory. - files.append(&mut locate_webx_files(&path)); + files.append(&mut locate_files(&path)); } else if path.is_file() { files.push(path.canonicalize().map_err(|e| e.to_string()).expect("Failed to canonicalize path.")); } else { @@ -150,42 +149,32 @@ pub fn locate_webx_files(src: &Path) -> Vec { files } -type DependencyTree = HashMap>; - -pub fn detect_circular_dependencies(tree: &DependencyTree) -> Vec { - let mut circular_dependencies = Vec::new(); - for (_, dependents) in tree { - for dependent in dependents { - if tree.contains_key(dependent) { - circular_dependencies.push(dependent.clone()); - } - } - } - circular_dependencies -} - -/// Construct a dependency tree from a list of WebX files. -/// The tree is a hashmap where the keys are the dependencies and the values are the files that -/// depend on them. -/// If a circular dependency is detected, an error is returned. -/// -/// ## Arguments -/// - `files` - The list of WebX files. -/// -/// ## Returns -/// The dependency tree. -pub fn construct_dependency_tree(files: &Vec) -> DependencyTree { - let mut tree = DependencyTree::new(); - for file in files.iter() { - // Insert dependencies into the tree as keys and the file path as the value. - for dependency in file.module_scope.includes.iter() { - let dependency_target = file.path.join(dependency); - tree.entry(dependency_target) - .or_insert(Vec::new()) - .push(file.path.clone()); - } +/// Load all WebX modules from a given directory. +/// This function will recursively find all `.webx` files in the given directory, +/// parse them, and return a vector of the parsed modules. +/// If any of the files fail to parse, an error is reported and the program exits. +/// +/// ## Note +/// This function does not perform any static analysis on the modules +/// such as detecting circular dependencies. +pub fn load_modules(src: &Path) -> Vec { + let files = locate_files(src); + let webx_modules = files.iter().map(|f| parse_webx_file(f)).collect::>(); + let errors = webx_modules + .iter() + .filter(|m| m.is_err()) + .map(|m| m.as_ref().unwrap_err()) + .collect::>(); + if !errors.is_empty() { + exit_error( + format!("Failed to parse webx files:\n{:?}", errors), + ERROR_READ_WEBX_FILES, + ); } - tree + webx_modules + .into_iter() + .map(|m| m.unwrap()) + .collect::>() } /// Create a new WebX project in the given directory.