use std::{
fmt::{Display, Formatter, Result},
path::PathBuf,
};
use serde::Serialize;
#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ProjectType {
Rust,
Node,
Python,
Go,
Java,
Cpp,
Swift,
DotNet,
Ruby,
Elixir,
Deno,
Php,
Haskell,
Dart,
Zig,
Scala,
}
#[derive(Clone, Debug, Serialize)]
pub struct BuildArtifacts {
pub path: PathBuf,
pub size: u64,
}
#[derive(Clone, Debug, Serialize)]
pub struct Project {
pub kind: ProjectType,
pub root_path: PathBuf,
pub build_arts: Vec<BuildArtifacts>,
pub name: Option<String>,
}
impl Project {
#[must_use]
pub const fn new(
kind: ProjectType,
root_path: PathBuf,
build_arts: Vec<BuildArtifacts>,
name: Option<String>,
) -> Self {
Self {
kind,
root_path,
build_arts,
name,
}
}
#[must_use]
pub fn total_size(&self) -> u64 {
self.build_arts.iter().map(|a| a.size).sum()
}
}
impl Display for Project {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let icon = match self.kind {
ProjectType::Rust => "[rs]",
ProjectType::Node => "[js]",
ProjectType::Python => "[py]",
ProjectType::Go => "[go]",
ProjectType::Java => "[java]",
ProjectType::Cpp => "[cpp]",
ProjectType::Swift => "[swift]",
ProjectType::DotNet => "[net]",
ProjectType::Ruby => "[rb]",
ProjectType::Elixir => "[ex]",
ProjectType::Deno => "[deno]",
ProjectType::Php => "[php]",
ProjectType::Haskell => "λ",
ProjectType::Dart => "[dart]",
ProjectType::Zig => "[zig]",
ProjectType::Scala => "[scala]",
};
if let Some(name) = &self.name {
write!(f, "{icon} {name} ({})", self.root_path.display())
} else {
write!(f, "{icon} {}", self.root_path.display())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn create_test_build_artifacts(path: &str, size: u64) -> BuildArtifacts {
BuildArtifacts {
path: PathBuf::from(path),
size,
}
}
fn create_test_project(
kind: ProjectType,
root_path: &str,
build_path: &str,
size: u64,
name: Option<String>,
) -> Project {
Project::new(
kind,
PathBuf::from(root_path),
vec![create_test_build_artifacts(build_path, size)],
name,
)
}
#[test]
fn test_project_type_equality() {
assert_eq!(ProjectType::Rust, ProjectType::Rust);
assert_eq!(ProjectType::Node, ProjectType::Node);
assert_eq!(ProjectType::Python, ProjectType::Python);
assert_eq!(ProjectType::Go, ProjectType::Go);
assert_eq!(ProjectType::Java, ProjectType::Java);
assert_eq!(ProjectType::Cpp, ProjectType::Cpp);
assert_eq!(ProjectType::Swift, ProjectType::Swift);
assert_eq!(ProjectType::DotNet, ProjectType::DotNet);
assert_eq!(ProjectType::Ruby, ProjectType::Ruby);
assert_eq!(ProjectType::Elixir, ProjectType::Elixir);
assert_eq!(ProjectType::Deno, ProjectType::Deno);
assert_eq!(ProjectType::Php, ProjectType::Php);
assert_eq!(ProjectType::Haskell, ProjectType::Haskell);
assert_eq!(ProjectType::Dart, ProjectType::Dart);
assert_eq!(ProjectType::Zig, ProjectType::Zig);
assert_eq!(ProjectType::Scala, ProjectType::Scala);
assert_ne!(ProjectType::Rust, ProjectType::Node);
assert_ne!(ProjectType::Node, ProjectType::Python);
assert_ne!(ProjectType::Python, ProjectType::Go);
assert_ne!(ProjectType::Go, ProjectType::Java);
assert_ne!(ProjectType::Java, ProjectType::Cpp);
assert_ne!(ProjectType::Cpp, ProjectType::Swift);
assert_ne!(ProjectType::Swift, ProjectType::DotNet);
assert_ne!(ProjectType::DotNet, ProjectType::Ruby);
assert_ne!(ProjectType::Ruby, ProjectType::Elixir);
assert_ne!(ProjectType::Elixir, ProjectType::Deno);
assert_ne!(ProjectType::Deno, ProjectType::Php);
assert_ne!(ProjectType::Php, ProjectType::Haskell);
assert_ne!(ProjectType::Haskell, ProjectType::Dart);
assert_ne!(ProjectType::Dart, ProjectType::Zig);
assert_ne!(ProjectType::Zig, ProjectType::Scala);
}
#[test]
fn test_build_artifacts_creation() {
let artifacts = create_test_build_artifacts("/path/to/target", 1024);
assert_eq!(artifacts.path, PathBuf::from("/path/to/target"));
assert_eq!(artifacts.size, 1024);
}
#[test]
fn test_project_new() {
let project = create_test_project(
ProjectType::Rust,
"/path/to/project",
"/path/to/project/target",
1024,
Some("test-project".to_string()),
);
assert_eq!(project.kind, ProjectType::Rust);
assert_eq!(project.root_path, PathBuf::from("/path/to/project"));
assert_eq!(
project.build_arts[0].path,
PathBuf::from("/path/to/project/target")
);
assert_eq!(project.build_arts[0].size, 1024);
assert_eq!(project.name, Some("test-project".to_string()));
}
#[test]
fn test_project_display_with_name() {
let rust_project = create_test_project(
ProjectType::Rust,
"/path/to/rust-project",
"/path/to/rust-project/target",
1024,
Some("my-rust-app".to_string()),
);
let expected = "[rs] my-rust-app (/path/to/rust-project)";
assert_eq!(format!("{rust_project}"), expected);
let node_project = create_test_project(
ProjectType::Node,
"/path/to/node-project",
"/path/to/node-project/node_modules",
2048,
Some("my-node-app".to_string()),
);
let expected = "[js] my-node-app (/path/to/node-project)";
assert_eq!(format!("{node_project}"), expected);
let python_project = create_test_project(
ProjectType::Python,
"/path/to/python-project",
"/path/to/python-project/__pycache__",
512,
Some("my-python-app".to_string()),
);
let expected = "[py] my-python-app (/path/to/python-project)";
assert_eq!(format!("{python_project}"), expected);
let go_project = create_test_project(
ProjectType::Go,
"/path/to/go-project",
"/path/to/go-project/vendor",
4096,
Some("my-go-app".to_string()),
);
let expected = "[go] my-go-app (/path/to/go-project)";
assert_eq!(format!("{go_project}"), expected);
let java_project = create_test_project(
ProjectType::Java,
"/path/to/java-project",
"/path/to/java-project/target",
8192,
Some("my-java-app".to_string()),
);
let expected = "[java] my-java-app (/path/to/java-project)";
assert_eq!(format!("{java_project}"), expected);
let cpp_project = create_test_project(
ProjectType::Cpp,
"/path/to/cpp-project",
"/path/to/cpp-project/build",
2048,
Some("my-cpp-app".to_string()),
);
let expected = "[cpp] my-cpp-app (/path/to/cpp-project)";
assert_eq!(format!("{cpp_project}"), expected);
let swift_project = create_test_project(
ProjectType::Swift,
"/path/to/swift-project",
"/path/to/swift-project/.build",
1024,
Some("my-swift-app".to_string()),
);
let expected = "[swift] my-swift-app (/path/to/swift-project)";
assert_eq!(format!("{swift_project}"), expected);
let dotnet_project = create_test_project(
ProjectType::DotNet,
"/path/to/dotnet-project",
"/path/to/dotnet-project/obj",
4096,
Some("my-dotnet-app".to_string()),
);
let expected = "[net] my-dotnet-app (/path/to/dotnet-project)";
assert_eq!(format!("{dotnet_project}"), expected);
let ruby_project = create_test_project(
ProjectType::Ruby,
"/path/to/ruby-project",
"/path/to/ruby-project/vendor/bundle",
2048,
Some("my-ruby-gem".to_string()),
);
let expected = "[rb] my-ruby-gem (/path/to/ruby-project)";
assert_eq!(format!("{ruby_project}"), expected);
let elixir_project = create_test_project(
ProjectType::Elixir,
"/path/to/elixir-project",
"/path/to/elixir-project/_build",
1024,
Some("my_elixir_app".to_string()),
);
let expected = "[ex] my_elixir_app (/path/to/elixir-project)";
assert_eq!(format!("{elixir_project}"), expected);
let deno_project = create_test_project(
ProjectType::Deno,
"/path/to/deno-project",
"/path/to/deno-project/vendor",
512,
Some("my-deno-app".to_string()),
);
let expected = "[deno] my-deno-app (/path/to/deno-project)";
assert_eq!(format!("{deno_project}"), expected);
}
#[test]
fn test_project_display_without_name() {
let rust_project = create_test_project(
ProjectType::Rust,
"/path/to/unnamed-project",
"/path/to/unnamed-project/target",
1024,
None,
);
let expected = "[rs] /path/to/unnamed-project";
assert_eq!(format!("{rust_project}"), expected);
let node_project = create_test_project(
ProjectType::Node,
"/some/other/path",
"/some/other/path/node_modules",
2048,
None,
);
let expected = "[js] /some/other/path";
assert_eq!(format!("{node_project}"), expected);
}
#[test]
fn test_project_clone() {
let original = create_test_project(
ProjectType::Rust,
"/original/path",
"/original/path/target",
1024,
Some("original-project".to_string()),
);
let cloned = original.clone();
assert_eq!(original.kind, cloned.kind);
assert_eq!(original.root_path, cloned.root_path);
assert_eq!(original.build_arts[0].path, cloned.build_arts[0].path);
assert_eq!(original.build_arts[0].size, cloned.build_arts[0].size);
assert_eq!(original.name, cloned.name);
}
#[test]
fn test_build_artifacts_clone() {
let original = create_test_build_artifacts("/test/path", 2048);
let cloned = original.clone();
assert_eq!(original.path, cloned.path);
assert_eq!(original.size, cloned.size);
}
#[test]
fn test_project_with_zero_size() {
let project = create_test_project(
ProjectType::Python,
"/empty/project",
"/empty/project/__pycache__",
0,
Some("empty-project".to_string()),
);
assert_eq!(project.total_size(), 0);
assert_eq!(format!("{project}"), "[py] empty-project (/empty/project)");
}
#[test]
fn test_project_with_large_size() {
let large_size = u64::MAX;
let project = create_test_project(
ProjectType::Go,
"/large/project",
"/large/project/vendor",
large_size,
Some("huge-project".to_string()),
);
assert_eq!(project.total_size(), large_size);
}
}