Skip to content

Commit

Permalink
Work on module structure and project analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
WilliamRagstad committed Aug 9, 2023
1 parent 1f1024e commit 943718a
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 18 deletions.
42 changes: 40 additions & 2 deletions src/engine/runner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
use std::path::Path;

pub fn run(dir: &Path, prod: bool) {
use crate::file::parser::parse_webx_file;
use crate::project::{locate_webx_files, load_project_config, construct_dependency_tree, detect_circular_dependencies};
use crate::reporting::error::{exit_error, ERROR_READ_WEBX_FILES, ERROR_CIRCULAR_DEPENDENCY};

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 = match locate_webx_files(&source_root) {
Ok(files) => files,
Err(err) => exit_error(format!("Failed to locate webx program files due to, {}", err), ERROR_READ_WEBX_FILES),
};

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.iter().map(|m| m.as_ref().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,
);
}

println!("Running web server in {} mode", if prod { "production" } else { "development" });
println!("Directory: {}", dir.display());
println!("Directory: {}", root.display());
}
2 changes: 2 additions & 0 deletions src/file/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod webx;
pub mod parser;
15 changes: 15 additions & 0 deletions src/file/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use std::{path::PathBuf, io::BufReader};
use crate::file::webx::WebXFile;

pub fn parse_webx_file(file: &PathBuf) -> Result<WebXFile, String> {
let module = WebXFile {
path: file.clone(),
includes: vec![],
scopes: vec![],
};
let file_contents = std::fs::read_to_string(file).map_err(|e| e.to_string())?;

let reader = BufReader::new(file_contents.as_bytes());

Ok(module)
}
90 changes: 90 additions & 0 deletions src/file/webx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::path::PathBuf;

/// # WebX file format
/// A module for working with WebX files.
#[derive(Debug)]
pub struct WebXFile {
/// The path to the file.
pub path: PathBuf,
/// The dependencies of the file.
pub includes: Vec<String>,
/// Scopes defined in the file.
/// Created by root and the `location` keyword.
pub scopes: Vec<WebXScope>,
}

#[derive(Debug)]
pub struct WebXScope {
/// Global TypeScript code block
pub global_ts: String,
/// ORM Model definitions
pub models: Vec<WebXModel>,
/// Handler functions
pub handlers: Vec<WebXHandler>,
/// Route endpoints
pub routes: Vec<WebXRoute>,
}

#[derive(Debug)]
pub struct WebXModel {
/// The name of the model.
pub name: String,
/// The fields of the model.
pub fields: String,
}

#[derive(Debug)]
pub struct WebXHandler {
/// The name of the handler.
pub name: String,
/// The parameters of the handler.
pub params: String,
/// Return type of the handler.
pub return_type: Option<String>,
/// The typescript body of the handler.
pub body: String,
}

#[derive(Debug)]
pub enum WebXRouteMethod {
GET,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
HEAD,
CONNECT,
TRACE,
ANY,
}

pub fn route_from_str(method: String) -> Result<WebXRouteMethod, String> {
match method.to_uppercase().as_str() {
"GET" => Ok(WebXRouteMethod::GET),
"POST" => Ok(WebXRouteMethod::POST),
"PUT" => Ok(WebXRouteMethod::PUT),
"PATCH" => Ok(WebXRouteMethod::PATCH),
"DELETE" => Ok(WebXRouteMethod::DELETE),
"OPTIONS" => Ok(WebXRouteMethod::OPTIONS),
"HEAD" => Ok(WebXRouteMethod::HEAD),
"CONNECT" => Ok(WebXRouteMethod::CONNECT),
"TRACE" => Ok(WebXRouteMethod::TRACE),
"ANY" => Ok(WebXRouteMethod::ANY),
_ => Err(format!("Invalid route method: {}", method)),
}
}

#[derive(Debug)]
pub struct WebXRoute {
/// HTTP method of the route.
pub method: WebXRouteMethod,
/// The path of the route.
pub path: String,
/// The pre-handler functions of the route.
pub pre_handlers: Vec<String>,
/// The code block of the route.
pub code: Option<String>,
/// The post-handler functions of the route.
pub post_handlers: Vec<String>,
}
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
mod engine;
mod project;
mod reporting;
mod file;

use clap::{Arg, Command, ArgAction};
use colored::*;
use reporting::error::{exit_error, error};
use reporting::error::error;

