Skip to content

Commit

Permalink
Refactor deps analysis to own module
Browse files Browse the repository at this point in the history
  • Loading branch information
WilliamRagstad committed Sep 13, 2023
1 parent e70d044 commit e331003
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 80 deletions.
58 changes: 58 additions & 0 deletions src/analytics/dependencies.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf, Vec<PathBuf>>;

fn detect_circular_dependencies(tree: &DependencyTree) -> Vec<PathBuf> {
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<WXModule>) -> 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<WXModule>) {
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,
);
}
}
1 change: 1 addition & 0 deletions src/analytics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod dependencies;
41 changes: 6 additions & 35 deletions src/engine/runner.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,23 @@
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";

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::<Vec<_>>();
let errors = webx_modules
.iter()
.filter(|m| m.is_err())
.map(|m| m.as_ref().unwrap_err())
.collect::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>()
.join(", ")
);
Expand Down
10 changes: 5 additions & 5 deletions src/file/parser.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -750,15 +750,15 @@ impl<'a> WebXFileParser<'a> {
Ok(scope)
}

fn parse_module(&mut self) -> Result<WXFile, String> {
Ok(WXFile {
fn parse_module(&mut self) -> Result<WXModule, String> {
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<WXFile, String> {
pub fn parse_webx_file(file: &PathBuf) -> Result<WXModule, String> {
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()?)
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod engine;
mod project;
mod analytics;
mod reporting;
mod file;

Expand Down
69 changes: 29 additions & 40 deletions src/project.rs
Original file line number Diff line number Diff line change
@@ -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,
},
};
Expand Down Expand Up @@ -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<PathBuf> {
pub fn locate_files(src: &Path) -> Vec<PathBuf> {
let src = src.to_path_buf();
if !src.exists() {
exit_error(
Expand All @@ -137,7 +136,7 @@ pub fn locate_webx_files(src: &Path) -> Vec<PathBuf> {
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 {
Expand All @@ -150,42 +149,32 @@ pub fn locate_webx_files(src: &Path) -> Vec<PathBuf> {
files
}

type DependencyTree = HashMap<PathBuf, Vec<PathBuf>>;

pub fn detect_circular_dependencies(tree: &DependencyTree) -> Vec<PathBuf> {
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<WXFile>) -> 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<WXModule> {
let files = locate_files(src);
let webx_modules = files.iter().map(|f| parse_webx_file(f)).collect::<Vec<_>>();
let errors = webx_modules
.iter()
.filter(|m| m.is_err())
.map(|m| m.as_ref().unwrap_err())
.collect::<Vec<_>>();
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::<Vec<_>>()
}

/// Create a new WebX project in the given directory.
Expand Down

0 comments on commit e331003

Please sign in to comment.