pub mod bash;
pub mod c;
pub mod csharp;
pub mod dart;
pub mod documentation;
pub mod elixir;
pub mod go;
pub mod java;
pub mod json_validator;
pub mod kotlin;
pub mod php;
pub mod python;
pub mod r;
pub mod ruby;
pub mod rust;
pub mod swift;
pub mod toml_validator;
pub mod typescript;
pub mod yaml_validator;
pub mod zig;
use crate::snippets::error::Result;
use crate::snippets::types::{Language, Snippet, SnippetStatus, ValidationLevel};
use std::collections::HashMap;
use std::io::Read;
pub trait SnippetValidator: Send + Sync {
fn language(&self) -> Language;
fn is_available(&self) -> bool;
fn validate(
&self,
snippet: &Snippet,
level: ValidationLevel,
timeout_secs: u64,
) -> Result<(SnippetStatus, Option<String>)>;
fn max_level(&self) -> ValidationLevel;
fn is_dependency_error(&self, _error_output: &str) -> bool {
false
}
}
pub struct ValidatorRegistry {
validators: HashMap<Language, Box<dyn SnippetValidator>>,
}
impl ValidatorRegistry {
#[must_use]
pub fn new() -> Self {
let mut registry = Self {
validators: HashMap::new(),
};
registry.register(Box::new(rust::RustValidator));
registry.register(Box::new(python::PythonValidator));
registry.register(Box::new(typescript::TypeScriptValidator));
registry.register(Box::new(php::PhpValidator));
registry.register(Box::new(ruby::RubyValidator));
registry.register(Box::new(elixir::ElixirValidator));
registry.register(Box::new(bash::BashValidator));
registry.register(Box::new(toml_validator::TomlValidator));
registry.register(Box::new(c::CValidator));
registry.register(Box::new(csharp::CsharpValidator));
registry.register(Box::new(dart::DartValidator));
registry.register(Box::new(go::GoValidator));
registry.register(Box::new(java::JavaValidator));
registry.register(Box::new(kotlin::KotlinValidator));
registry.register(Box::new(swift::SwiftValidator));
registry.register(Box::new(zig::ZigValidator));
registry.register(Box::new(json_validator::JsonValidator));
registry.register(Box::new(yaml_validator::YamlValidator));
registry.register(Box::new(r::RValidator));
registry.register(Box::new(documentation::TextValidator));
registry.register(Box::new(documentation::MermaidValidator));
registry.register(Box::new(documentation::PowerShellValidator));
registry.register(Box::new(documentation::XmlValidator));
registry.register(Box::new(documentation::DockerValidator));
registry
}
fn register(&mut self, validator: Box<dyn SnippetValidator>) {
self.validators.insert(validator.language(), validator);
}
#[must_use]
pub fn get(&self, language: Language) -> Option<&dyn SnippetValidator> {
self.validators.get(&language).map(Box::as_ref)
}
}
impl Default for ValidatorRegistry {
fn default() -> Self {
Self::new()
}
}
fn strip_ansi_codes(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut chars = input.chars();
while let Some(ch) = chars.next() {
if ch == '\u{1b}' {
if matches!(chars.next(), Some('[')) {
for next in chars.by_ref() {
if next == 'm' {
break;
}
}
}
} else {
result.push(ch);
}
}
result
}
pub fn run_command(command: &mut std::process::Command, timeout_secs: u64) -> Result<(bool, String)> {
let mut child = command
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|err| crate::snippets::error::Error::Other(format!("spawn failed: {err}")))?;
let timeout = std::time::Duration::from_secs(timeout_secs);
match child.wait_timeout(timeout) {
Ok(Some(status)) => {
let mut output = String::new();
if let Some(mut stdout) = child.stdout.take() {
let _ = stdout.read_to_string(&mut output);
}
if let Some(mut stderr) = child.stderr.take() {
let _ = stderr.read_to_string(&mut output);
}
Ok((status.success(), strip_ansi_codes(&output)))
}
Ok(None) => {
let _ = child.kill();
let _ = child.wait();
Err(crate::snippets::error::Error::Timeout {
command: format!("{command:?}"),
timeout_secs,
})
}
Err(err) => Err(crate::snippets::error::Error::Other(format!("wait failed: {err}"))),
}
}
trait WaitTimeout {
fn wait_timeout(&mut self, timeout: std::time::Duration) -> std::io::Result<Option<std::process::ExitStatus>>;
}
impl WaitTimeout for std::process::Child {
fn wait_timeout(&mut self, timeout: std::time::Duration) -> std::io::Result<Option<std::process::ExitStatus>> {
let start = std::time::Instant::now();
let poll_interval = std::time::Duration::from_millis(50);
loop {
if let Some(status) = self.try_wait()? {
return Ok(Some(status));
}
if start.elapsed() >= timeout {
return Ok(None);
}
std::thread::sleep(poll_interval);
}
}
}