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
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -21,4 +21,5 @@ Thumbs.db | ||||||
| .idea/ | .idea/ | ||||||
| .vscode/ | .vscode/ | ||||||
| *.swp | *.swp | ||||||
| *.swo | *.swo | ||||||
|  | output_tests | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
										
									
									
									
								
							|  | @ -38,16 +38,32 @@ cargo run --bin cratedocs http --address 0.0.0.0:3000 | ||||||
| cargo run --bin cratedocs http --debug | 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 | ```bash | ||||||
| # STDIN/STDOUT Mode | # Get help for the test command | ||||||
| cargo run --bin stdio-server | cargo run --bin cratedocs test --tool help | ||||||
| 
 | 
 | ||||||
| # HTTP/SSE Mode | # Look up crate documentation | ||||||
| cargo run --bin axum-docs | 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`. | By default, the HTTP server will listen on `http://127.0.0.1:8080/sse`. | ||||||
|  | @ -127,4 +143,4 @@ This server implements the Model Context Protocol (MCP) which allows it to be ea | ||||||
| 
 | 
 | ||||||
| ## License | ## License | ||||||
| 
 | 
 | ||||||
| MIT License | MIT License | ||||||
|  |  | ||||||
|  | @ -63,6 +63,14 @@ enum Commands { | ||||||
|         #[arg(long)] |         #[arg(long)] | ||||||
|         limit: Option<u32>, |         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
 |         /// Enable debug logging
 | ||||||
|         #[arg(short, long)] |         #[arg(short, long)] | ||||||
|         debug: bool, |         debug: bool, | ||||||
|  | @ -82,9 +90,21 @@ async fn main() -> Result<()> { | ||||||
|             item_path, 
 |             item_path, 
 | ||||||
|             query, 
 |             query, 
 | ||||||
|             version, 
 |             version, 
 | ||||||
|             limit, 
 |             limit, | ||||||
|  |             format, | ||||||
|  |             output, | ||||||
|             debug 
 |             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(()) |     Ok(()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Run a direct test of a documentation tool from the CLI
 | /// Configuration for the test tool
 | ||||||
| async fn run_test_tool( | struct TestToolConfig { | ||||||
|     tool: String, |     tool: String, | ||||||
|     crate_name: Option<String>, |     crate_name: Option<String>, | ||||||
|     item_path: Option<String>, |     item_path: Option<String>, | ||||||
|     query: Option<String>, |     query: Option<String>, | ||||||
|     version: Option<String>, |     version: Option<String>, | ||||||
|     limit: Option<u32>, |     limit: Option<u32>, | ||||||
|  |     format: Option<String>, | ||||||
|  |     output: Option<String>, | ||||||
|     debug: bool, |     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"
 |     // Print help information if the tool is "help"
 | ||||||
|     if tool == "help" { |     if tool == "help" { | ||||||
|         println!("CrateDocs CLI Tool Tester\n"); |         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_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 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 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!("  cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5"); | ||||||
|         println!("Available tools:"); |         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_crate   - Look up documentation for a Rust crate"); | ||||||
|         println!("  lookup_item    - Look up documentation for a specific item in a 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!("                   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!("                   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!("  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(()); |         return Ok(()); | ||||||
|     } |     } | ||||||
|  |     
 | ||||||
|     // Set up console logging
 |     // Set up console logging
 | ||||||
|     let level = if debug { tracing::Level::DEBUG } else { tracing::Level::INFO }; |     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); |     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
 |     // Prepare arguments based on the tool being tested
 | ||||||
|     let arguments = match tool.as_str() { |     let arguments = match tool.as_str() { | ||||||
|         "lookup_crate" => { |         "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: 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 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 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"); |             eprintln!("  - For help: cargo run --bin cratedocs -- test --tool help"); | ||||||
|             return Ok(()); |             return Ok(()); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     
 |     
 | ||||||
|     // Print results
 |     // Process and output results
 | ||||||
|     if !result.is_empty() { |     if !result.is_empty() { | ||||||
|         for content in result { |         for content in result { | ||||||
|             match content { |             if let Content::Text(text) = content { | ||||||
|                 Content::Text(text) => { |                 let content_str = text.text; | ||||||
|                     println!("\n--- TOOL RESULT ---\n"); |                 let formatted_output = match format.as_str() { | ||||||
|                     // Access the raw string from TextContent.text field
 |                     "json" => { | ||||||
|                     println!("{}", text.text); |                         // For search_crates, which may return JSON content
 | ||||||
|                     println!("\n--- END RESULT ---"); |                         if tool == "search_crates" && content_str.trim().starts_with('{') { | ||||||
|                 }, |                             // If content is already valid JSON, pretty print it
 | ||||||
|                 _ => println!("Received non-text content"), |                             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() | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     "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 { |     } else { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Danielle Jenkins
						Danielle Jenkins