const VERSION: &str = env!("CARGO_PKG_VERSION");
const NAME: &str = "webx";
Expand Down
72 changes: 61 additions & 11 deletions src/project.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};

use crate::reporting::warning::warning;
use crate::{
file::webx::WebXFile,
reporting::{
error::{exit_error, ERROR_CIRCULAR_DEPENDENCY},
warning::warning,
},
};

/// The configuration for a WebX project.
///
Expand Down Expand Up @@ -93,15 +100,16 @@ pub struct CacheConfig {
}

/// Parse the project configuration from a given filepath.
///
///
/// ## Arguments
/// - `config` - The path to the project configuration file.
///
///
/// ## Returns
/// The project configuration.
pub fn load_project_config(config_file: &PathBuf) -> ProjectConfig {
let txt = fs::read_to_string(config_file).expect("Failed to open project configuration.");
let config: ProjectConfig = serde_json::from_str(&txt).expect("Failed to parse project configuration.");
let config: ProjectConfig =
serde_json::from_str(&txt).expect("Failed to parse project configuration.");
config
}

Expand All @@ -111,7 +119,7 @@ pub fn load_project_config(config_file: &PathBuf) -> ProjectConfig {
/// - `src` - The path to the source directory.
///
/// ## Returns
/// A vector of paths to all .webx files in the project's source directory.
/// A vector of canonical paths to all .webx files in the project's source directory.
///
/// ## Errors
/// If the source directory does not exist, an error is returned.
Expand All @@ -128,7 +136,7 @@ pub fn locate_webx_files(src: &Path) -> Result<Vec<PathBuf>, String> {
// Recursively find all .webx files in the directory.
files.append(&mut locate_webx_files(&path).unwrap());
} else if path.is_file() {
files.push(path);
files.push(path.canonicalize().map_err(|e| e.to_string())?);
} else {
panic!(
"The path '{}' is neither a file nor a directory.",
Expand All @@ -139,12 +147,50 @@ pub fn locate_webx_files(src: &Path) -> Result<Vec<PathBuf>, String> {
Ok(files)
}

/// Create a new WebX project in the given directory.
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<&WebXFile>) -> 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.includes.iter() {
let dependency_target = file.path.join(dependency);
tree.entry(dependency_target)
.or_insert(Vec::new())
.push(file.path.clone());
}
}
tree
}

/// Create a new WebX project in the given directory.
///
/// ## Arguments
/// - `root_dir` - The path to the root directory of the project.
/// - `override_existing` - Whether or not to override an existing project.
///
///
/// ## File Structure
/// The following files are added to the root directory:
/// ```text
Expand All @@ -156,7 +202,7 @@ pub fn locate_webx_files(src: &Path) -> Result<Vec<PathBuf>, String> {
/// The `webx.config.json` file contains the default configuration for the project.
/// The `webx/` directory contains all of the WebX source files.
/// The `index.webx` file contains some default example code.
///
///
/// ## Warning
/// If a `webx.config.json` file already exists in the root directory,
/// and `override_existing` is set to `false`, then a warning is printed and
Expand Down Expand Up @@ -243,5 +289,9 @@ location /todo {

fs::create_dir_all(&src_dir).expect("Failed to create source directory.");
fs::write(&index_file, DEFAULT_INDEX_FILE_CONTENTS).expect("Failed to create index file.");
fs::write(&config_file, serde_json::to_string_pretty(&default_config).unwrap()).expect("Failed to create config file.");
}
fs::write(
&config_file,
serde_json::to_string_pretty(&default_config).unwrap(),
)
.expect("Failed to create config file.");
}
5 changes: 1 addition & 4 deletions src/reporting/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use colored::*;
// Error codes:
pub static ERROR_READ_WEBX_FILES: i32 = 1;
pub static ERROR_READ_PROJECT_CONFIG: i32 = 2;
pub static ERROR_CIRCULAR_DEPENDENCY: i32 = 3;

fn error_generic(message: String, error_name: &str) {
eprintln!("{} {}", error_name.red(), message);
Expand All @@ -20,7 +21,3 @@ pub fn error(message: String) {
pub fn exit_error(message: String, code: i32) -> ! {
exit_error_generic(message, code, "Error");
}

pub fn exit_fatal(message: String) -> ! {
exit_error_generic(message, 1, "Fatal error");
}

0 comments on commit 943718a

Please sign in to comment.