use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct InteractiveAPIReference {
pub traits: HashMap<String, DocumentedTrait>,
pub types: HashMap<String, DocumentedType>,
pub functions: HashMap<String, DocumentedFunction>,
pub search_index: SearchIndex,
pub config: APIReferenceConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct APIReferenceConfig {
pub enable_live_examples: bool,
pub enable_type_highlighting: bool,
pub enable_cross_references: bool,
pub syntax_theme: String,
}
impl Default for APIReferenceConfig {
fn default() -> Self {
Self {
enable_live_examples: true,
enable_type_highlighting: true,
enable_cross_references: true,
syntax_theme: "monokai".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentedTrait {
pub name: String,
pub full_path: String,
pub description: String,
pub associated_types: Vec<AssociatedType>,
pub required_methods: Vec<DocumentedMethod>,
pub provided_methods: Vec<DocumentedMethod>,
pub implementors: Vec<String>,
pub examples: Vec<InteractiveExample>,
pub related_traits: Vec<String>,
pub since: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssociatedType {
pub name: String,
pub bounds: Vec<String>,
pub description: String,
pub default: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentedMethod {
pub name: String,
pub signature: String,
pub description: String,
pub parameters: Vec<Parameter>,
pub return_type: String,
pub examples: Vec<String>,
pub safety: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Parameter {
pub name: String,
pub param_type: String,
pub description: String,
pub default: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentedType {
pub name: String,
pub full_path: String,
pub kind: TypeKind,
pub description: String,
pub fields: Vec<Field>,
pub variants: Vec<Variant>,
pub traits: Vec<String>,
pub methods: Vec<DocumentedMethod>,
pub examples: Vec<InteractiveExample>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypeKind {
Struct,
Enum,
Union,
TypeAlias,
Trait,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Field {
pub name: String,
pub field_type: String,
pub description: String,
pub visibility: Visibility,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Variant {
pub name: String,
pub fields: Vec<Field>,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
Public,
Crate,
Module,
Private,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentedFunction {
pub name: String,
pub full_path: String,
pub signature: String,
pub description: String,
pub parameters: Vec<Parameter>,
pub return_type: String,
pub examples: Vec<InteractiveExample>,
pub is_async: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InteractiveExample {
pub title: String,
pub code: String,
pub expected_output: Option<String>,
pub runnable: bool,
pub language: String,
}
#[derive(Debug, Clone)]
pub struct SearchIndex {
pub trait_index: HashMap<String, Vec<String>>,
pub type_index: HashMap<String, Vec<String>>,
pub function_index: HashMap<String, Vec<String>>,
}
impl InteractiveAPIReference {
pub fn new() -> Self {
Self {
traits: HashMap::new(),
types: HashMap::new(),
functions: HashMap::new(),
search_index: SearchIndex {
trait_index: HashMap::new(),
type_index: HashMap::new(),
function_index: HashMap::new(),
},
config: APIReferenceConfig::default(),
}
}
pub fn add_trait(&mut self, trait_doc: DocumentedTrait) {
self.index_trait(&trait_doc);
self.traits.insert(trait_doc.name.clone(), trait_doc);
}
pub fn add_type(&mut self, type_doc: DocumentedType) {
self.index_type(&type_doc);
self.types.insert(type_doc.name.clone(), type_doc);
}
pub fn add_function(&mut self, func_doc: DocumentedFunction) {
self.index_function(&func_doc);
self.functions.insert(func_doc.name.clone(), func_doc);
}
pub fn search(&self, query: &str) -> SearchResults {
let query_lower = query.to_lowercase();
let mut results = SearchResults {
traits: Vec::new(),
types: Vec::new(),
functions: Vec::new(),
};
for (name, trait_doc) in &self.traits {
if name.to_lowercase().contains(&query_lower)
|| trait_doc.description.to_lowercase().contains(&query_lower)
{
results.traits.push(trait_doc.clone());
}
}
for (name, type_doc) in &self.types {
if name.to_lowercase().contains(&query_lower)
|| type_doc.description.to_lowercase().contains(&query_lower)
{
results.types.push(type_doc.clone());
}
}
for (name, func_doc) in &self.functions {
if name.to_lowercase().contains(&query_lower)
|| func_doc.description.to_lowercase().contains(&query_lower)
{
results.functions.push(func_doc.clone());
}
}
results
}
pub fn generate_html(&self) -> String {
let mut html = String::from("<!DOCTYPE html>\n<html>\n<head>\n");
html.push_str("<title>sklears API Reference</title>\n");
html.push_str("<style>\n");
html.push_str(self.generate_css());
html.push_str("</style>\n");
html.push_str("</head>\n<body>\n");
html.push_str("<h1>sklears API Reference</h1>\n");
html.push_str("<div class='search-box'>\n");
html.push_str("<input type='text' id='search' placeholder='Search API...'>\n");
html.push_str("</div>\n");
html.push_str("<nav>\n");
html.push_str("<h2>Categories</h2>\n");
html.push_str("<ul>\n");
html.push_str("<li><a href='#traits'>Traits</a></li>\n");
html.push_str("<li><a href='#types'>Types</a></li>\n");
html.push_str("<li><a href='#functions'>Functions</a></li>\n");
html.push_str("</ul>\n");
html.push_str("</nav>\n");
html.push_str("<main>\n");
html.push_str("<section id='traits'>\n");
html.push_str("<h2>Traits</h2>\n");
for trait_doc in self.traits.values() {
html.push_str(&self.generate_trait_html(trait_doc));
}
html.push_str("</section>\n");
html.push_str("<section id='types'>\n");
html.push_str("<h2>Types</h2>\n");
for type_doc in self.types.values() {
html.push_str(&self.generate_type_html(type_doc));
}
html.push_str("</section>\n");
html.push_str("</main>\n");
html.push_str("</body>\n</html>");
html
}
fn generate_css(&self) -> &str {
r#"
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #333;
border-bottom: 3px solid #007acc;
padding-bottom: 10px;
}
.search-box {
margin: 20px 0;
}
#search {
width: 100%;
padding: 10px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 5px;
}
nav {
background: white;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
nav ul {
list-style: none;
padding: 0;
}
nav li {
margin: 10px 0;
}
nav a {
color: #007acc;
text-decoration: none;
font-weight: bold;
}
section {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 5px;
}
.trait, .type, .function {
border-left: 4px solid #007acc;
padding-left: 20px;
margin: 20px 0;
}
code {
background: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
}
pre {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
}
"#
}
fn generate_trait_html(&self, trait_doc: &DocumentedTrait) -> String {
let mut html = String::new();
html.push_str("<div class='trait'>\n");
html.push_str(&format!("<h3>{}</h3>\n", trait_doc.name));
html.push_str(&format!("<p>{}</p>\n", trait_doc.description));
if !trait_doc.required_methods.is_empty() {
html.push_str("<h4>Required Methods</h4>\n");
html.push_str("<ul>\n");
for method in &trait_doc.required_methods {
html.push_str(&format!(
"<li><code>{}</code> - {}</li>\n",
method.signature, method.description
));
}
html.push_str("</ul>\n");
}
html.push_str("</div>\n");
html
}
fn generate_type_html(&self, type_doc: &DocumentedType) -> String {
let mut html = String::new();
html.push_str("<div class='type'>\n");
html.push_str(&format!("<h3>{}</h3>\n", type_doc.name));
html.push_str(&format!("<p>{}</p>\n", type_doc.description));
html.push_str("</div>\n");
html
}
fn index_trait(&mut self, trait_doc: &DocumentedTrait) {
let keywords: Vec<String> = trait_doc
.name
.split('_')
.map(|s| s.to_lowercase())
.collect();
for keyword in keywords {
self.search_index
.trait_index
.entry(keyword)
.or_default()
.push(trait_doc.name.clone());
}
}
fn index_type(&mut self, type_doc: &DocumentedType) {
let keywords: Vec<String> = type_doc.name.split('_').map(|s| s.to_lowercase()).collect();
for keyword in keywords {
self.search_index
.type_index
.entry(keyword)
.or_default()
.push(type_doc.name.clone());
}
}
fn index_function(&mut self, func_doc: &DocumentedFunction) {
let keywords: Vec<String> = func_doc.name.split('_').map(|s| s.to_lowercase()).collect();
for keyword in keywords {
self.search_index
.function_index
.entry(keyword)
.or_default()
.push(func_doc.name.clone());
}
}
}
impl Default for InteractiveAPIReference {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResults {
pub traits: Vec<DocumentedTrait>,
pub types: Vec<DocumentedType>,
pub functions: Vec<DocumentedFunction>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_reference_creation() {
let api_ref = InteractiveAPIReference::new();
assert_eq!(api_ref.traits.len(), 0);
assert_eq!(api_ref.types.len(), 0);
}
#[test]
fn test_add_trait() {
let mut api_ref = InteractiveAPIReference::new();
let trait_doc = DocumentedTrait {
name: "Estimator".to_string(),
full_path: "sklears::traits::Estimator".to_string(),
description: "Base trait for all estimators".to_string(),
associated_types: vec![],
required_methods: vec![],
provided_methods: vec![],
implementors: vec![],
examples: vec![],
related_traits: vec![],
since: "0.1.0".to_string(),
};
api_ref.add_trait(trait_doc);
assert_eq!(api_ref.traits.len(), 1);
}
#[test]
fn test_search() {
let mut api_ref = InteractiveAPIReference::new();
let trait_doc = DocumentedTrait {
name: "Estimator".to_string(),
full_path: "sklears::traits::Estimator".to_string(),
description: "Base trait for all estimators".to_string(),
associated_types: vec![],
required_methods: vec![],
provided_methods: vec![],
implementors: vec![],
examples: vec![],
related_traits: vec![],
since: "0.1.0".to_string(),
};
api_ref.add_trait(trait_doc);
let results = api_ref.search("estimator");
assert_eq!(results.traits.len(), 1);
}
#[test]
fn test_html_generation() {
let api_ref = InteractiveAPIReference::new();
let html = api_ref.generate_html();
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("sklears API Reference"));
}
#[test]
fn test_interactive_example() {
let example = InteractiveExample {
title: "Basic Usage".to_string(),
code: "let x = 5;".to_string(),
expected_output: Some("5".to_string()),
runnable: true,
language: "rust".to_string(),
};
assert_eq!(example.language, "rust");
assert!(example.runnable);
}
#[test]
fn test_documented_method() {
let method = DocumentedMethod {
name: "fit".to_string(),
signature: "fn fit(&mut self, X: &Array2<f64>)".to_string(),
description: "Fit the model".to_string(),
parameters: vec![],
return_type: "Result<()>".to_string(),
examples: vec![],
safety: None,
};
assert_eq!(method.name, "fit");
}
#[test]
fn test_type_kind() {
assert_eq!(TypeKind::Struct, TypeKind::Struct);
assert_ne!(TypeKind::Struct, TypeKind::Enum);
}
#[test]
fn test_visibility() {
assert_eq!(Visibility::Public, Visibility::Public);
assert_ne!(Visibility::Public, Visibility::Private);
}
#[test]
fn test_search_results() {
let results = SearchResults {
traits: vec![],
types: vec![],
functions: vec![],
};
assert_eq!(results.traits.len(), 0);
}
#[test]
fn test_config_default() {
let config = APIReferenceConfig::default();
assert!(config.enable_live_examples);
assert!(config.enable_cross_references);
}
}