sklears_core/
interactive_api_reference.rs

1//! Interactive API Reference Generator
2//!
3//! This module provides an interactive, searchable API reference with live examples,
4//! type information, and cross-references.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Interactive API reference generator
10///
11/// Generates comprehensive, interactive API documentation with search,
12/// filtering, and live code examples.
13#[derive(Debug, Clone)]
14pub struct InteractiveAPIReference {
15    /// All documented traits
16    pub traits: HashMap<String, DocumentedTrait>,
17    /// All documented types
18    pub types: HashMap<String, DocumentedType>,
19    /// All documented functions
20    pub functions: HashMap<String, DocumentedFunction>,
21    /// Search index for fast lookups
22    pub search_index: SearchIndex,
23    /// Configuration
24    pub config: APIReferenceConfig,
25}
26
27/// Configuration for API reference generation
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct APIReferenceConfig {
30    /// Enable live code examples
31    pub enable_live_examples: bool,
32    /// Enable type highlighting
33    pub enable_type_highlighting: bool,
34    /// Enable cross-references
35    pub enable_cross_references: bool,
36    /// Theme for syntax highlighting
37    pub syntax_theme: String,
38}
39
40impl Default for APIReferenceConfig {
41    fn default() -> Self {
42        Self {
43            enable_live_examples: true,
44            enable_type_highlighting: true,
45            enable_cross_references: true,
46            syntax_theme: "monokai".to_string(),
47        }
48    }
49}
50
51/// Documented trait with enhanced information
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct DocumentedTrait {
54    /// Trait name
55    pub name: String,
56    /// Full qualified name
57    pub full_path: String,
58    /// Description
59    pub description: String,
60    /// Associated types
61    pub associated_types: Vec<AssociatedType>,
62    /// Required methods
63    pub required_methods: Vec<DocumentedMethod>,
64    /// Provided methods
65    pub provided_methods: Vec<DocumentedMethod>,
66    /// Implementors
67    pub implementors: Vec<String>,
68    /// Examples
69    pub examples: Vec<InteractiveExample>,
70    /// Related traits
71    pub related_traits: Vec<String>,
72    /// Since version
73    pub since: String,
74}
75
76/// Associated type in a trait
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AssociatedType {
79    /// Type name
80    pub name: String,
81    /// Type bounds
82    pub bounds: Vec<String>,
83    /// Description
84    pub description: String,
85    /// Default type (if any)
86    pub default: Option<String>,
87}
88
89/// Documented method
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct DocumentedMethod {
92    /// Method name
93    pub name: String,
94    /// Method signature
95    pub signature: String,
96    /// Description
97    pub description: String,
98    /// Parameters
99    pub parameters: Vec<Parameter>,
100    /// Return type
101    pub return_type: String,
102    /// Examples
103    pub examples: Vec<String>,
104    /// Safety notes (for unsafe methods)
105    pub safety: Option<String>,
106}
107
108/// Method/function parameter
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct Parameter {
111    /// Parameter name
112    pub name: String,
113    /// Parameter type
114    pub param_type: String,
115    /// Description
116    pub description: String,
117    /// Default value (if optional)
118    pub default: Option<String>,
119}
120
121/// Documented type (struct, enum, etc.)
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct DocumentedType {
124    /// Type name
125    pub name: String,
126    /// Full path
127    pub full_path: String,
128    /// Type kind
129    pub kind: TypeKind,
130    /// Description
131    pub description: String,
132    /// Fields (for structs)
133    pub fields: Vec<Field>,
134    /// Variants (for enums)
135    pub variants: Vec<Variant>,
136    /// Implemented traits
137    pub traits: Vec<String>,
138    /// Methods
139    pub methods: Vec<DocumentedMethod>,
140    /// Examples
141    pub examples: Vec<InteractiveExample>,
142}
143
144/// Kind of type
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146pub enum TypeKind {
147    Struct,
148    Enum,
149    Union,
150    TypeAlias,
151    Trait,
152}
153
154/// Struct field
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct Field {
157    /// Field name
158    pub name: String,
159    /// Field type
160    pub field_type: String,
161    /// Description
162    pub description: String,
163    /// Visibility
164    pub visibility: Visibility,
165}
166
167/// Enum variant
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct Variant {
170    /// Variant name
171    pub name: String,
172    /// Variant fields (if any)
173    pub fields: Vec<Field>,
174    /// Description
175    pub description: String,
176}
177
178/// Visibility level
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180pub enum Visibility {
181    Public,
182    Crate,
183    Module,
184    Private,
185}
186
187/// Documented function
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct DocumentedFunction {
190    /// Function name
191    pub name: String,
192    /// Full path
193    pub full_path: String,
194    /// Function signature
195    pub signature: String,
196    /// Description
197    pub description: String,
198    /// Parameters
199    pub parameters: Vec<Parameter>,
200    /// Return type
201    pub return_type: String,
202    /// Examples
203    pub examples: Vec<InteractiveExample>,
204    /// Async function
205    pub is_async: bool,
206}
207
208/// Interactive code example
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct InteractiveExample {
211    /// Example title
212    pub title: String,
213    /// Source code
214    pub code: String,
215    /// Expected output
216    pub expected_output: Option<String>,
217    /// Whether example is runnable
218    pub runnable: bool,
219    /// Language (usually "rust")
220    pub language: String,
221}
222
223/// Search index for API reference
224#[derive(Debug, Clone)]
225pub struct SearchIndex {
226    /// Trait index
227    pub trait_index: HashMap<String, Vec<String>>,
228    /// Type index
229    pub type_index: HashMap<String, Vec<String>>,
230    /// Function index
231    pub function_index: HashMap<String, Vec<String>>,
232}
233
234impl InteractiveAPIReference {
235    /// Create a new API reference
236    pub fn new() -> Self {
237        Self {
238            traits: HashMap::new(),
239            types: HashMap::new(),
240            functions: HashMap::new(),
241            search_index: SearchIndex {
242                trait_index: HashMap::new(),
243                type_index: HashMap::new(),
244                function_index: HashMap::new(),
245            },
246            config: APIReferenceConfig::default(),
247        }
248    }
249
250    /// Add a documented trait
251    pub fn add_trait(&mut self, trait_doc: DocumentedTrait) {
252        // Index for search
253        self.index_trait(&trait_doc);
254        self.traits.insert(trait_doc.name.clone(), trait_doc);
255    }
256
257    /// Add a documented type
258    pub fn add_type(&mut self, type_doc: DocumentedType) {
259        self.index_type(&type_doc);
260        self.types.insert(type_doc.name.clone(), type_doc);
261    }
262
263    /// Add a documented function
264    pub fn add_function(&mut self, func_doc: DocumentedFunction) {
265        self.index_function(&func_doc);
266        self.functions.insert(func_doc.name.clone(), func_doc);
267    }
268
269    /// Search the API reference
270    pub fn search(&self, query: &str) -> SearchResults {
271        let query_lower = query.to_lowercase();
272        let mut results = SearchResults {
273            traits: Vec::new(),
274            types: Vec::new(),
275            functions: Vec::new(),
276        };
277
278        // Search traits
279        for (name, trait_doc) in &self.traits {
280            if name.to_lowercase().contains(&query_lower)
281                || trait_doc.description.to_lowercase().contains(&query_lower)
282            {
283                results.traits.push(trait_doc.clone());
284            }
285        }
286
287        // Search types
288        for (name, type_doc) in &self.types {
289            if name.to_lowercase().contains(&query_lower)
290                || type_doc.description.to_lowercase().contains(&query_lower)
291            {
292                results.types.push(type_doc.clone());
293            }
294        }
295
296        // Search functions
297        for (name, func_doc) in &self.functions {
298            if name.to_lowercase().contains(&query_lower)
299                || func_doc.description.to_lowercase().contains(&query_lower)
300            {
301                results.functions.push(func_doc.clone());
302            }
303        }
304
305        results
306    }
307
308    /// Generate HTML documentation
309    pub fn generate_html(&self) -> String {
310        let mut html = String::from("<!DOCTYPE html>\n<html>\n<head>\n");
311        html.push_str("<title>sklears API Reference</title>\n");
312        html.push_str("<style>\n");
313        html.push_str(self.generate_css());
314        html.push_str("</style>\n");
315        html.push_str("</head>\n<body>\n");
316
317        // Header
318        html.push_str("<h1>sklears API Reference</h1>\n");
319
320        // Search box
321        html.push_str("<div class='search-box'>\n");
322        html.push_str("<input type='text' id='search' placeholder='Search API...'>\n");
323        html.push_str("</div>\n");
324
325        // Navigation
326        html.push_str("<nav>\n");
327        html.push_str("<h2>Categories</h2>\n");
328        html.push_str("<ul>\n");
329        html.push_str("<li><a href='#traits'>Traits</a></li>\n");
330        html.push_str("<li><a href='#types'>Types</a></li>\n");
331        html.push_str("<li><a href='#functions'>Functions</a></li>\n");
332        html.push_str("</ul>\n");
333        html.push_str("</nav>\n");
334
335        // Content
336        html.push_str("<main>\n");
337
338        // Traits section
339        html.push_str("<section id='traits'>\n");
340        html.push_str("<h2>Traits</h2>\n");
341        for trait_doc in self.traits.values() {
342            html.push_str(&self.generate_trait_html(trait_doc));
343        }
344        html.push_str("</section>\n");
345
346        // Types section
347        html.push_str("<section id='types'>\n");
348        html.push_str("<h2>Types</h2>\n");
349        for type_doc in self.types.values() {
350            html.push_str(&self.generate_type_html(type_doc));
351        }
352        html.push_str("</section>\n");
353
354        html.push_str("</main>\n");
355        html.push_str("</body>\n</html>");
356
357        html
358    }
359
360    /// Generate CSS for HTML documentation
361    fn generate_css(&self) -> &str {
362        r#"
363        body {
364            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
365            max-width: 1200px;
366            margin: 0 auto;
367            padding: 20px;
368            background: #f5f5f5;
369        }
370        h1 {
371            color: #333;
372            border-bottom: 3px solid #007acc;
373            padding-bottom: 10px;
374        }
375        .search-box {
376            margin: 20px 0;
377        }
378        #search {
379            width: 100%;
380            padding: 10px;
381            font-size: 16px;
382            border: 2px solid #ddd;
383            border-radius: 5px;
384        }
385        nav {
386            background: white;
387            padding: 20px;
388            border-radius: 5px;
389            margin-bottom: 20px;
390        }
391        nav ul {
392            list-style: none;
393            padding: 0;
394        }
395        nav li {
396            margin: 10px 0;
397        }
398        nav a {
399            color: #007acc;
400            text-decoration: none;
401            font-weight: bold;
402        }
403        section {
404            background: white;
405            padding: 20px;
406            margin-bottom: 20px;
407            border-radius: 5px;
408        }
409        .trait, .type, .function {
410            border-left: 4px solid #007acc;
411            padding-left: 20px;
412            margin: 20px 0;
413        }
414        code {
415            background: #f0f0f0;
416            padding: 2px 6px;
417            border-radius: 3px;
418            font-family: 'Courier New', monospace;
419        }
420        pre {
421            background: #2d2d2d;
422            color: #f8f8f2;
423            padding: 15px;
424            border-radius: 5px;
425            overflow-x: auto;
426        }
427        "#
428    }
429
430    /// Generate HTML for a trait
431    fn generate_trait_html(&self, trait_doc: &DocumentedTrait) -> String {
432        let mut html = String::new();
433        html.push_str("<div class='trait'>\n");
434        html.push_str(&format!("<h3>{}</h3>\n", trait_doc.name));
435        html.push_str(&format!("<p>{}</p>\n", trait_doc.description));
436
437        if !trait_doc.required_methods.is_empty() {
438            html.push_str("<h4>Required Methods</h4>\n");
439            html.push_str("<ul>\n");
440            for method in &trait_doc.required_methods {
441                html.push_str(&format!(
442                    "<li><code>{}</code> - {}</li>\n",
443                    method.signature, method.description
444                ));
445            }
446            html.push_str("</ul>\n");
447        }
448
449        html.push_str("</div>\n");
450        html
451    }
452
453    /// Generate HTML for a type
454    fn generate_type_html(&self, type_doc: &DocumentedType) -> String {
455        let mut html = String::new();
456        html.push_str("<div class='type'>\n");
457        html.push_str(&format!("<h3>{}</h3>\n", type_doc.name));
458        html.push_str(&format!("<p>{}</p>\n", type_doc.description));
459        html.push_str("</div>\n");
460        html
461    }
462
463    /// Index a trait for search
464    fn index_trait(&mut self, trait_doc: &DocumentedTrait) {
465        let keywords: Vec<String> = trait_doc
466            .name
467            .split('_')
468            .map(|s| s.to_lowercase())
469            .collect();
470
471        for keyword in keywords {
472            self.search_index
473                .trait_index
474                .entry(keyword)
475                .or_default()
476                .push(trait_doc.name.clone());
477        }
478    }
479
480    /// Index a type for search
481    fn index_type(&mut self, type_doc: &DocumentedType) {
482        let keywords: Vec<String> = type_doc.name.split('_').map(|s| s.to_lowercase()).collect();
483
484        for keyword in keywords {
485            self.search_index
486                .type_index
487                .entry(keyword)
488                .or_default()
489                .push(type_doc.name.clone());
490        }
491    }
492
493    /// Index a function for search
494    fn index_function(&mut self, func_doc: &DocumentedFunction) {
495        let keywords: Vec<String> = func_doc.name.split('_').map(|s| s.to_lowercase()).collect();
496
497        for keyword in keywords {
498            self.search_index
499                .function_index
500                .entry(keyword)
501                .or_default()
502                .push(func_doc.name.clone());
503        }
504    }
505}
506
507impl Default for InteractiveAPIReference {
508    fn default() -> Self {
509        Self::new()
510    }
511}
512
513/// Search results
514#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct SearchResults {
516    /// Matching traits
517    pub traits: Vec<DocumentedTrait>,
518    /// Matching types
519    pub types: Vec<DocumentedType>,
520    /// Matching functions
521    pub functions: Vec<DocumentedFunction>,
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    #[test]
529    fn test_api_reference_creation() {
530        let api_ref = InteractiveAPIReference::new();
531        assert_eq!(api_ref.traits.len(), 0);
532        assert_eq!(api_ref.types.len(), 0);
533    }
534
535    #[test]
536    fn test_add_trait() {
537        let mut api_ref = InteractiveAPIReference::new();
538
539        let trait_doc = DocumentedTrait {
540            name: "Estimator".to_string(),
541            full_path: "sklears::traits::Estimator".to_string(),
542            description: "Base trait for all estimators".to_string(),
543            associated_types: vec![],
544            required_methods: vec![],
545            provided_methods: vec![],
546            implementors: vec![],
547            examples: vec![],
548            related_traits: vec![],
549            since: "0.1.0".to_string(),
550        };
551
552        api_ref.add_trait(trait_doc);
553        assert_eq!(api_ref.traits.len(), 1);
554    }
555
556    #[test]
557    fn test_search() {
558        let mut api_ref = InteractiveAPIReference::new();
559
560        let trait_doc = DocumentedTrait {
561            name: "Estimator".to_string(),
562            full_path: "sklears::traits::Estimator".to_string(),
563            description: "Base trait for all estimators".to_string(),
564            associated_types: vec![],
565            required_methods: vec![],
566            provided_methods: vec![],
567            implementors: vec![],
568            examples: vec![],
569            related_traits: vec![],
570            since: "0.1.0".to_string(),
571        };
572
573        api_ref.add_trait(trait_doc);
574
575        let results = api_ref.search("estimator");
576        assert_eq!(results.traits.len(), 1);
577    }
578
579    #[test]
580    fn test_html_generation() {
581        let api_ref = InteractiveAPIReference::new();
582        let html = api_ref.generate_html();
583        assert!(html.contains("<!DOCTYPE html>"));
584        assert!(html.contains("sklears API Reference"));
585    }
586
587    #[test]
588    fn test_interactive_example() {
589        let example = InteractiveExample {
590            title: "Basic Usage".to_string(),
591            code: "let x = 5;".to_string(),
592            expected_output: Some("5".to_string()),
593            runnable: true,
594            language: "rust".to_string(),
595        };
596
597        assert_eq!(example.language, "rust");
598        assert!(example.runnable);
599    }
600
601    #[test]
602    fn test_documented_method() {
603        let method = DocumentedMethod {
604            name: "fit".to_string(),
605            signature: "fn fit(&mut self, X: &Array2<f64>)".to_string(),
606            description: "Fit the model".to_string(),
607            parameters: vec![],
608            return_type: "Result<()>".to_string(),
609            examples: vec![],
610            safety: None,
611        };
612
613        assert_eq!(method.name, "fit");
614    }
615
616    #[test]
617    fn test_type_kind() {
618        assert_eq!(TypeKind::Struct, TypeKind::Struct);
619        assert_ne!(TypeKind::Struct, TypeKind::Enum);
620    }
621
622    #[test]
623    fn test_visibility() {
624        assert_eq!(Visibility::Public, Visibility::Public);
625        assert_ne!(Visibility::Public, Visibility::Private);
626    }
627
628    #[test]
629    fn test_search_results() {
630        let results = SearchResults {
631            traits: vec![],
632            types: vec![],
633            functions: vec![],
634        };
635
636        assert_eq!(results.traits.len(), 0);
637    }
638
639    #[test]
640    fn test_config_default() {
641        let config = APIReferenceConfig::default();
642        assert!(config.enable_live_examples);
643        assert!(config.enable_cross_references);
644    }
645}