Add output formatting as a cli option
This commit is contained in:
		
							parent
							
								
									f50ac58a24
								
							
						
					
					
						commit
						704c7333b6
					
				
					 3 changed files with 170 additions and 25 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -22,3 +22,4 @@ Thumbs.db | |||
| .vscode/ | ||||
| *.swp | ||||
| *.swo | ||||
| output_tests | ||||
|  |  | |||
							
								
								
									
										28
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
										
									
									
									
								
							|  | @ -38,16 +38,32 @@ cargo run --bin cratedocs http --address 0.0.0.0:3000 | |||
| cargo run --bin cratedocs http --debug | ||||
| ``` | ||||
| 
 | ||||
| ### Legacy Commands | ||||
| ### Directly Testing Documentation Tools | ||||
| 
 | ||||
| For backward compatibility, you can still use the original binaries: | ||||
| You can directly test the documentation tools from the command line without starting a server: | ||||
| 
 | ||||
| ```bash | ||||
| # STDIN/STDOUT Mode | ||||
| cargo run --bin stdio-server | ||||
| # Get help for the test command | ||||
| cargo run --bin cratedocs test --tool help | ||||
| 
 | ||||
| # HTTP/SSE Mode | ||||
| cargo run --bin axum-docs | ||||
| # Look up crate documentation | ||||
| cargo run --bin cratedocs test --tool lookup_crate --crate-name tokio | ||||
| 
 | ||||
| # Look up item documentation | ||||
| cargo run --bin cratedocs test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender | ||||
| 
 | ||||
| # Look up documentation for a specific version | ||||
| cargo run --bin cratedocs test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147 | ||||
| 
 | ||||
| # Search for crates | ||||
| cargo run --bin cratedocs test --tool search_crates --query logger --limit 5 | ||||
| 
 | ||||
| # Output in different formats (markdown, text, json) | ||||
| cargo run --bin cratedocs test --tool search_crates --query logger --format json | ||||
| cargo run --bin cratedocs test --tool lookup_crate --crate-name tokio --format text | ||||
| 
 | ||||
