From 9fcd0269a8e093ddc54a56043980235009166e18 Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Thu, 11 Sep 2025 19:00:55 -0400 Subject: [PATCH] WIP --- .../src/projects/framework_detector.rs | 70 +++-------- .../src/projects/framework_macro.rs | 118 ++++++++++++++++++ waycast-plugins/src/projects/mod.rs | 1 + 3 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 waycast-plugins/src/projects/framework_macro.rs diff --git a/waycast-plugins/src/projects/framework_detector.rs b/waycast-plugins/src/projects/framework_detector.rs index cad61d4..d2514b0 100644 --- a/waycast-plugins/src/projects/framework_detector.rs +++ b/waycast-plugins/src/projects/framework_detector.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use crate::projects::framework_macro::FrameworkHeuristics; pub enum Framework { Laravel, @@ -8,63 +8,29 @@ pub enum Framework { Ansible, } -fn has_file>(project_path: P, file: P) -> bool { - PathBuf::from(project_path.as_ref()).join(file).exists() -} - -fn read_json_config>(project_path: P, file: P) -> Option { - let pb = PathBuf::from(project_path.as_ref()); - if let Ok(text) = std::fs::read_to_string(pb.join(file)) { - if let Ok(v) = serde_json::from_str::(&text) { - return Some(v); - } - } else { - return None; - } - - None -} - -trait FrameworkHeuristics: Sync { - fn name(&self) -> &'static str; - fn matches(&self, project_path: &str) -> bool; -} - -struct Laravel; -impl FrameworkHeuristics for Laravel { - fn name(&self) -> &'static str { - "Laravel" - } - - fn matches(&self, project_path: &str) -> bool { - // Check for composer.json - if !has_file(project_path, "composer.json") { - return false; - } - - // If composer.json has "laravel/framework" - // we can say yes immediately - if let Some(cfg) = read_json_config(project_path, "composer.json") { - let requires_laravel = cfg - .get("require") - .and_then(|r| r.get("laravel/framework")) - .is_some(); - if requires_laravel { - return true; - } - } - - false - } +crate::frameworks! { + Laravel { + files: ["composer.json"], + json_checks: [("composer.json", "require.laravel/framework")], + }, + Ansible { + directories: ["playbooks"], + custom: |project_path: &str| { + use crate::projects::framework_macro::has_file; + has_file(project_path, "ansible.cfg") || + has_file(project_path, "playbook.yml") || + has_file(project_path, "site.yml") || + has_file(project_path, "inventory") || + has_file(project_path, "hosts") || + has_file(project_path, "hosts.yml") + }, + }, } pub struct FrameworkDetector { heuristics: &'static [&'static dyn FrameworkHeuristics], } -static LARAVEL: Laravel = Laravel; -static HEURISTICS: &[&dyn FrameworkHeuristics] = &[&LARAVEL]; - impl FrameworkDetector { pub fn new() -> FrameworkDetector { FrameworkDetector { diff --git a/waycast-plugins/src/projects/framework_macro.rs b/waycast-plugins/src/projects/framework_macro.rs new file mode 100644 index 0000000..fd73826 --- /dev/null +++ b/waycast-plugins/src/projects/framework_macro.rs @@ -0,0 +1,118 @@ +use std::path::{Path, PathBuf}; + +pub fn has_file>(project_path: P, file: P) -> bool { + PathBuf::from(project_path.as_ref()).join(file).exists() +} + +pub fn has_directory>(project_path: P, dir: P) -> bool { + let path = PathBuf::from(project_path.as_ref()).join(dir); + path.exists() && path.is_dir() +} + +pub fn read_json_config>(project_path: P, file: P) -> Option { + let pb = PathBuf::from(project_path.as_ref()); + if let Ok(text) = std::fs::read_to_string(pb.join(file)) { + if let Ok(v) = serde_json::from_str::(&text) { + return Some(v); + } + } + None +} + +pub fn check_json_path(json: &serde_json::Value, path: &str) -> bool { + let parts: Vec<&str> = path.split('.').collect(); + let mut current = json; + + for part in parts { + if let Some(next) = current.get(part) { + current = next; + } else { + return false; + } + } + + true +} + +pub trait FrameworkHeuristics: Sync { + fn name(&self) -> &'static str; + fn matches(&self, project_path: &str) -> bool; +} + +#[macro_export] +macro_rules! frameworks { + ( + $( + $name:ident { + $(files: [$($file:literal),* $(,)?],)? + $(directories: [$($dir:literal),* $(,)?],)? + $(json_checks: [$(($json_file:literal, $json_path:literal)),* $(,)?],)? + $(custom: $custom_fn:expr,)? + } + ),* $(,)? + ) => { + $( + struct $name; + impl $crate::projects::framework_macro::FrameworkHeuristics for $name { + fn name(&self) -> &'static str { + stringify!($name) + } + + fn matches(&self, project_path: &str) -> bool { + // Check required files first + $( + $( + if !$crate::projects::framework_macro::has_file(project_path, $file) { + return false; + } + )* + )? + + // Check directories - any match returns true + $( + $( + if $crate::projects::framework_macro::has_directory(project_path, $dir) { + return true; + } + )* + )? + + // Check JSON paths - any match returns true + $( + $( + if let Some(json) = $crate::projects::framework_macro::read_json_config(project_path, $json_file) { + if $crate::projects::framework_macro::check_json_path(&json, $json_path) { + return true; + } + } + )* + )? + + // Custom validation + $( + if ($custom_fn)(project_path) { + return true; + } + )? + + // If we have files specified but no other checks, files existing means match + #[allow(unreachable_code)] + { + $( + $( + let _ = $file; // Use the file variable to indicate files were specified + return true; + )* + )? + false + } + } + } + + )* + + static HEURISTICS: &[&dyn $crate::projects::framework_macro::FrameworkHeuristics] = &[ + $(&$name {},)* + ]; + }; +} \ No newline at end of file diff --git a/waycast-plugins/src/projects/mod.rs b/waycast-plugins/src/projects/mod.rs index b77e303..8acba09 100644 --- a/waycast-plugins/src/projects/mod.rs +++ b/waycast-plugins/src/projects/mod.rs @@ -1,4 +1,5 @@ pub mod framework_detector; +pub mod framework_macro; pub mod type_scanner; // TODO: Project type detection and icon use std::{