From 7af033987173d7b78e93088f1873ad90fea289bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Sun, 10 Oct 2021 17:55:50 +1300 Subject: [PATCH] Detect project origins (if any) --- lib/examples/project-origins.rs | 18 +++++ lib/src/project.rs | 115 +++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 lib/examples/project-origins.rs diff --git a/lib/examples/project-origins.rs b/lib/examples/project-origins.rs new file mode 100644 index 0000000..ffbd187 --- /dev/null +++ b/lib/examples/project-origins.rs @@ -0,0 +1,18 @@ +use std::env::args; + +use miette::{IntoDiagnostic, Result}; +use watchexec::project::origins; + +// Run with: `cargo run --example project-origins [PATH]` +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let path = + dunce::canonicalize(args().nth(1).unwrap_or_else(|| ".".to_string())).into_diagnostic()?; + for origin in origins(&path).await { + println!("{}", origin.display()); + } + + Ok(()) +} diff --git a/lib/src/project.rs b/lib/src/project.rs index 482849e..8ca6a11 100644 --- a/lib/src/project.rs +++ b/lib/src/project.rs @@ -1,12 +1,90 @@ //! Detect project type and origin. use std::{ + fs::Metadata, io::Error, path::{Path, PathBuf}, }; -pub async fn origins(path: impl AsRef) -> Result, Error> { - todo!() +use futures::{future::ready as is_true, stream::FuturesUnordered, StreamExt}; +use tokio::fs::metadata; +use tracing::trace; + +pub async fn origins(path: impl AsRef) -> Vec { + let mut origins = Vec::new(); + + async fn check_origin(path: &Path) -> bool { + let dirtests: FuturesUnordered<_> = vec![ + dir_exists(path.join("_darcs")), + dir_exists(path.join(".bzr")), + dir_exists(path.join(".fossil-settings")), + dir_exists(path.join(".git")), + dir_exists(path.join(".github")), + dir_exists(path.join(".hg")), + ] + .into_iter() + .collect(); + + let filetests: FuturesUnordered<_> = vec![ + file_exists(path.join(".asf.yaml")), + file_exists(path.join(".bzrignore")), + file_exists(path.join(".codecov.yml")), + file_exists(path.join(".ctags")), + file_exists(path.join(".editorconfig")), + file_exists(path.join(".gitattributes")), + file_exists(path.join(".gitmodules")), + file_exists(path.join(".hgignore")), + file_exists(path.join(".hgtags")), + file_exists(path.join(".perltidyrc")), + file_exists(path.join(".travis.yml")), + file_exists(path.join("appveyor.yml")), + file_exists(path.join("build.gradle")), + file_exists(path.join("build.properties")), + file_exists(path.join("build.xml")), + file_exists(path.join("Cargo.toml")), + file_exists(path.join("cgmanifest.json")), + file_exists(path.join("CMakeLists.txt")), + file_exists(path.join("composer.json")), + file_exists(path.join("COPYING")), + file_exists(path.join("docker-compose.yml")), + file_exists(path.join("Dockerfile")), + file_exists(path.join("Gemfile")), + file_exists(path.join("LICENSE.txt")), + file_exists(path.join("LICENSE")), + file_exists(path.join("Makefile.am")), + file_exists(path.join("Makefile.pl")), + file_exists(path.join("Makefile.PL")), + file_exists(path.join("Makefile")), + file_exists(path.join("mix.exs")), + file_exists(path.join("moonshine-dependencies.xml")), + file_exists(path.join("package.json")), + file_exists(path.join("pom.xml")), + file_exists(path.join("project.clj")), + file_exists(path.join("README.md")), + file_exists(path.join("README")), + file_exists(path.join("requirements.txt")), + file_exists(path.join("v.mod")), + ] + .into_iter() + .collect(); + + dirtests.any(is_true).await || filetests.any(is_true).await + } + + let mut current = path.as_ref(); + if check_origin(path.as_ref()).await { + origins.push(current.to_owned()); + } + + while let Some(parent) = current.parent() { + current = parent; + if check_origin(current).await { + origins.push(current.to_owned()); + continue; + } + } + + origins } /// Returns all project types detected at this given origin. @@ -33,3 +111,36 @@ pub enum ProjectType { Pip, RubyGem, } + +#[inline] +async fn exists(path: &Path) -> Option { + metadata(path).await.ok() +} + +#[inline] +async fn file_exists(path: PathBuf) -> bool { + let res = exists(&path) + .await + .map(|meta| meta.is_file()) + .unwrap_or(false); + + if res { + trace!(?path, "file exists"); + } + + res +} + +#[inline] +async fn dir_exists(path: PathBuf) -> bool { + let res = exists(&path) + .await + .map(|meta| meta.is_dir()) + .unwrap_or(false); + + if res { + trace!(?path, "dir exists"); + } + + res +}