From 5fd715e7c30420ced129a41a1765919b31528cc6 Mon Sep 17 00:00:00 2001 From: Tim Mulqueen Date: Sun, 12 May 2019 13:37:23 -0400 Subject: [PATCH] Implement directory scanner (#34) --- src/context.rs | 135 ++++++++++++++++++++++++++++++++++++++++++ src/modules/go.rs | 63 ++++++++------------ src/modules/nodejs.rs | 21 +++---- src/modules/python.rs | 32 ++++------ src/modules/rust.rs | 19 +++--- 5 files changed, 184 insertions(+), 86 deletions(-) diff --git a/src/context.rs b/src/context.rs index 035eec90..63ca28e7 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,7 @@ use clap::ArgMatches; use git2::Repository; use std::env; +use std::ffi::OsStr; use std::fs; use std::path::PathBuf; @@ -56,4 +57,138 @@ impl<'a> Context<'a> { } dir } + + // returns a new ScanDir struct with reference to current dir_files of context + // see ScanDir for methods + pub fn new_scan_dir(&'a self) -> ScanDir<'a> { + ScanDir { + dir_files: self.dir_files.as_ref(), + files: &[], + folders: &[], + extensions: &[], + } + } +} + +// A struct of Criteria which will be used to verify current PathBuf is +// of X language, criteria can be set via the builder pattern +pub struct ScanDir<'a> { + dir_files: &'a Vec, // Replace with reference + files: &'a [&'a str], + folders: &'a [&'a str], + extensions: &'a [&'a str], +} + +impl<'a> ScanDir<'a> { + pub fn set_files(mut self, files: &'a [&'a str]) -> Self { + self.files = files; + self + } + + pub fn set_extensions(mut self, extensions: &'a [&'a str]) -> Self { + self.extensions = extensions; + self + } + + pub fn set_folders(mut self, folders: &'a [&'a str]) -> Self { + self.folders = folders; + self + } + + /// based on the current Pathbuf check to see + /// if any of this criteria match or exist and returning a boolean + pub fn scan(&mut self) -> bool { + self.dir_files.iter().any(|path| { + path_has_name(&path, &self.folders) + || path_has_name(&path, &self.files) + || has_extension(&path, &self.extensions) + }) + } +} + +/// checks to see if the pathbuf matches a file or folder name +pub fn path_has_name<'a>(dir_entry: &PathBuf, names: &'a [&'a str]) -> bool { + let found_file_or_folder_name = names.into_iter().find(|file_or_folder_name| { + dir_entry + .file_name() + .and_then(OsStr::to_str) + .unwrap_or_default() + == **file_or_folder_name + }); + + match found_file_or_folder_name { + Some(name) => !name.is_empty(), + None => false, + } +} + +/// checks if pathbuf matches the extension provided +pub fn has_extension<'a>(dir_entry: &PathBuf, extensions: &'a [&'a str]) -> bool { + let found_ext = extensions.into_iter().find(|ext| { + dir_entry + .extension() + .and_then(OsStr::to_str) + .unwrap_or_default() + == **ext + }); + + match found_ext { + Some(extension) => !extension.is_empty(), + None => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_has_name() { + let mut buf = PathBuf::from("/"); + let files = vec!["package.json"]; + + assert_eq!(path_has_name(&buf, &files), false); + + buf.set_file_name("some-file.js"); + assert_eq!(path_has_name(&buf, &files), false); + + buf.set_file_name("package.json"); + assert_eq!(path_has_name(&buf, &files), true); + } + + #[test] + fn test_has_extension() { + let mut buf = PathBuf::from("/"); + let extensions = vec!["js"]; + + assert_eq!(has_extension(&buf, &extensions), false); + + buf.set_file_name("some-file.rs"); + assert_eq!(has_extension(&buf, &extensions), false); + + buf.set_file_name("some-file.js"); + assert_eq!(has_extension(&buf, &extensions), true) + } + + #[test] + fn test_criteria_scan() { + let mut failing_criteria = ScanDir { + dir_files: &vec![PathBuf::new()], + files: &["package.json"], + extensions: &["js"], + folders: &["node_modules"], + }; + + // fails if buffer does not match any criteria + assert_eq!(failing_criteria.scan(), false); + + let mut passing_criteria = ScanDir { + dir_files: &vec![PathBuf::from("package.json")], + files: &["package.json"], + extensions: &["js"], + folders: &["node_modules"], + }; + + assert_eq!(passing_criteria.scan(), true); + } } diff --git a/src/modules/go.rs b/src/modules/go.rs index f6b2f2c9..76728e38 100644 --- a/src/modules/go.rs +++ b/src/modules/go.rs @@ -4,59 +4,44 @@ use std::process::Command; use super::{Context, Module}; -/// Creates a segment with the current Python version +/// Creates a segment with the current Go version /// -/// Will display the Python version if any of the following criteria are met: -/// - Current directory contains a `.go` file +/// Will display the Go version if any of the following criteria are met: /// - Current directory contains a `go.mod` file /// - Current directory contains a `go.sum` file -/// - Current directory contains a `Godeps` directory /// - Current directory contains a `glide.yaml` file /// - Current directory contains a `Gopkg.yml` file /// - Current directory contains a `Gopkg.lock` file +/// - Current directory contains a `.go` file +/// - Current directory contains a `Godeps` directory pub fn segment(context: &Context) -> Option { - let is_go_project = context.dir_files.iter().any(has_go_files); + let is_go_project = context + .new_scan_dir() + .set_files(&["go.mod", "go.sum", "glide.yaml", "Gopkg.yml", "Gopkg.lock"]) + .set_extensions(&["go"]) + .set_folders(&["Godeps"]) + .scan(); + if !is_go_project { return None; } - const GO_CHAR: &str = "🐹 "; - let module_color = Color::Cyan.bold(); + match get_go_version() { + Some(go_version) => { + const GO_CHAR: &str = "🐹 "; + let module_color = Color::Cyan.bold(); - let mut module = Module::new("go"); - module.set_style(module_color); + let mut module = Module::new("go"); + module.set_style(module_color); - let go_version = get_go_version()?; - let formatted_version = format_go_version(go_version)?; - module.new_segment("symbol", GO_CHAR); - module.new_segment("version", formatted_version); + let formatted_version = format_go_version(go_version)?; + module.new_segment("symbol", GO_CHAR); + module.new_segment("version", formatted_version); - Some(module) -} - -fn has_go_files(dir_entry: &PathBuf) -> bool { - let is_go_mod = - |d: &PathBuf| -> bool { d.is_file() && d.file_name().unwrap_or_default() == "go.mod" }; - let is_go_sum = - |d: &PathBuf| -> bool { d.is_file() && d.file_name().unwrap_or_default() == "go.sum" }; - let is_godeps = - |d: &PathBuf| -> bool { d.is_dir() && d.file_name().unwrap_or_default() == "Godeps" }; - let is_glide_yaml = - |d: &PathBuf| -> bool { d.is_file() && d.file_name().unwrap_or_default() == "glide.yaml" }; - let is_go_file = - |d: &PathBuf| -> bool { d.is_file() && d.extension().unwrap_or_default() == "go" }; - let is_gopkg_yml = - |d: &PathBuf| -> bool { d.is_file() && d.file_name().unwrap_or_default() == "Gopkg.yml" }; - let is_gopkg_lock = - |d: &PathBuf| -> bool { d.is_file() && d.file_name().unwrap_or_default() == "Gopkg.lock" }; - - is_go_mod(&dir_entry) - || is_go_sum(&dir_entry) - || is_godeps(&dir_entry) - || is_glide_yaml(&dir_entry) - || is_go_file(&dir_entry) - || is_gopkg_yml(&dir_entry) - || is_gopkg_lock(&dir_entry) + Some(module) + } + None => None, + } } fn get_go_version() -> Option { diff --git a/src/modules/nodejs.rs b/src/modules/nodejs.rs index 6552c555..b9736687 100644 --- a/src/modules/nodejs.rs +++ b/src/modules/nodejs.rs @@ -1,5 +1,4 @@ use ansi_term::Color; -use std::path::PathBuf; use std::process::Command; use super::{Context, Module}; @@ -11,7 +10,13 @@ use super::{Context, Module}; /// - Current directory contains a `package.json` file /// - Current directory contains a `node_modules` directory pub fn segment(context: &Context) -> Option { - let is_js_project = context.dir_files.iter().any(has_js_files); + let is_js_project = context + .new_scan_dir() + .set_files(&["package.json"]) + .set_extensions(&["js"]) + .set_folders(&["node_modules"]) + .scan(); + if !is_js_project { return None; } @@ -34,18 +39,6 @@ pub fn segment(context: &Context) -> Option { } } -fn has_js_files(dir_entry: &PathBuf) -> bool { - let is_js_file = - |d: &PathBuf| -> bool { d.is_file() && d.extension().unwrap_or_default() == "js" }; - let is_node_modules = - |d: &PathBuf| -> bool { d.is_dir() && d.file_name().unwrap_or_default() == "node_modules" }; - let is_package_json = |d: &PathBuf| -> bool { - d.is_file() && d.file_name().unwrap_or_default() == "package.json" - }; - - is_js_file(&dir_entry) || is_node_modules(&dir_entry) || is_package_json(&dir_entry) -} - fn get_node_version() -> Option { match Command::new("node").arg("--version").output() { Ok(output) => Some(String::from_utf8(output.stdout).unwrap()), diff --git a/src/modules/python.rs b/src/modules/python.rs index 70625a47..fdab440e 100644 --- a/src/modules/python.rs +++ b/src/modules/python.rs @@ -1,5 +1,4 @@ use ansi_term::Color; -use std::path::PathBuf; use std::process::Command; use super::{Context, Module}; @@ -12,7 +11,17 @@ use super::{Context, Module}; /// - Current directory contains a `requirements.txt` file /// - Current directory contains a `pyproject.toml` file pub fn segment(context: &Context) -> Option { - let is_py_project = context.dir_files.iter().any(has_py_files); + let is_py_project = context + .new_scan_dir() + .set_files(&[ + "requirements.txt", + ".python-version", + "pyproject.toml", + "pyproject.toml", + ]) + .set_extensions(&["py"]) + .scan(); + if !is_py_project { return None; } @@ -35,25 +44,6 @@ pub fn segment(context: &Context) -> Option { } } -fn has_py_files(dir_entry: &PathBuf) -> bool { - let is_py_file = - |d: &PathBuf| -> bool { d.is_file() && d.extension().unwrap_or_default() == "py" }; - let is_python_version = |d: &PathBuf| -> bool { - d.is_file() && d.file_name().unwrap_or_default() == ".python-version" - }; - let is_requirements_txt = |d: &PathBuf| -> bool { - d.is_file() && d.file_name().unwrap_or_default() == "requirements.txt" - }; - let is_py_project = |d: &PathBuf| -> bool { - d.is_file() && d.file_name().unwrap_or_default() == "pyproject.toml" - }; - - is_py_file(&dir_entry) - || is_python_version(&dir_entry) - || is_requirements_txt(&dir_entry) - || is_py_project(&dir_entry) -} - fn get_python_version() -> Option { match Command::new("python").arg("--version").output() { Ok(output) => Some(String::from_utf8(output.stdout).unwrap()), diff --git a/src/modules/rust.rs b/src/modules/rust.rs index 1f0ae603..ab3679e7 100644 --- a/src/modules/rust.rs +++ b/src/modules/rust.rs @@ -1,5 +1,4 @@ use ansi_term::Color; -use std::path::PathBuf; use std::process::Command; use super::{Context, Module}; @@ -7,10 +6,15 @@ use super::{Context, Module}; /// Creates a segment with the current Rust version /// /// Will display the Rust version if any of the following criteria are met: -/// - Current directory contains a `.rs` file +/// - Current directory contains a file with a `.rs` extension /// - Current directory contains a `Cargo.toml` file pub fn segment(context: &Context) -> Option { - let is_rs_project = context.dir_files.iter().any(has_rs_files); + let is_rs_project = context + .new_scan_dir() + .set_files(&["Cargo.toml"]) + .set_extensions(&["rs"]) + .scan(); + if !is_rs_project { return None; } @@ -33,15 +37,6 @@ pub fn segment(context: &Context) -> Option { } } -fn has_rs_files(dir_entry: &PathBuf) -> bool { - let is_rs_file = - |d: &PathBuf| -> bool { d.is_file() && d.extension().unwrap_or_default() == "rs" }; - let is_cargo_toml = - |d: &PathBuf| -> bool { d.is_file() && d.file_name().unwrap_or_default() == "Cargo.toml" }; - - is_rs_file(&dir_entry) || is_cargo_toml(&dir_entry) -} - fn get_rust_version() -> Option { match Command::new("rustc").arg("-V").output() { Ok(output) => Some(String::from_utf8(output.stdout).unwrap()),