use std::path::{Path, PathBuf};
use crate::kind::KindId;
use crate::workspace::WorkspaceKindId;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "lowercase"))]
pub enum DetectorHit {
Project {
kind: KindId,
signals: Vec<String>,
},
Workspace {
kind: WorkspaceKindId,
members: Vec<PathBuf>,
signals: Vec<String>,
},
Both {
project_kind: KindId,
workspace_kind: WorkspaceKindId,
members: Vec<PathBuf>,
signals: Vec<String>,
},
}
impl DetectorHit {
pub fn project_kind(&self) -> Option<&KindId> {
match self {
Self::Project { kind, .. } | Self::Both { project_kind: kind, .. } => Some(kind),
Self::Workspace { .. } => None,
}
}
pub fn workspace_kind(&self) -> Option<&WorkspaceKindId> {
match self {
Self::Workspace { kind, .. } | Self::Both { workspace_kind: kind, .. } => Some(kind),
Self::Project { .. } => None,
}
}
pub fn members(&self) -> &[PathBuf] {
match self {
Self::Workspace { members, .. } | Self::Both { members, .. } => members,
Self::Project { .. } => &[],
}
}
pub fn signals(&self) -> &[String] {
match self {
Self::Project { signals, .. }
| Self::Workspace { signals, .. }
| Self::Both { signals, .. } => signals,
}
}
}
pub trait Detector: Send + Sync {
fn name(&self) -> &str;
fn detect(&self, dir: &Path) -> Option<DetectorHit>;
fn priority(&self) -> i32 {
0
}
fn declared_project_kind(&self) -> Option<KindId> {
None
}
fn declared_workspace_kind(&self) -> Option<WorkspaceKindId> {
None
}
}
pub struct DetectorRegistry {
detectors: Vec<Box<dyn Detector>>,
}
impl DetectorRegistry {
pub fn empty() -> Self {
Self { detectors: Vec::new() }
}
pub fn with_builtins() -> Self {
let mut r = Self::empty();
crate::builtin::register_all(&mut r);
r
}
pub fn add(&mut self, d: impl Detector + 'static) -> &mut Self {
self.detectors.push(Box::new(d));
self
}
pub fn remove(&mut self, name: &str) -> usize {
let before = self.detectors.len();
self.detectors.retain(|d| d.name() != name);
before - self.detectors.len()
}
pub fn names(&self) -> Vec<&str> {
self.detectors.iter().map(|d| d.name()).collect()
}
pub fn project_kinds(&self) -> Vec<KindId> {
let mut seen: std::collections::HashSet<KindId> = std::collections::HashSet::new();
let mut out = Vec::new();
for d in &self.detectors {
if let Some(k) = d.declared_project_kind() {
if seen.insert(k.clone()) {
out.push(k);
}
}
}
out
}
pub fn workspace_kinds(&self) -> Vec<WorkspaceKindId> {
let mut seen: std::collections::HashSet<WorkspaceKindId> =
std::collections::HashSet::new();
let mut out = Vec::new();
for d in &self.detectors {
if let Some(k) = d.declared_workspace_kind() {
if seen.insert(k.clone()) {
out.push(k);
}
}
}
out
}
pub fn detect(&self, dir: &Path) -> Vec<DetectorHit> {
if !dir.is_dir() {
return Vec::new();
}
let mut raw: Vec<(i32, DetectorHit)> = Vec::new();
for d in &self.detectors {
if let Some(h) = d.detect(dir) {
raw.push((d.priority(), h));
}
}
let mut best_project: Option<(i32, usize)> = None;
for (idx, (prio, hit)) in raw.iter().enumerate() {
if hit.project_kind().is_some() {
match best_project {
None => best_project = Some((*prio, idx)),
Some((p, _)) if *prio > p => best_project = Some((*prio, idx)),
_ => {}
}
}
}
let mut best_workspace: std::collections::HashMap<WorkspaceKindId, (i32, usize)> =
std::collections::HashMap::new();
for (idx, (prio, hit)) in raw.iter().enumerate() {
if let Some(wk) = hit.workspace_kind() {
match best_workspace.get(wk) {
None => {
best_workspace.insert(wk.clone(), (*prio, idx));
}
Some((p, _)) if prio > p => {
best_workspace.insert(wk.clone(), (*prio, idx));
}
_ => {}
}
}
}
let project_winner = best_project.map(|(_, idx)| idx);
let workspace_winners: std::collections::HashSet<usize> =
best_workspace.values().map(|(_, idx)| *idx).collect();
let mut out = Vec::new();
for (idx, (_, hit)) in raw.into_iter().enumerate() {
let project_wins_here = project_winner == Some(idx);
let workspace_wins_here = workspace_winners.contains(&idx);
match hit {
DetectorHit::Project { .. } if project_wins_here => out.push(hit),
DetectorHit::Workspace { .. } if workspace_wins_here => out.push(hit),
DetectorHit::Both {
project_kind,
workspace_kind,
members,
signals,
} => match (project_wins_here, workspace_wins_here) {
(true, true) => out.push(DetectorHit::Both {
project_kind,
workspace_kind,
members,
signals,
}),
(true, false) => out.push(DetectorHit::Project {
kind: project_kind,
signals,
}),
(false, true) => out.push(DetectorHit::Workspace {
kind: workspace_kind,
members,
signals,
}),
(false, false) => {}
},
_ => {}
}
}
out
}
}
impl Default for DetectorRegistry {
fn default() -> Self {
Self::with_builtins()
}
}