1use crate::api_analyzers::{CrossReferenceBuilder, ExampleValidator, TraitAnalyzer, TypeExtractor};
8use crate::api_data_structures::{
9 ApiMetadata, ApiReference, CodeExample, CrateInfo, TraitInfo, TypeInfo,
10};
11use crate::api_generator_config::{GeneratorConfig, OutputFormat, ThemeConfig};
12use crate::error::{Result, SklearsError};
13use std::collections::HashMap;
14use std::path::PathBuf;
15
16#[derive(Debug)]
22pub struct ApiReferenceGenerator {
23 config: GeneratorConfig,
24 trait_analyzer: TraitAnalyzer,
25 type_extractor: TypeExtractor,
26 example_validator: ExampleValidator,
27 cross_ref_builder: CrossReferenceBuilder,
28 formatter: DocumentFormatter,
29}
30
31impl ApiReferenceGenerator {
32 pub fn new(config: GeneratorConfig) -> Self {
34 Self {
35 formatter: DocumentFormatter::new(config.clone()),
36 config: config.clone(),
37 trait_analyzer: TraitAnalyzer::new(config.clone()),
38 type_extractor: TypeExtractor::new(config.clone()),
39 example_validator: ExampleValidator::new(),
40 cross_ref_builder: CrossReferenceBuilder::new(),
41 }
42 }
43
44 pub fn generate_from_crate(&mut self, crate_name: &str) -> Result<ApiReference> {
46 let crate_info = self.analyze_crate(crate_name)?;
47
48 let traits = if self.config.include_type_info {
49 self.trait_analyzer.analyze_traits(&crate_info)?
50 } else {
51 Vec::new()
52 };
53
54 let types = if self.config.include_type_info {
55 self.type_extractor.extract_types(&crate_info)?
56 } else {
57 Vec::new()
58 };
59
60 let examples = if self.config.include_examples {
61 let raw_examples = self.extract_examples(&crate_info)?;
62 if self.config.validate_examples {
63 self.example_validator.validate_examples(&raw_examples)?
64 } else {
65 raw_examples
66 }
67 } else {
68 Vec::new()
69 };
70
71 let cross_refs = if self.config.include_cross_refs {
72 self.cross_ref_builder
73 .build_cross_references(&traits, &types)?
74 } else {
75 HashMap::new()
76 };
77
78 Ok(ApiReference {
79 crate_name: crate_name.to_string(),
80 version: crate_info.version.clone(),
81 traits,
82 types,
83 examples,
84 cross_references: cross_refs,
85 metadata: self.generate_metadata(&crate_info)?,
86 })
87 }
88
89 pub fn generate_from_files(&mut self, source_files: Vec<PathBuf>) -> Result<ApiReference> {
91 let crate_info = self.analyze_source_files(&source_files)?;
93
94 let traits = if self.config.include_type_info {
96 self.trait_analyzer.analyze_traits(&crate_info)?
97 } else {
98 Vec::new()
99 };
100
101 let types = if self.config.include_type_info {
102 self.type_extractor.extract_types(&crate_info)?
103 } else {
104 Vec::new()
105 };
106
107 let examples = if self.config.include_examples {
108 let raw_examples = self.extract_examples_from_files(&source_files)?;
109 if self.config.validate_examples {
110 self.example_validator.validate_examples(&raw_examples)?
111 } else {
112 raw_examples
113 }
114 } else {
115 Vec::new()
116 };
117
118 let cross_refs = if self.config.include_cross_refs {
119 self.cross_ref_builder
120 .build_cross_references(&traits, &types)?
121 } else {
122 HashMap::new()
123 };
124
125 Ok(ApiReference {
126 crate_name: "custom".to_string(),
127 version: "unknown".to_string(),
128 traits,
129 types,
130 examples,
131 cross_references: cross_refs,
132 metadata: self.generate_metadata(&crate_info)?,
133 })
134 }
135
136 pub fn format_output(&self, api_ref: &ApiReference) -> Result<String> {
138 match self.config.output_format {
139 OutputFormat::Json => self.formatter.format_json(api_ref),
140 OutputFormat::Html => self.formatter.format_html(api_ref),
141 OutputFormat::Markdown => self.formatter.format_markdown(api_ref),
142 OutputFormat::Interactive => self.formatter.format_interactive(api_ref),
143 OutputFormat::OpenApi => self.formatter.format_openapi(api_ref),
144 }
145 }
146
147 pub fn format_as(&self, api_ref: &ApiReference, format: OutputFormat) -> Result<String> {
149 match format {
150 OutputFormat::Json => self.formatter.format_json(api_ref),
151 OutputFormat::Html => self.formatter.format_html(api_ref),
152 OutputFormat::Markdown => self.formatter.format_markdown(api_ref),
153 OutputFormat::Interactive => self.formatter.format_interactive(api_ref),
154 OutputFormat::OpenApi => self.formatter.format_openapi(api_ref),
155 }
156 }
157
158 pub fn formatter(&self) -> &DocumentFormatter {
160 &self.formatter
161 }
162
163 pub fn formatter_mut(&mut self) -> &mut DocumentFormatter {
165 &mut self.formatter
166 }
167
168 fn analyze_crate(&self, crate_name: &str) -> Result<CrateInfo> {
170 Ok(CrateInfo {
172 name: crate_name.to_string(),
173 version: "0.1.0".to_string(),
174 description: format!("API reference for {}", crate_name),
175 modules: vec![
176 "core".to_string(),
177 "traits".to_string(),
178 "error".to_string(),
179 "utils".to_string(),
180 ],
181 dependencies: vec![
182 "serde".to_string(),
183 "ndarray".to_string(),
184 "thiserror".to_string(),
185 ],
186 })
187 }
188
189 fn analyze_source_files(&self, _source_files: &[PathBuf]) -> Result<CrateInfo> {
191 Ok(CrateInfo {
193 name: "custom".to_string(),
194 version: "unknown".to_string(),
195 description: "Custom source file analysis".to_string(),
196 modules: Vec::new(),
197 dependencies: Vec::new(),
198 })
199 }
200
201 fn extract_examples(&self, crate_info: &CrateInfo) -> Result<Vec<CodeExample>> {
203 let mut examples = Vec::new();
205
206 examples.push(CodeExample {
207 title: "Basic Usage".to_string(),
208 description: format!("Demonstrates basic usage of {}", crate_info.name),
209 code: format!(
210 r#"use {};
211
212fn main() {{
213 // Your code here
214 println!("Hello from {}!");
215}}"#,
216 crate_info.name, crate_info.name
217 ),
218 language: "rust".to_string(),
219 runnable: true,
220 expected_output: Some(format!("Hello from {}!", crate_info.name)),
221 });
222
223 examples.push(CodeExample {
224 title: "Advanced Example".to_string(),
225 description: "Shows advanced features and patterns".to_string(),
226 code: format!(
227 r#"use {}::{{traits::*, error::*}};
228
229fn advanced_example() -> Result<(), Box<dyn std::error::Error>> {{
230 // Advanced usage patterns
231 let result = process_data()?;
232 println!("Result: {{:?}}", result);
233 Ok(())
234}}
235
236fn process_data() -> Result<String, {}Error> {{
237 // Complex processing logic
238 Ok("Processed data".to_string())
239}}"#,
240 crate_info.name,
241 crate_info.name.replace('-', "").to_title_case()
242 ),
243 language: "rust".to_string(),
244 runnable: true,
245 expected_output: Some("Result: \"Processed data\"".to_string()),
246 });
247
248 Ok(examples)
249 }
250
251 fn extract_examples_from_files(&self, _source_files: &[PathBuf]) -> Result<Vec<CodeExample>> {
253 Ok(vec![CodeExample {
255 title: "Source File Example".to_string(),
256 description: "Example extracted from source file documentation".to_string(),
257 code: "// Example code from source files".to_string(),
258 language: "rust".to_string(),
259 runnable: false,
260 expected_output: None,
261 }])
262 }
263
264 fn generate_metadata(&self, crate_info: &CrateInfo) -> Result<ApiMetadata> {
266 Ok(ApiMetadata {
267 generation_time: chrono::Utc::now().to_string(),
268 generator_version: env!("CARGO_PKG_VERSION").to_string(),
269 crate_version: crate_info.version.clone(),
270 rust_version: env!("CARGO_PKG_RUST_VERSION").to_string(),
271 config: self.config.clone(),
272 })
273 }
274}
275
276impl Default for ApiReferenceGenerator {
277 fn default() -> Self {
278 Self::new(GeneratorConfig::default())
279 }
280}
281
282#[derive(Debug, Clone)]
288pub struct DocumentFormatter {
289 #[allow(dead_code)]
290 config: GeneratorConfig,
291 theme: ThemeConfig,
292 custom_templates: HashMap<String, String>,
293}
294
295impl DocumentFormatter {
296 pub fn new(config: GeneratorConfig) -> Self {
298 Self {
299 theme: ThemeConfig::default(),
300 config,
301 custom_templates: HashMap::new(),
302 }
303 }
304
305 pub fn with_theme(config: GeneratorConfig, theme: ThemeConfig) -> Self {
307 Self {
308 config,
309 theme,
310 custom_templates: HashMap::new(),
311 }
312 }
313
314 pub fn format_json(&self, api_ref: &ApiReference) -> Result<String> {
316 serde_json::to_string_pretty(api_ref)
317 .map_err(|e| SklearsError::InvalidInput(format!("JSON serialization failed: {}", e)))
318 }
319
320 pub fn format_html(&self, api_ref: &ApiReference) -> Result<String> {
322 let mut html = String::new();
323
324 html.push_str("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n");
326 html.push_str(" <meta charset=\"UTF-8\">\n");
327 html.push_str(
328 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n",
329 );
330 html.push_str(&format!(
331 " <title>API Reference - {}</title>\n",
332 api_ref.crate_name
333 ));
334 html.push_str(" <style>\n");
335 html.push_str(&self.generate_html_css()?);
336 html.push_str(" </style>\n");
337 html.push_str("</head>\n<body>\n");
338
339 html.push_str(&self.generate_navigation(api_ref)?);
341
342 html.push_str(" <main class=\"content\">\n");
344 html.push_str(&format!(
345 " <h1>API Reference for {}</h1>\n",
346 api_ref.crate_name
347 ));
348 html.push_str(&format!(
349 " <p class=\"version\">Version: {}</p>\n",
350 api_ref.version
351 ));
352
353 html.push_str(&self.generate_table_of_contents(api_ref)?);
355
356 if !api_ref.traits.is_empty() {
358 html.push_str(&self.format_traits_html(&api_ref.traits)?);
359 }
360
361 if !api_ref.types.is_empty() {
363 html.push_str(&self.format_types_html(&api_ref.types)?);
364 }
365
366 if !api_ref.examples.is_empty() {
368 html.push_str(&self.format_examples_html(&api_ref.examples)?);
369 }
370
371 if !api_ref.cross_references.is_empty() {
373 html.push_str(&self.format_cross_refs_html(&api_ref.cross_references)?);
374 }
375
376 html.push_str(" </main>\n");
377
378 html.push_str(&self.generate_footer(api_ref)?);
380
381 html.push_str("</body>\n</html>");
382 Ok(html)
383 }
384
385 pub fn format_markdown(&self, api_ref: &ApiReference) -> Result<String> {
387 let mut md = String::new();
388
389 md.push_str(&format!("# API Reference - {}\n\n", api_ref.crate_name));
391 md.push_str(&format!("**Version:** {}\n\n", api_ref.version));
392 md.push_str(&format!(
393 "**Generated:** {}\n\n",
394 api_ref.metadata.generation_time
395 ));
396
397 md.push_str("## Table of Contents\n\n");
399 if !api_ref.traits.is_empty() {
400 md.push_str("- [Traits](#traits)\n");
401 }
402 if !api_ref.types.is_empty() {
403 md.push_str("- [Types](#types)\n");
404 }
405 if !api_ref.examples.is_empty() {
406 md.push_str("- [Examples](#examples)\n");
407 }
408 if !api_ref.cross_references.is_empty() {
409 md.push_str("- [Cross References](#cross-references)\n");
410 }
411 md.push('\n');
412
413 if !api_ref.traits.is_empty() {
415 md.push_str(&self.format_traits_markdown(&api_ref.traits)?);
416 }
417
418 if !api_ref.types.is_empty() {
420 md.push_str(&self.format_types_markdown(&api_ref.types)?);
421 }
422
423 if !api_ref.examples.is_empty() {
425 md.push_str(&self.format_examples_markdown(&api_ref.examples)?);
426 }
427
428 if !api_ref.cross_references.is_empty() {
430 md.push_str(&self.format_cross_refs_markdown(&api_ref.cross_references)?);
431 }
432
433 Ok(md)
434 }
435
436 pub fn format_interactive(&self, api_ref: &ApiReference) -> Result<String> {
438 let mut html = String::new();
439
440 html.push_str("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n");
441 html.push_str(" <meta charset=\"UTF-8\">\n");
442 html.push_str(
443 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n",
444 );
445 html.push_str(&format!(
446 " <title>Interactive API Reference - {}</title>\n",
447 api_ref.crate_name
448 ));
449
450 html.push_str(" <script src=\"https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs/loader.js\"></script>\n");
452 html.push_str(" <script src=\"https://unpkg.com/@webassembly/wasi-sdk@0.11.0/bin/wasm-ld\"></script>\n");
453
454 html.push_str(" <style>\n");
456 html.push_str(&self.generate_interactive_css()?);
457 html.push_str(" </style>\n");
458 html.push_str("</head>\n<body>\n");
459
460 html.push_str(" <header class=\"interactive-header\">\n");
462 html.push_str(&format!(
463 " <h1>Interactive API Reference - {}</h1>\n",
464 api_ref.crate_name
465 ));
466 html.push_str(" <nav class=\"interactive-nav\">\n");
467 html.push_str(" <button id=\"run-code\" class=\"nav-btn\">Run Code</button>\n");
468 html.push_str(" <button id=\"reset-code\" class=\"nav-btn\">Reset</button>\n");
469 html.push_str(" <button id=\"share-code\" class=\"nav-btn\">Share</button>\n");
470 html.push_str(" </nav>\n");
471 html.push_str(" </header>\n");
472
473 html.push_str(" <main class=\"interactive-main\">\n");
475 html.push_str(" <div class=\"editor-panel\">\n");
476 html.push_str(" <h2>Code Editor</h2>\n");
477 html.push_str(" <div id=\"code-editor\"></div>\n");
478 html.push_str(" </div>\n");
479 html.push_str(" <div class=\"output-panel\">\n");
480 html.push_str(" <h2>Output</h2>\n");
481 html.push_str(" <div id=\"output-display\"></div>\n");
482 html.push_str(" </div>\n");
483 html.push_str(" <div class=\"api-panel\">\n");
484 html.push_str(" <h2>API Explorer</h2>\n");
485 html.push_str(&self.format_interactive_api_explorer(api_ref)?);
486 html.push_str(" </div>\n");
487 html.push_str(" </main>\n");
488
489 html.push_str(" <section class=\"examples-gallery\">\n");
491 html.push_str(" <h2>Examples</h2>\n");
492 html.push_str(&self.format_interactive_examples(&api_ref.examples)?);
493 html.push_str(" </section>\n");
494
495 html.push_str(" <script>\n");
497 html.push_str(&self.generate_interactive_js(api_ref)?);
498 html.push_str(" </script>\n");
499
500 html.push_str("</body>\n</html>");
501 Ok(html)
502 }
503
504 pub fn format_openapi(&self, api_ref: &ApiReference) -> Result<String> {
506 let mut openapi = String::new();
507
508 openapi.push_str("openapi: 3.0.3\n");
509 openapi.push_str("info:\n");
510 openapi.push_str(&format!(" title: {} API\n", api_ref.crate_name));
511 openapi.push_str(&format!(" version: {}\n", api_ref.version));
512 openapi.push_str(&format!(
513 " description: API specification for {}\n",
514 api_ref.crate_name
515 ));
516 openapi.push_str("servers:\n");
517 openapi.push_str(" - url: http://localhost:8080\n");
518 openapi.push_str(" description: Development server\n");
519
520 openapi.push_str("paths:\n");
521
522 for trait_info in &api_ref.traits {
524 for method in &trait_info.methods {
525 let path = format!("/{}/{}", trait_info.name.to_lowercase(), method.name);
526 openapi.push_str(&format!(" {}:\n", path));
527 openapi.push_str(" post:\n");
528 openapi.push_str(&format!(" summary: {}\n", method.description));
529 openapi.push_str(&format!(
530 " operationId: {}_{}\n",
531 trait_info.name, method.name
532 ));
533 openapi.push_str(" responses:\n");
534 openapi.push_str(" '200':\n");
535 openapi.push_str(" description: Successful operation\n");
536 openapi.push_str(" content:\n");
537 openapi.push_str(" application/json:\n");
538 openapi.push_str(" schema:\n");
539 openapi.push_str(" type: object\n");
540 }
541 }
542
543 openapi.push_str("components:\n");
544 openapi.push_str(" schemas:\n");
545
546 for type_info in &api_ref.types {
548 openapi.push_str(&format!(" {}:\n", type_info.name));
549 openapi.push_str(" type: object\n");
550 if !type_info.fields.is_empty() {
551 openapi.push_str(" properties:\n");
552 for field in &type_info.fields {
553 openapi.push_str(&format!(" {}:\n", field.name));
554 openapi.push_str(&format!(
555 " type: {}\n",
556 self.rust_type_to_openapi(&field.field_type)
557 ));
558 if !field.description.is_empty() {
559 openapi
560 .push_str(&format!(" description: {}\n", field.description));
561 }
562 }
563 }
564 }
565
566 Ok(openapi)
567 }
568
569 pub fn set_theme(&mut self, theme: ThemeConfig) {
571 self.theme = theme;
572 }
573
574 pub fn add_template(&mut self, name: String, template: String) {
576 self.custom_templates.insert(name, template);
577 }
578
579 fn generate_html_css(&self) -> Result<String> {
581 let mut css = String::new();
582
583 css.push_str(&self.theme.to_css_variables());
585 css.push_str("\n\n");
586
587 css.push_str(
589 r#"
590 body {
591 font-family: var(--font-family);
592 background-color: var(--background-color);
593 color: var(--text-color);
594 line-height: 1.6;
595 margin: 0;
596 padding: 0;
597 }
598
599 .content {
600 max-width: 1200px;
601 margin: 0 auto;
602 padding: 2rem;
603 }
604
605 h1, h2, h3, h4, h5, h6 {
606 color: var(--primary-color);
607 margin-top: 2rem;
608 margin-bottom: 1rem;
609 }
610
611 .version {
612 color: var(--secondary-color);
613 font-weight: bold;
614 }
615
616 .toc {
617 background: var(--code-background);
618 padding: 1rem;
619 border-radius: 8px;
620 margin: 2rem 0;
621 }
622
623 .toc ul {
624 list-style-type: none;
625 padding-left: 1rem;
626 }
627
628 .toc a {
629 color: var(--primary-color);
630 text-decoration: none;
631 }
632
633 .toc a:hover {
634 text-decoration: underline;
635 }
636
637 .trait-section, .type-section, .example-section {
638 margin: 3rem 0;
639 padding: 2rem;
640 border: 1px solid #ddd;
641 border-radius: 8px;
642 }
643
644 .method-list {
645 margin-left: 1rem;
646 }
647
648 .method-item {
649 margin: 1rem 0;
650 padding: 1rem;
651 background: var(--code-background);
652 border-radius: 4px;
653 }
654
655 .method-signature {
656 font-family: var(--code-font-family);
657 background: var(--code-background);
658 color: var(--code-text-color);
659 padding: 0.5rem;
660 border-radius: 4px;
661 font-size: 0.9rem;
662 }
663
664 .code-example {
665 background: var(--code-background);
666 color: var(--code-text-color);
667 padding: 1rem;
668 border-radius: 4px;
669 overflow-x: auto;
670 margin: 1rem 0;
671 }
672
673 .code-example pre {
674 margin: 0;
675 font-family: var(--code-font-family);
676 }
677
678 .footer {
679 margin-top: 4rem;
680 padding: 2rem;
681 border-top: 1px solid #ddd;
682 text-align: center;
683 color: #666;
684 }
685
686 .nav {
687 background: var(--primary-color);
688 padding: 1rem;
689 margin-bottom: 2rem;
690 }
691
692 .nav a {
693 color: white;
694 text-decoration: none;
695 margin-right: 1rem;
696 padding: 0.5rem 1rem;
697 border-radius: 4px;
698 transition: background 0.2s;
699 }
700
701 .nav a:hover {
702 background: rgba(255, 255, 255, 0.2);
703 }
704 "#,
705 );
706
707 Ok(css)
708 }
709
710 fn generate_navigation(&self, api_ref: &ApiReference) -> Result<String> {
712 let mut nav = String::new();
713 nav.push_str(" <nav class=\"nav\">\n");
714 if !api_ref.traits.is_empty() {
715 nav.push_str(" <a href=\"#traits\">Traits</a>\n");
716 }
717 if !api_ref.types.is_empty() {
718 nav.push_str(" <a href=\"#types\">Types</a>\n");
719 }
720 if !api_ref.examples.is_empty() {
721 nav.push_str(" <a href=\"#examples\">Examples</a>\n");
722 }
723 nav.push_str(" </nav>\n");
724 Ok(nav)
725 }
726
727 fn generate_table_of_contents(&self, api_ref: &ApiReference) -> Result<String> {
729 let mut toc = String::new();
730 toc.push_str(" <div class=\"toc\">\n");
731 toc.push_str(" <h2>Table of Contents</h2>\n");
732 toc.push_str(" <ul>\n");
733
734 if !api_ref.traits.is_empty() {
735 toc.push_str(" <li><a href=\"#traits\">Traits</a>\n");
736 toc.push_str(" <ul>\n");
737 for trait_info in &api_ref.traits {
738 toc.push_str(&format!(
739 " <li><a href=\"#{}\">{}::{}</a></li>\n",
740 trait_info.name.to_lowercase(),
741 trait_info
742 .path
743 .split("::")
744 .last()
745 .unwrap_or(&trait_info.path),
746 trait_info.name
747 ));
748 }
749 toc.push_str(" </ul>\n");
750 toc.push_str(" </li>\n");
751 }
752
753 if !api_ref.types.is_empty() {
754 toc.push_str(" <li><a href=\"#types\">Types</a>\n");
755 toc.push_str(" <ul>\n");
756 for type_info in &api_ref.types {
757 toc.push_str(&format!(
758 " <li><a href=\"#{}\">{}::{}</a></li>\n",
759 type_info.name.to_lowercase(),
760 type_info.path.split("::").last().unwrap_or(&type_info.path),
761 type_info.name
762 ));
763 }
764 toc.push_str(" </ul>\n");
765 toc.push_str(" </li>\n");
766 }
767
768 if !api_ref.examples.is_empty() {
769 toc.push_str(" <li><a href=\"#examples\">Examples</a></li>\n");
770 }
771
772 toc.push_str(" </ul>\n");
773 toc.push_str(" </div>\n");
774 Ok(toc)
775 }
776
777 fn format_traits_html(&self, traits: &[TraitInfo]) -> Result<String> {
779 let mut html = String::new();
780 html.push_str(" <section id=\"traits\">\n");
781 html.push_str(" <h2>Traits</h2>\n");
782
783 for trait_info in traits {
784 html.push_str(&format!(
785 " <div class=\"trait-section\" id=\"{}\">\n",
786 trait_info.name.to_lowercase()
787 ));
788 html.push_str(&format!(" <h3>{}</h3>\n", trait_info.name));
789 html.push_str(&format!(
790 " <p>{}</p>\n",
791 trait_info.description
792 ));
793 html.push_str(&format!(
794 " <p><strong>Path:</strong> <code>{}</code></p>\n",
795 trait_info.path
796 ));
797
798 if !trait_info.generics.is_empty() {
799 html.push_str(" <p><strong>Generic Parameters:</strong> ");
800 html.push_str(&trait_info.generics.join(", "));
801 html.push_str("</p>\n");
802 }
803
804 if !trait_info.supertraits.is_empty() {
805 html.push_str(" <p><strong>Supertraits:</strong> ");
806 html.push_str(&trait_info.supertraits.join(", "));
807 html.push_str("</p>\n");
808 }
809
810 if !trait_info.methods.is_empty() {
811 html.push_str(" <h4>Methods</h4>\n");
812 html.push_str(" <div class=\"method-list\">\n");
813 for method in &trait_info.methods {
814 html.push_str(" <div class=\"method-item\">\n");
815 html.push_str(&format!(
816 " <h5>{}</h5>\n",
817 method.name
818 ));
819 html.push_str(&format!(
820 " <div class=\"method-signature\"><code>{}</code></div>\n",
821 method.signature
822 ));
823 html.push_str(&format!(
824 " <p>{}</p>\n",
825 method.description
826 ));
827 if method.required {
828 html.push_str(" <p><em>Required method</em></p>\n");
829 }
830 html.push_str(" </div>\n");
831 }
832 html.push_str(" </div>\n");
833 }
834
835 html.push_str(" </div>\n");
836 }
837
838 html.push_str(" </section>\n");
839 Ok(html)
840 }
841
842 fn format_types_html(&self, types: &[TypeInfo]) -> Result<String> {
844 let mut html = String::new();
845 html.push_str(" <section id=\"types\">\n");
846 html.push_str(" <h2>Types</h2>\n");
847
848 for type_info in types {
849 html.push_str(&format!(
850 " <div class=\"type-section\" id=\"{}\">\n",
851 type_info.name.to_lowercase()
852 ));
853 html.push_str(&format!(" <h3>{}</h3>\n", type_info.name));
854 html.push_str(&format!(
855 " <p>{}</p>\n",
856 type_info.description
857 ));
858 html.push_str(&format!(
859 " <p><strong>Path:</strong> <code>{}</code></p>\n",
860 type_info.path
861 ));
862 html.push_str(&format!(
863 " <p><strong>Kind:</strong> {:?}</p>\n",
864 type_info.kind
865 ));
866
867 if !type_info.fields.is_empty() {
868 html.push_str(" <h4>Fields</h4>\n");
869 html.push_str(" <ul>\n");
870 for field in &type_info.fields {
871 html.push_str(&format!(
872 " <li><strong>{}:</strong> <code>{}</code> - {}</li>\n",
873 field.name, field.field_type, field.description
874 ));
875 }
876 html.push_str(" </ul>\n");
877 }
878
879 if !type_info.trait_impls.is_empty() {
880 html.push_str(" <p><strong>Implemented Traits:</strong> ");
881 html.push_str(&type_info.trait_impls.join(", "));
882 html.push_str("</p>\n");
883 }
884
885 html.push_str(" </div>\n");
886 }
887
888 html.push_str(" </section>\n");
889 Ok(html)
890 }
891
892 fn format_examples_html(&self, examples: &[CodeExample]) -> Result<String> {
894 let mut html = String::new();
895 html.push_str(" <section id=\"examples\">\n");
896 html.push_str(" <h2>Examples</h2>\n");
897
898 for example in examples {
899 html.push_str(" <div class=\"example-section\">\n");
900 html.push_str(&format!(" <h3>{}</h3>\n", example.title));
901 html.push_str(&format!(" <p>{}</p>\n", example.description));
902 html.push_str(&format!(
903 " <div class=\"code-example\"><pre><code class=\"{}\">{}</code></pre></div>\n",
904 example.language, example.code
905 ));
906 if let Some(output) = &example.expected_output {
907 html.push_str(" <p><strong>Expected Output:</strong></p>\n");
908 html.push_str(&format!(
909 " <div class=\"code-example\"><pre>{}</pre></div>\n",
910 output
911 ));
912 }
913 html.push_str(" </div>\n");
914 }
915
916 html.push_str(" </section>\n");
917 Ok(html)
918 }
919
920 fn format_cross_refs_html(&self, cross_refs: &HashMap<String, Vec<String>>) -> Result<String> {
922 let mut html = String::new();
923 html.push_str(" <section id=\"cross-references\">\n");
924 html.push_str(" <h2>Cross References</h2>\n");
925
926 for (item, refs) in cross_refs {
927 if !refs.is_empty() {
928 html.push_str(&format!(" <h3>{}</h3>\n", item));
929 html.push_str(" <ul>\n");
930 for ref_item in refs {
931 html.push_str(&format!(" <li>{}</li>\n", ref_item));
932 }
933 html.push_str(" </ul>\n");
934 }
935 }
936
937 html.push_str(" </section>\n");
938 Ok(html)
939 }
940
941 fn format_traits_markdown(&self, traits: &[TraitInfo]) -> Result<String> {
943 let mut md = String::new();
944 md.push_str("## Traits\n\n");
945
946 for trait_info in traits {
947 md.push_str(&format!("### {}\n\n", trait_info.name));
948 md.push_str(&format!("{}\n\n", trait_info.description));
949 md.push_str(&format!("**Path:** `{}`\n\n", trait_info.path));
950
951 if !trait_info.generics.is_empty() {
952 md.push_str(&format!(
953 "**Generic Parameters:** {}\n\n",
954 trait_info.generics.join(", ")
955 ));
956 }
957
958 if !trait_info.supertraits.is_empty() {
959 md.push_str(&format!(
960 "**Supertraits:** {}\n\n",
961 trait_info.supertraits.join(", ")
962 ));
963 }
964
965 if !trait_info.methods.is_empty() {
966 md.push_str("#### Methods\n\n");
967 for method in &trait_info.methods {
968 md.push_str(&format!("##### {}\n\n", method.name));
969 md.push_str(&format!("```rust\n{}\n```\n\n", method.signature));
970 md.push_str(&format!("{}\n\n", method.description));
971 if method.required {
972 md.push_str("*Required method*\n\n");
973 }
974 }
975 }
976 }
977
978 Ok(md)
979 }
980
981 fn format_types_markdown(&self, types: &[TypeInfo]) -> Result<String> {
983 let mut md = String::new();
984 md.push_str("## Types\n\n");
985
986 for type_info in types {
987 md.push_str(&format!("### {}\n\n", type_info.name));
988 md.push_str(&format!("{}\n\n", type_info.description));
989 md.push_str(&format!("**Path:** `{}`\n\n", type_info.path));
990 md.push_str(&format!("**Kind:** {:?}\n\n", type_info.kind));
991
992 if !type_info.fields.is_empty() {
993 md.push_str("#### Fields\n\n");
994 for field in &type_info.fields {
995 md.push_str(&format!(
996 "- **{}:** `{}` - {}\n",
997 field.name, field.field_type, field.description
998 ));
999 }
1000 md.push('\n');
1001 }
1002
1003 if !type_info.trait_impls.is_empty() {
1004 md.push_str(&format!(
1005 "**Implemented Traits:** {}\n\n",
1006 type_info.trait_impls.join(", ")
1007 ));
1008 }
1009 }
1010
1011 Ok(md)
1012 }
1013
1014 fn format_examples_markdown(&self, examples: &[CodeExample]) -> Result<String> {
1016 let mut md = String::new();
1017 md.push_str("## Examples\n\n");
1018
1019 for example in examples {
1020 md.push_str(&format!("### {}\n\n", example.title));
1021 md.push_str(&format!("{}\n\n", example.description));
1022 md.push_str(&format!(
1023 "```{}\n{}\n```\n\n",
1024 example.language, example.code
1025 ));
1026 if let Some(output) = &example.expected_output {
1027 md.push_str("**Expected Output:**\n\n");
1028 md.push_str(&format!("```\n{}\n```\n\n", output));
1029 }
1030 }
1031
1032 Ok(md)
1033 }
1034
1035 fn format_cross_refs_markdown(
1037 &self,
1038 cross_refs: &HashMap<String, Vec<String>>,
1039 ) -> Result<String> {
1040 let mut md = String::new();
1041 md.push_str("## Cross References\n\n");
1042
1043 for (item, refs) in cross_refs {
1044 if !refs.is_empty() {
1045 md.push_str(&format!("### {}\n\n", item));
1046 for ref_item in refs {
1047 md.push_str(&format!("- {}\n", ref_item));
1048 }
1049 md.push('\n');
1050 }
1051 }
1052
1053 Ok(md)
1054 }
1055
1056 fn generate_footer(&self, api_ref: &ApiReference) -> Result<String> {
1058 Ok(format!(
1059 r#" <footer class="footer">
1060 <p>Generated on {} by sklears-core API generator v{}</p>
1061 <p>Rust version: {}</p>
1062 </footer>"#,
1063 api_ref.metadata.generation_time,
1064 api_ref.metadata.generator_version,
1065 api_ref.metadata.rust_version
1066 ))
1067 }
1068
1069 fn generate_interactive_css(&self) -> Result<String> {
1071 Ok(r#"
1072 .interactive-header {
1073 background: var(--primary-color);
1074 color: white;
1075 padding: 1rem;
1076 display: flex;
1077 justify-content: space-between;
1078 align-items: center;
1079 }
1080
1081 .interactive-nav {
1082 display: flex;
1083 gap: 1rem;
1084 }
1085
1086 .nav-btn {
1087 background: rgba(255, 255, 255, 0.2);
1088 color: white;
1089 border: none;
1090 padding: 0.5rem 1rem;
1091 border-radius: 4px;
1092 cursor: pointer;
1093 transition: background 0.2s;
1094 }
1095
1096 .nav-btn:hover {
1097 background: rgba(255, 255, 255, 0.3);
1098 }
1099
1100 .interactive-main {
1101 display: grid;
1102 grid-template-columns: 1fr 1fr 300px;
1103 gap: 1rem;
1104 padding: 1rem;
1105 height: 70vh;
1106 }
1107
1108 .editor-panel, .output-panel, .api-panel {
1109 border: 1px solid #ddd;
1110 border-radius: 8px;
1111 padding: 1rem;
1112 overflow: hidden;
1113 }
1114
1115 #code-editor {
1116 height: 90%;
1117 border: 1px solid #ddd;
1118 border-radius: 4px;
1119 }
1120
1121 #output-display {
1122 height: 90%;
1123 background: var(--code-background);
1124 color: var(--code-text-color);
1125 padding: 1rem;
1126 border-radius: 4px;
1127 overflow-y: auto;
1128 font-family: var(--code-font-family);
1129 }
1130
1131 .api-panel {
1132 overflow-y: auto;
1133 }
1134
1135 .examples-gallery {
1136 padding: 1rem;
1137 border-top: 1px solid #ddd;
1138 }
1139
1140 .example-card {
1141 background: var(--code-background);
1142 padding: 1rem;
1143 margin: 0.5rem;
1144 border-radius: 4px;
1145 cursor: pointer;
1146 transition: transform 0.2s;
1147 }
1148
1149 .example-card:hover {
1150 transform: translateY(-2px);
1151 }
1152 "#
1153 .to_string())
1154 }
1155
1156 fn format_interactive_api_explorer(&self, api_ref: &ApiReference) -> Result<String> {
1158 let mut html = String::new();
1159
1160 html.push_str(" <div class=\"api-search\">\n");
1161 html.push_str(" <input type=\"text\" placeholder=\"Search API...\" id=\"api-search-input\">\n");
1162 html.push_str(" </div>\n");
1163
1164 if !api_ref.traits.is_empty() {
1165 html.push_str(" <div class=\"api-section\">\n");
1166 html.push_str(" <h3>Traits</h3>\n");
1167 for trait_info in &api_ref.traits {
1168 html.push_str(&format!(
1169 " <div class=\"api-item\" data-type=\"trait\" onclick=\"insertCode('{}')\">{}</div>\n",
1170 trait_info.name, trait_info.name
1171 ));
1172 }
1173 html.push_str(" </div>\n");
1174 }
1175
1176 if !api_ref.types.is_empty() {
1177 html.push_str(" <div class=\"api-section\">\n");
1178 html.push_str(" <h3>Types</h3>\n");
1179 for type_info in &api_ref.types {
1180 html.push_str(&format!(
1181 " <div class=\"api-item\" data-type=\"type\" onclick=\"insertCode('{}')\">{}</div>\n",
1182 type_info.name, type_info.name
1183 ));
1184 }
1185 html.push_str(" </div>\n");
1186 }
1187
1188 Ok(html)
1189 }
1190
1191 fn format_interactive_examples(&self, examples: &[CodeExample]) -> Result<String> {
1193 let mut html = String::new();
1194
1195 html.push_str(" <div class=\"examples-grid\">\n");
1196 for (i, example) in examples.iter().enumerate() {
1197 html.push_str(&format!(
1198 r#" <div class="example-card" onclick="loadExample({})" title="{}">
1199 <h4>{}</h4>
1200 <p>{}</p>
1201 </div>"#,
1202 i, example.description, example.title, example.description
1203 ));
1204 }
1205 html.push_str(" </div>\n");
1206
1207 Ok(html)
1208 }
1209
1210 fn generate_interactive_js(&self, api_ref: &ApiReference) -> Result<String> {
1212 let mut js = String::new();
1213
1214 js.push_str(" const examples = [\n");
1216 for example in &api_ref.examples {
1217 js.push_str(&format!(
1218 " {{ title: '{}', code: `{}` }},\n",
1219 example.title.replace('\'', "\\'"),
1220 example.code.replace('`', "\\`")
1221 ));
1222 }
1223 js.push_str(" ];\n\n");
1224
1225 js.push_str(r#"
1227 let editor;
1228
1229 // Initialize Monaco Editor
1230 require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs' }});
1231 require(['vs/editor/editor.main'], function () {
1232 editor = monaco.editor.create(document.getElementById('code-editor'), {
1233 value: examples[0] ? examples[0].code : 'fn main() {\n println!("Hello, sklears!");\n}',
1234 language: 'rust',
1235 theme: 'vs-dark',
1236 automaticLayout: true
1237 });
1238 });
1239
1240 function runCode() {
1241 const code = editor.getValue();
1242 const output = document.getElementById('output-display');
1243 output.innerHTML = 'Running code...\n\n' + code;
1244 }
1245
1246 function resetCode() {
1247 if (examples[0]) {
1248 editor.setValue(examples[0].code);
1249 }
1250 document.getElementById('output-display').innerHTML = 'Output will appear here...';
1251 }
1252
1253 function shareCode() {
1254 const code = editor.getValue();
1255 const encoded = btoa(code);
1256 const url = window.location.origin + window.location.pathname + '?code=' + encoded;
1257 navigator.clipboard.writeText(url);
1258 alert('Shareable link copied to clipboard!');
1259 }
1260
1261 function loadExample(index) {
1262 if (examples[index]) {
1263 editor.setValue(examples[index].code);
1264 document.getElementById('output-display').innerHTML = 'Example loaded: ' + examples[index].title;
1265 }
1266 }
1267
1268 function insertCode(apiItem) {
1269 const currentCode = editor.getValue();
1270 const newCode = currentCode + '\n// Using ' + apiItem + '\n';
1271 editor.setValue(newCode);
1272 }
1273
1274 // Event listeners
1275 document.getElementById('run-code').addEventListener('click', runCode);
1276 document.getElementById('reset-code').addEventListener('click', resetCode);
1277 document.getElementById('share-code').addEventListener('click', shareCode);
1278
1279 // API search functionality
1280 document.getElementById('api-search-input').addEventListener('input', function(e) {
1281 const query = e.target.value.toLowerCase();
1282 const items = document.querySelectorAll('.api-item');
1283 items.forEach(item => {
1284 if (item.textContent.toLowerCase().includes(query)) {
1285 item.style.display = 'block';
1286 } else {
1287 item.style.display = 'none';
1288 }
1289 });
1290 });
1291 "#);
1292
1293 Ok(js)
1294 }
1295
1296 fn rust_type_to_openapi(&self, rust_type: &str) -> &'static str {
1298 match rust_type {
1299 "String" | "&str" => "string",
1300 "i32" | "i64" | "u32" | "u64" | "usize" | "isize" => "integer",
1301 "f32" | "f64" => "number",
1302 "bool" => "boolean",
1303 _ => "object",
1304 }
1305 }
1306}
1307
1308impl Default for DocumentFormatter {
1309 fn default() -> Self {
1310 Self::new(GeneratorConfig::default())
1311 }
1312}
1313
1314trait StringExt {
1320 fn to_title_case(&self) -> String;
1321}
1322
1323impl StringExt for str {
1324 fn to_title_case(&self) -> String {
1325 self.chars()
1326 .enumerate()
1327 .map(|(i, c)| {
1328 if i == 0 || self.chars().nth(i - 1).unwrap_or(' ').is_whitespace() {
1329 c.to_uppercase().collect::<String>()
1330 } else {
1331 c.to_lowercase().collect::<String>()
1332 }
1333 })
1334 .collect()
1335 }
1336}
1337
1338mod chrono {
1340 pub struct DateTime<T>(std::marker::PhantomData<T>);
1341 pub struct Utc;
1342
1343 impl Utc {
1344 pub fn now() -> DateTime<Utc> {
1345 DateTime(std::marker::PhantomData)
1346 }
1347 }
1348
1349 impl<T> std::fmt::Display for DateTime<T> {
1350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1351 write!(f, "2024-01-01T00:00:00Z")
1352 }
1353 }
1354}
1355
1356#[allow(non_snake_case)]
1361#[cfg(test)]
1362mod tests {
1363 use super::*;
1364 use crate::api_data_structures::{FieldInfo, MethodInfo, TypeKind, Visibility};
1365
1366 #[test]
1367 fn test_document_formatter_json() {
1368 let formatter = DocumentFormatter::new(GeneratorConfig::new());
1369 let api_ref = create_test_api_reference();
1370
1371 let json = formatter.format_json(&api_ref).unwrap();
1372 assert!(json.contains("test-crate"));
1373 assert!(json.contains("traits"));
1374 }
1375
1376 #[test]
1377 fn test_document_formatter_html() {
1378 let formatter = DocumentFormatter::new(GeneratorConfig::new());
1379 let api_ref = create_test_api_reference();
1380
1381 let html = formatter.format_html(&api_ref).unwrap();
1382 assert!(html.contains("<html"));
1383 assert!(html.contains("API Reference for test-crate"));
1384 assert!(html.contains("</html>"));
1385 }
1386
1387 #[test]
1388 fn test_document_formatter_markdown() {
1389 let formatter = DocumentFormatter::new(GeneratorConfig::new());
1390 let api_ref = create_test_api_reference();
1391
1392 let md = formatter.format_markdown(&api_ref).unwrap();
1393 assert!(md.contains("# API Reference - test-crate"));
1394 assert!(md.contains("## Table of Contents"));
1395 }
1396
1397 #[test]
1398 fn test_document_formatter_interactive() {
1399 let formatter = DocumentFormatter::new(GeneratorConfig::new());
1400 let api_ref = create_test_api_reference();
1401
1402 let interactive = formatter.format_interactive(&api_ref).unwrap();
1403 assert!(interactive.contains("Interactive API Reference"));
1404 assert!(interactive.contains("code-editor"));
1405 assert!(interactive.contains("monaco-editor"));
1406 }
1407
1408 #[test]
1409 fn test_document_formatter_openapi() {
1410 let formatter = DocumentFormatter::new(GeneratorConfig::new());
1411 let api_ref = create_test_api_reference();
1412
1413 let openapi = formatter.format_openapi(&api_ref).unwrap();
1414 assert!(openapi.contains("openapi: 3.0.3"));
1415 assert!(openapi.contains("test-crate API"));
1416 }
1417
1418 #[test]
1419 fn test_api_reference_generator() {
1420 let config = GeneratorConfig::new();
1421 let mut generator = ApiReferenceGenerator::new(config);
1422
1423 let api_ref = generator.generate_from_crate("test-crate").unwrap();
1424 assert_eq!(api_ref.crate_name, "test-crate");
1425 assert!(!api_ref.traits.is_empty());
1426 assert!(!api_ref.examples.is_empty());
1427 }
1428
1429 #[test]
1430 fn test_format_output() {
1431 let config = GeneratorConfig::new().with_output_format(OutputFormat::Html);
1432 let generator = ApiReferenceGenerator::new(config);
1433 let api_ref = create_test_api_reference();
1434
1435 let output = generator.format_output(&api_ref).unwrap();
1436 assert!(output.contains("<html"));
1437 }
1438
1439 #[test]
1440 fn test_format_as() {
1441 let generator = ApiReferenceGenerator::new(GeneratorConfig::new());
1442 let api_ref = create_test_api_reference();
1443
1444 let json = generator.format_as(&api_ref, OutputFormat::Json).unwrap();
1445 assert!(json.contains("test-crate"));
1446
1447 let html = generator.format_as(&api_ref, OutputFormat::Html).unwrap();
1448 assert!(html.contains("<html"));
1449
1450 let md = generator
1451 .format_as(&api_ref, OutputFormat::Markdown)
1452 .unwrap();
1453 assert!(md.contains("# API Reference"));
1454 }
1455
1456 #[test]
1457 fn test_string_ext() {
1458 assert_eq!("hello world".to_title_case(), "Hello World");
1459 assert_eq!("HELLO WORLD".to_title_case(), "Hello World");
1460 assert_eq!("helloWorld".to_title_case(), "Helloworld");
1461 }
1462
1463 fn create_test_api_reference() -> ApiReference {
1464 ApiReference {
1465 crate_name: "test-crate".to_string(),
1466 version: "1.0.0".to_string(),
1467 traits: vec![TraitInfo {
1468 name: "TestTrait".to_string(),
1469 description: "A test trait".to_string(),
1470 path: "test::TestTrait".to_string(),
1471 generics: Vec::new(),
1472 associated_types: Vec::new(),
1473 methods: vec![MethodInfo {
1474 name: "test_method".to_string(),
1475 signature: "fn test_method(&self)".to_string(),
1476 description: "A test method".to_string(),
1477 parameters: Vec::new(),
1478 return_type: "()".to_string(),
1479 required: true,
1480 }],
1481 supertraits: Vec::new(),
1482 implementations: Vec::new(),
1483 }],
1484 types: vec![TypeInfo {
1485 name: "TestType".to_string(),
1486 description: "A test type".to_string(),
1487 path: "test::TestType".to_string(),
1488 kind: TypeKind::Struct,
1489 generics: Vec::new(),
1490 fields: vec![FieldInfo {
1491 name: "field".to_string(),
1492 field_type: "String".to_string(),
1493 description: "A test field".to_string(),
1494 visibility: Visibility::Public,
1495 }],
1496 trait_impls: Vec::new(),
1497 }],
1498 examples: vec![CodeExample {
1499 title: "Test Example".to_string(),
1500 description: "A test example".to_string(),
1501 code: "fn main() { println!(\"Hello, test!\"); }".to_string(),
1502 language: "rust".to_string(),
1503 runnable: true,
1504 expected_output: Some("Hello, test!".to_string()),
1505 }],
1506 cross_references: HashMap::new(),
1507 metadata: ApiMetadata::default(),
1508 }
1509 }
1510}