use crate::graph::Graph;
use crate::level::{AttributeSpec, Level, Thresholds};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::Path;
pub fn detect_with_marker(workspace: &Path, marker: &str) -> bool {
workspace.join(marker).exists()
}
pub type Options = BTreeMap<String, String>;
#[derive(Debug, Clone, Default)]
pub struct PluginInput {
pub ignore: Vec<String>,
pub ignore_tests: bool,
pub options: Options,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Preset {
pub id: String,
pub label: String,
pub title: String,
pub prompt: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub doc_url: Option<String>,
pub sort_metric: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub connections: Vec<String>,
}
pub trait LanguagePlugin {
fn name(&self) -> &str;
fn detect(&self, workspace: &Path, input: &PluginInput) -> bool;
fn levels(&self) -> Vec<Level>;
fn analyze(&self, workspace: &Path, level: &str, input: &PluginInput) -> Result<Graph>;
fn metrics(&self, _graph: &mut Graph) -> usize {
0
}
fn is_test_path(&self, _rel_path: &str) -> bool {
false
}
fn versions(&self, _workspace: &Path, _input: &PluginInput) -> Vec<(String, String)> {
Vec::new()
}
fn roots(&self, _workspace: &Path) -> Vec<(String, String)> {
Vec::new()
}
fn presets(&self, defaults: Vec<Preset>, _input: &PluginInput) -> Vec<Preset> {
defaults
}
fn metric_specs(
&self,
defaults: BTreeMap<String, AttributeSpec>,
) -> BTreeMap<String, AttributeSpec> {
defaults
}
fn thresholds(&self) -> BTreeMap<String, Thresholds> {
BTreeMap::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
struct Dummy;
impl LanguagePlugin for Dummy {
fn name(&self) -> &str {
"dummy"
}
fn detect(&self, _w: &Path, _i: &PluginInput) -> bool {
false
}
fn levels(&self) -> Vec<crate::level::Level> {
Vec::new()
}
fn analyze(&self, _w: &Path, _l: &str, _i: &PluginInput) -> Result<Graph> {
Ok(Graph {
nodes: Vec::new(),
edges: Vec::new(),
})
}
}
#[test]
fn trait_default_hooks_are_noops() {
let p = Dummy;
let ws = Path::new("/tmp");
let input = PluginInput::default();
assert_eq!(p.name(), "dummy");
assert!(!p.detect(ws, &input));
assert!(p.levels().is_empty());
let g = p.analyze(ws, "files", &input).expect("dummy analyze ok");
assert!(g.nodes.is_empty() && g.edges.is_empty());
assert!(!p.is_test_path("anything"), "default: nothing is a test");
assert!(p.versions(ws, &input).is_empty(), "default: no versions");
assert!(p.roots(ws).is_empty(), "default: no roots");
assert!(p.thresholds().is_empty(), "default: no thresholds");
assert!(p.presets(Vec::new(), &input).is_empty());
let specs: BTreeMap<String, AttributeSpec> = BTreeMap::new();
assert!(p.metric_specs(specs).is_empty());
let mut g = Graph {
nodes: Vec::new(),
edges: Vec::new(),
};
assert_eq!(p.metrics(&mut g), 0);
}
#[test]
fn detect_with_marker_checks_file_presence() {
let dir = std::env::temp_dir();
assert!(!detect_with_marker(&dir, "code-ranker-no-such-marker.xyz"));
}
}