| # Save output to a file | ||||
| cargo run --bin cratedocs test --tool lookup_crate --crate-name tokio --output tokio-docs.md | ||||
| ``` | ||||
| 
 | ||||
| By default, the HTTP server will listen on `http://127.0.0.1:8080/sse`. | ||||
|  |  | |||
|  | @ -63,6 +63,14 @@ enum Commands { | |||
|         #[arg(long)] | ||||
|         limit: Option<u32>, | ||||
|         
 | ||||
|         /// Output format (markdown, text, json)
 | ||||
|         #[arg(long, default_value = "markdown")] | ||||
|         format: Option<String>, | ||||
|         
 | ||||
|         /// Output file path (if not specified, results will be printed to stdout)
 | ||||
|         #[arg(long)] | ||||
|         output: Option<String>, | ||||
|         
 | ||||
|         /// Enable debug logging
 | ||||
|         #[arg(short, long)] | ||||
|         debug: bool, | ||||
|  | @ -83,8 +91,20 @@ async fn main() -> Result<()> { | |||
|             query, 
 | ||||
|             version, 
 | ||||
|             limit, | ||||
|             format, | ||||
|             output, | ||||
|             debug 
 | ||||
|         } => run_test_tool(tool, crate_name, item_path, query, version, limit, debug).await, | ||||
|         } => run_test_tool(TestToolConfig { | ||||
|             tool, | ||||
|             crate_name, | ||||
|             item_path, | ||||
|             query, | ||||
|             version, | ||||
|             limit, | ||||
|             format, | ||||
|             output, | ||||
|             debug | ||||
|         }).await, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -143,16 +163,32 @@ async fn run_http_server(address: String, debug: bool) -> Result<()> { | |||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// Run a direct test of a documentation tool from the CLI
 | ||||
| async fn run_test_tool( | ||||
| /// Configuration for the test tool
 | ||||
| struct TestToolConfig { | ||||
|     tool: String, | ||||
|     crate_name: Option<String>, | ||||
|     item_path: Option<String>, | ||||
|     query: Option<String>, | ||||
|     version: Option<String>, | ||||
|     limit: Option<u32>, | ||||
|     format: Option<String>, | ||||
|     output: Option<String>, | ||||
|     debug: bool, | ||||
| ) -> Result<()> { | ||||
| } | ||||
| 
 | ||||
| /// Run a direct test of a documentation tool from the CLI
 | ||||
| async fn run_test_tool(config: TestToolConfig) -> Result<()> { | ||||
|     let TestToolConfig { | ||||
|         tool, | ||||
|         crate_name, | ||||
|         item_path, | ||||
|         query, | ||||
|         version, | ||||
|         limit, | ||||
|         format, | ||||
|         output, | ||||
|         debug, | ||||
|     } = config; | ||||
|     // Print help information if the tool is "help"
 | ||||
|     if tool == "help" { | ||||
|         println!("CrateDocs CLI Tool Tester\n"); | ||||
|  | @ -161,16 +197,22 @@ async fn run_test_tool( | |||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --version 1.35.0"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool search_crates --query logger\n"); | ||||
|         println!("Available tools:"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool search_crates --query logger --format json"); | ||||
|         println!("  cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --output tokio-docs.md"); | ||||
|         println!("\nAvailable tools:"); | ||||
|         println!("  lookup_crate   - Look up documentation for a Rust crate"); | ||||
|         println!("  lookup_item    - Look up documentation for a specific item in a crate"); | ||||
|         println!("                   Format: 'module::path::ItemName' (e.g., 'sync::mpsc::Sender')"); | ||||
|         println!("                   The tool will try to detect if it's a struct, enum, trait, fn, or macro"); | ||||
|         println!("  search_crates  - Search for crates on crates.io"); | ||||
|         println!("  help           - Show this help information\n"); | ||||
|         println!("  help           - Show this help information"); | ||||
|         println!("\nOutput options:"); | ||||
|         println!("  --format       - Output format: markdown (default), text, json"); | ||||
|         println!("  --output       - Write output to a file instead of stdout"); | ||||
|         return Ok(()); | ||||
|     } | ||||
|     
 | ||||
|     // Set up console logging
 | ||||
|     let level = if debug { tracing::Level::DEBUG } else { tracing::Level::INFO }; | ||||
|     
 | ||||
|  | @ -185,6 +227,9 @@ async fn run_test_tool( | |||
|     
 | ||||
|     tracing::info!("Testing tool: {}", tool); | ||||
|     
 | ||||
|     // Get format option (default to markdown)
 | ||||
|     let format = format.unwrap_or_else(|| "markdown".to_string()); | ||||
|     
 | ||||
|     // Prepare arguments based on the tool being tested
 | ||||
|     let arguments = match tool.as_str() { | ||||
|         "lookup_crate" => { | ||||
|  | @ -233,22 +278,105 @@ async fn run_test_tool( | |||
|             eprintln!("  - For item lookup: cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender"); | ||||
|             eprintln!("  - For item lookup with version: cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147"); | ||||
|             eprintln!("  - For crate search: cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5"); | ||||
|             eprintln!("  - For output format: cargo run --bin cratedocs -- test --tool search_crates --query logger --format json"); | ||||
|             eprintln!("  - For file output: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --output tokio-docs.md"); | ||||
|             eprintln!("  - For help: cargo run --bin cratedocs -- test --tool help"); | ||||
|             return Ok(()); | ||||
|         } | ||||
|     }; | ||||
|     
 | ||||
|     // Print results
 | ||||
|     // Process and output results
 | ||||
|     if !result.is_empty() { | ||||
|         for content in result { | ||||
|             match content { | ||||
|                 Content::Text(text) => { | ||||
|                     println!("\n--- TOOL RESULT ---\n"); | ||||
|                     // Access the raw string from TextContent.text field
 | ||||
|                     println!("{}", text.text); | ||||
|                     println!("\n--- END RESULT ---"); | ||||
|             if let Content::Text(text) = content { | ||||
|                 let content_str = text.text; | ||||
|                 let formatted_output = match format.as_str() { | ||||
|                     "json" => { | ||||
|                         // For search_crates, which may return JSON content
 | ||||
|                         if tool == "search_crates" && content_str.trim().starts_with('{') { | ||||
|                             // If content is already valid JSON, pretty print it
 | ||||
|                             match serde_json::from_str::<serde_json::Value>(&content_str) { | ||||
|                                 Ok(json_value) => serde_json::to_string_pretty(&json_value) | ||||
|                                     .unwrap_or_else(|_| content_str.clone()), | ||||
|                                 Err(_) => { | ||||
|                                     // If it's not JSON, wrap it in a simple JSON object
 | ||||
|                                     json!({ "content": content_str }).to_string() | ||||
|                                 } | ||||
|                             } | ||||
|                         } else { | ||||
|                             // For non-JSON content, wrap in a JSON object
 | ||||
|                             json!({ "content": content_str }).to_string() | ||||
|                         } | ||||
|                     }, | ||||
|                 _ => println!("Received non-text content"), | ||||
|                     "text" => { | ||||
|                         // For JSON content, try to extract plain text
 | ||||
|                         if content_str.trim().starts_with('{') && tool == "search_crates" { | ||||
|                             match serde_json::from_str::<serde_json::Value>(&content_str) { | ||||
|                                 Ok(json_value) => { | ||||
|                                     // Try to create a simple text representation of search results
 | ||||
|                                     if let Some(crates) = json_value.get("crates").and_then(|v| v.as_array()) { | ||||
|                                         let mut text_output = String::from("Search Results:\n\n"); | ||||
|                                         for (i, crate_info) in crates.iter().enumerate() { | ||||
|                                             let name = crate_info.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown"); | ||||
|                                             let description = crate_info.get("description").and_then(|v| v.as_str()).unwrap_or("No description"); | ||||
|                                             let downloads = crate_info.get("downloads").and_then(|v| v.as_u64()).unwrap_or(0); | ||||
|                                             
 | ||||
|                                             text_output.push_str(&format!("{}. {} - {} (Downloads: {})\n", 
 | ||||
|                                                 i + 1, name, description, downloads)); | ||||
|                                         } | ||||
|                                         text_output | ||||
|                                     } else { | ||||
|                                         content_str | ||||
|                                     } | ||||
|                                 }, | ||||
|                                 Err(_) => content_str, | ||||
|                             } | ||||
|                         } else { | ||||
|                             // For markdown content, use a simple approach to convert to plain text
 | ||||
|                             // This is a very basic conversion - more sophisticated would need a proper markdown parser
 | ||||
|                             content_str | ||||
|                                 .replace("# ", "") | ||||
|                                 .replace("## ", "") | ||||
|                                 .replace("### ", "") | ||||
|                                 .replace("#### ", "") | ||||
|                                 .replace("##### ", "") | ||||
|                                 .replace("###### ", "") | ||||
|                                 .replace("**", "") | ||||
|                                 .replace("*", "") | ||||
|                                 .replace("`", "") | ||||
|                         } | ||||
|                     }, | ||||
|                     _ => content_str, // Default to original markdown for "markdown" or any other format
 | ||||
|                 }; | ||||
|                 
 | ||||
|                 // Output to file or stdout
 | ||||
|                 match &output { | ||||
|                     Some(file_path) => { | ||||
|                         use std::fs; | ||||
|                         use std::io::Write; | ||||
|                         
 | ||||
|                         tracing::info!("Writing output to file: {}", file_path); | ||||
|                         
 | ||||
|                         // Ensure parent directory exists
 | ||||
|                         if let Some(parent) = std::path::Path::new(file_path).parent() { | ||||
|                             if !parent.exists() { | ||||
|                                 fs::create_dir_all(parent)?; | ||||
|                             } | ||||
|                         } | ||||
|                         
 | ||||
|                         let mut file = fs::File::create(file_path)?; | ||||
|                         file.write_all(formatted_output.as_bytes())?; | ||||
|                         println!("Results written to file: {}", file_path); | ||||
|                     }, | ||||
|                     None => { | ||||
|                         // Print to stdout
 | ||||
|                         println!("\n--- TOOL RESULT ---\n"); | ||||
|                         println!("{}", formatted_output); | ||||
|                         println!("\n--- END RESULT ---"); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 println!("Received non-text content"); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Danielle Jenkins
						Danielle Jenkins