diff --git a/Cargo.lock b/Cargo.lock index b9f5eb9..5a72652 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.11" @@ -95,6 +117,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "spedesunu" version = "0.1.0" @@ -102,6 +133,7 @@ dependencies = [ "colored", "serde", "serde_json", + "toml", "walkdir", ] @@ -116,6 +148,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.13" @@ -279,3 +345,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index bdb619e..d584837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" colored = "2.1.0" serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" +toml = "0.8.19" walkdir = "2.5.0" diff --git a/src/main.rs b/src/main.rs index d408b86..9fe5060 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ mod file_handler; mod project; -use project::{node_js::NodeProject, scan_project_deps, Project}; +use project::{rust::RustProject, scan_project_deps, Project}; fn main() { println!( @@ -9,5 +9,5 @@ fn main() { std::env::current_dir().unwrap().display() ); - scan_project_deps(NodeProject::default()); + scan_project_deps(RustProject::default()); } diff --git a/src/project/mod.rs b/src/project/mod.rs index 0e99085..8d0382f 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -1,12 +1,12 @@ use std::collections::HashSet; use colored::Colorize; -use node_js::read_deps_file; use walkdir::WalkDir; use crate::file_handler::{read_file_at_path, string_exists_in_multiline_text}; pub mod node_js; +pub mod rust; pub trait Project { /// Default contrustor for a Project @@ -23,10 +23,12 @@ pub trait Project { fn should_scan_file(&self, file_name: &str) -> bool; fn deps(&self) -> &Vec; + + fn read_deps_file() -> String; } pub fn scan_project_deps(mut project: T) { - let deps_count = project.parse_deps(&read_deps_file()); + let deps_count = project.parse_deps(&T::read_deps_file()); println!("{deps_count} packages found in current project."); let mut used_deps = HashSet::new(); diff --git a/src/project/node_js.rs b/src/project/node_js.rs index 40f325d..a08e1e5 100644 --- a/src/project/node_js.rs +++ b/src/project/node_js.rs @@ -23,10 +23,10 @@ pub struct NodeProject { } impl NodeProject { - fn new(allowed_extensions: Vec, excluded_files: Vec) -> Self { + fn new(allowed_extensions: Vec, excluded_paths: Vec) -> Self { Self { allowed_extensions, - excluded_paths: excluded_files, + excluded_paths, deps: vec![], } } @@ -55,6 +55,7 @@ impl Project for NodeProject { self.deps.len() } + // TODO should_scan_file functions are exactly the same for both projects fn should_scan_file(&self, file_path: &str) -> bool { if file_path == "." { return true; @@ -76,16 +77,16 @@ impl Project for NodeProject { fn deps(&self) -> &Vec { &self.deps } -} -pub fn read_deps_file() -> String { - let f = File::open(DEPS_FILE).unwrap_or_else(|_| { - panic!( - "No file \"{DEPS_FILE}\" in {}", - env::current_dir().unwrap().display() - ) - }); - read_file(f).unwrap_or_else(|_| panic!("Cannot read {DEPS_FILE} file.")) + fn read_deps_file() -> String { + let f = File::open(DEPS_FILE).unwrap_or_else(|_| { + panic!( + "No file \"{DEPS_FILE}\" in {}", + env::current_dir().unwrap().display() + ) + }); + read_file(f).unwrap_or_else(|_| panic!("Cannot read {DEPS_FILE} file.")) + } } fn is_used_in_package_scripts(parsed_file: &NodePackagesHandler, name: &str) -> bool { diff --git a/src/project/rust.rs b/src/project/rust.rs new file mode 100644 index 0000000..bfd859b --- /dev/null +++ b/src/project/rust.rs @@ -0,0 +1,137 @@ +use std::{env, fs::File}; + +use serde::Deserialize; +use toml::Table; + +use crate::file_handler::read_file; + +use super::Project; + +const DEPS_FILE: &str = "Cargo.toml"; + +#[derive(Deserialize)] +pub struct RustPackagesHandler { + dependencies: Table +} + +pub struct RustProject { + deps: Vec, + + allowed_extensions: Vec, + excluded_paths: Vec, +} + +impl RustProject { + fn new(allowed_extensions: Vec, excluded_paths: Vec) -> Self { + Self { + allowed_extensions, + excluded_paths, + deps: Vec::new(), + } + } +} + +impl Project for RustProject { + fn default() -> Self { + Self::new( + Vec::from(["rs".into()]), + // For now, excluding Cargo.toml file is not necessary but if in the future we need to + // include .toml files then we can't miss it. + Vec::from(["Cargo.toml".into()]), + ) + } + + fn parse_deps(&mut self, deps_file_content: &str) -> usize { + let packages_handler: RustPackagesHandler = toml::from_str(deps_file_content) + .unwrap_or_else(|e| panic!("Cannot parse {DEPS_FILE} file. {}", e.to_string())); + self.deps = get_deps_names(packages_handler); + self.deps.len() + } + + // TODO should_scan_file functions are exactly the same for both projects + fn should_scan_file(&self, file_path: &str) -> bool { + if file_path == "." { + return true; + } + + for excluded in self.excluded_paths.iter() { + if file_path.contains(excluded) { + return false; + } + } + for ext in self.allowed_extensions.iter() { + if file_path.ends_with(&format!(".{ext}")) { + return true; + } + } + false + } + + fn deps(&self) -> &Vec { + &self.deps + } + + // TODO Always same function, need to be refactored + fn read_deps_file() -> String { + let f = File::open(DEPS_FILE).unwrap_or_else(|_| { + panic!( + "No file \"{DEPS_FILE}\" in {}", + env::current_dir().unwrap().display() + ) + }); + read_file(f).unwrap_or_else(|_| panic!("Cannot read {DEPS_FILE} file.")) + } +} + +fn get_deps_names(parsed_file: RustPackagesHandler) -> Vec { + let mut names = Vec::from_iter(parsed_file + .dependencies + .iter() + .map(|(name, _version)| { + name.clone() + })); + names.sort(); + names +} + +#[cfg(test)] +mod project_rust_tests { + use super::*; + + #[test] + fn should_scan_file_works() { + let project = RustProject::default(); + assert_eq!(project.should_scan_file("foo.rs"), true); + assert_eq!(project.should_scan_file("foo.js"), false); + assert_eq!(project.should_scan_file("Cargo.toml"), false); + assert_eq!(project.should_scan_file("Cargo.lock"), false); + } + + #[test] + fn get_deps_names_works() { + let mut packages_handler = RustPackagesHandler{ + dependencies: Table::new(), + }; + packages_handler + .dependencies + .insert("foo".into(), "0.1.0".into()); + packages_handler + .dependencies + .insert("bar".into(), "0.1.0".into()); + + assert_eq!(get_deps_names(packages_handler), Vec::from(["bar", "foo"])); + } + + #[test] + fn parse_deps_works() { + let mut project = RustProject::default(); + + let file_content = "[dependencies] + foo = \"2.1.0\" + bar = { version = \"1.0.215\", features = [\"derive\"] }"; + + assert_eq!(project.parse_deps(file_content), 2); + assert_eq!(project.deps.len(), 2); + assert_eq!(project.deps, Vec::from(["bar", "foo"])); + } +}