use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::lang::{LanguageAdapter, PythonAdapter, RustAdapter, TypeScriptAdapter};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct LanguageId(pub String);
impl LanguageId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into().to_ascii_lowercase())
}
}
impl std::fmt::Display for LanguageId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
pub struct CodingAdapterRegistry {
adapters: BTreeMap<LanguageId, Box<dyn LanguageAdapter>>,
}
impl std::fmt::Debug for CodingAdapterRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CodingAdapterRegistry")
.field("languages", &self.language_ids())
.finish()
}
}
impl CodingAdapterRegistry {
pub fn empty() -> Self {
Self {
adapters: BTreeMap::new(),
}
}
pub fn with_builtins() -> Self {
let mut registry = Self::empty();
let builtins: [Box<dyn LanguageAdapter>; 3] = [
Box::new(RustAdapter),
Box::new(PythonAdapter),
Box::new(TypeScriptAdapter),
];
for adapter in builtins {
registry.register(adapter.id(), adapter);
}
registry
}
pub fn register(&mut self, id: LanguageId, adapter: Box<dyn LanguageAdapter>) {
self.adapters.insert(id, adapter);
}
pub fn get(&self, id: &LanguageId) -> Option<&dyn LanguageAdapter> {
self.adapters.get(id).map(|a| a.as_ref())
}
pub fn language_ids(&self) -> Vec<LanguageId> {
self.adapters.keys().cloned().collect()
}
}
impl Default for CodingAdapterRegistry {
fn default() -> Self {
Self::with_builtins()
}
}
#[cfg(test)]
mod tests {
use super::*;
use perspt_sdk::{CorrectionDirection, IndependenceRoute, ResidualEvent, SensorRef};
struct GleamAdapter;
impl LanguageAdapter for GleamAdapter {
fn id(&self) -> LanguageId {
LanguageId::new("gleam")
}
fn diagnostic_sensor(&self) -> SensorRef {
SensorRef::new("gleam-check", IndependenceRoute::DeterministicTool)
}
fn parse_diagnostics(&self, _n: &str, _g: u32, _raw: &str) -> Vec<ResidualEvent> {
Vec::new()
}
fn correction_for(&self, _r: &ResidualEvent) -> Option<CorrectionDirection> {
None
}
}
#[test]
fn builtins_resolve_by_id() {
let registry = CodingAdapterRegistry::with_builtins();
for id in ["rust", "python", "typescript"] {
assert!(registry.get(&LanguageId::new(id)).is_some(), "{id}");
}
assert!(registry.get(&LanguageId::new("cobol")).is_none());
}
#[test]
fn a_new_language_registers_without_editing_the_orchestrator() {
let mut registry = CodingAdapterRegistry::with_builtins();
registry.register(LanguageId::new("gleam"), Box::new(GleamAdapter));
assert!(registry.get(&LanguageId::new("gleam")).is_some());
assert_eq!(registry.language_ids().len(), 4);
}
#[test]
fn ids_are_case_insensitive_by_construction() {
assert_eq!(LanguageId::new("Rust"), LanguageId::new("rust"));
}
}