This commit is contained in:
Javier Feliz 2025-09-11 19:00:55 -04:00
parent 16e9c422a8
commit 9fcd0269a8
3 changed files with 137 additions and 52 deletions

View File

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf}; use crate::projects::framework_macro::FrameworkHeuristics;
pub enum Framework { pub enum Framework {
Laravel, Laravel,
@ -8,63 +8,29 @@ pub enum Framework {
Ansible, Ansible,
} }
fn has_file<P: AsRef<Path>>(project_path: P, file: P) -> bool { crate::frameworks! {
PathBuf::from(project_path.as_ref()).join(file).exists() Laravel {
} files: ["composer.json"],
json_checks: [("composer.json", "require.laravel/framework")],
fn read_json_config<P: AsRef<Path>>(project_path: P, file: P) -> Option<serde_json::Value> { },
let pb = PathBuf::from(project_path.as_ref()); Ansible {
if let Ok(text) = std::fs::read_to_string(pb.join(file)) { directories: ["playbooks"],
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) { custom: |project_path: &str| {
return Some(v); use crate::projects::framework_macro::has_file;
} has_file(project_path, "ansible.cfg") ||
} else { has_file(project_path, "playbook.yml") ||
return None; has_file(project_path, "site.yml") ||
} has_file(project_path, "inventory") ||
has_file(project_path, "hosts") ||
None has_file(project_path, "hosts.yml")
} },
},
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
}
} }
pub struct FrameworkDetector { pub struct FrameworkDetector {
heuristics: &'static [&'static dyn FrameworkHeuristics], heuristics: &'static [&'static dyn FrameworkHeuristics],
} }
static LARAVEL: Laravel = Laravel;
static HEURISTICS: &[&dyn FrameworkHeuristics] = &[&LARAVEL];
impl FrameworkDetector { impl FrameworkDetector {
pub fn new() -> FrameworkDetector { pub fn new() -> FrameworkDetector {
FrameworkDetector { FrameworkDetector {

View File

@ -0,0 +1,118 @@
use std::path::{Path, PathBuf};
pub fn has_file<P: AsRef<Path>>(project_path: P, file: P) -> bool {
PathBuf::from(project_path.as_ref()).join(file).exists()
}
pub fn has_directory<P: AsRef<Path>>(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<P: AsRef<Path>>(project_path: P, file: P) -> Option<serde_json::Value> {
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::<serde_json::Value>(&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 {},)*
];
};
}

View File

@ -1,4 +1,5 @@
pub mod framework_detector; pub mod framework_detector;
pub mod framework_macro;
pub mod type_scanner; pub mod type_scanner;
// TODO: Project type detection and icon // TODO: Project type detection and icon
use std::{ use std::{