1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
14pub struct InteractiveAPIReference {
15 pub traits: HashMap<String, DocumentedTrait>,
17 pub types: HashMap<String, DocumentedType>,
19 pub functions: HashMap<String, DocumentedFunction>,
21 pub search_index: SearchIndex,
23 pub config: APIReferenceConfig,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct APIReferenceConfig {
30 pub enable_live_examples: bool,
32 pub enable_type_highlighting: bool,
34 pub enable_cross_references: bool,
36 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#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct DocumentedTrait {
54 pub name: String,
56 pub full_path: String,
58 pub description: String,
60 pub associated_types: Vec<AssociatedType>,
62 pub required_methods: Vec<DocumentedMethod>,
64 pub provided_methods: Vec<DocumentedMethod>,
66 pub implementors: Vec<String>,
68 pub examples: Vec<InteractiveExample>,
70 pub related_traits: Vec<String>,
72 pub since: String,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AssociatedType {
79 pub name: String,
81 pub bounds: Vec<String>,
83 pub description: String,
85 pub default: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct DocumentedMethod {
92 pub name: String,
94 pub signature: String,
96 pub description: String,
98 pub parameters: Vec<Parameter>,
100 pub return_type: String,
102 pub examples: Vec<String>,
104 pub safety: Option<String>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct Parameter {
111 pub name: String,
113 pub param_type: String,
115 pub description: String,
117 pub default: Option<String>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct DocumentedType {
124 pub name: String,
126 pub full_path: String,
128 pub kind: TypeKind,
130 pub description: String,
132 pub fields: Vec<Field>,
134 pub variants: Vec<Variant>,
136 pub traits: Vec<String>,
138 pub methods: Vec<DocumentedMethod>,
140 pub examples: Vec<InteractiveExample>,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146pub enum TypeKind {
147 Struct,
148 Enum,
149 Union,
150 TypeAlias,
151 Trait,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct Field {
157 pub name: String,
159 pub field_type: String,
161 pub description: String,
163 pub visibility: Visibility,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct Variant {
170 pub name: String,
172 pub fields: Vec<Field>,
174 pub description: String,
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180pub enum Visibility {
181 Public,
182 Crate,
183 Module,
184 Private,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct DocumentedFunction {
190 pub name: String,
192 pub full_path: String,
194 pub signature: String,
196 pub description: String,
198 pub parameters: Vec<Parameter>,
200 pub return_type: String,
202 pub examples: Vec<InteractiveExample>,
204 pub is_async: bool,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct InteractiveExample {
211 pub title: String,
213 pub code: String,
215 pub expected_output: Option<String>,
217 pub runnable: bool,
219 pub language: String,
221}
222
223#[derive(Debug, Clone)]
225pub struct SearchIndex {
226 pub trait_index: HashMap<String, Vec<String>>,
228 pub type_index: HashMap<String, Vec<String>>,
230 pub function_index: HashMap<String, Vec<String>>,
232}
233
234impl InteractiveAPIReference {
235 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 pub fn add_trait(&mut self, trait_doc: DocumentedTrait) {
252 self.index_trait(&trait_doc);
254 self.traits.insert(trait_doc.name.clone(), trait_doc);
255 }
256
257 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 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 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 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 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 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 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 html.push_str("<h1>sklears API Reference</h1>\n");
319
320 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 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 html.push_str("<main>\n");
337
338 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 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 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 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 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct SearchResults {
516 pub traits: Vec<DocumentedTrait>,
518 pub types: Vec<DocumentedType>,
520 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}