@@ -63,6 +63,14 @@ enum Commands {
6363 #[ arg( long) ]
6464 limit : Option < u32 > ,
6565
66+ /// Output format (markdown, text, json)
67+ #[ arg( long, default_value = "markdown" ) ]
68+ format : Option < String > ,
69+
70+ /// Output file path (if not specified, results will be printed to stdout)
71+ #[ arg( long) ]
72+ output : Option < String > ,
73+
6674 /// Enable debug logging
6775 #[ arg( short, long) ]
6876 debug : bool ,
@@ -82,9 +90,21 @@ async fn main() -> Result<()> {
8290 item_path,
8391 query,
8492 version,
85- limit,
93+ limit,
94+ format,
95+ output,
8696 debug
87- } => run_test_tool ( tool, crate_name, item_path, query, version, limit, debug) . await ,
97+ } => run_test_tool ( TestToolConfig {
98+ tool,
99+ crate_name,
100+ item_path,
101+ query,
102+ version,
103+ limit,
104+ format,
105+ output,
106+ debug
107+ } ) . await ,
88108 }
89109}
90110
@@ -143,16 +163,32 @@ async fn run_http_server(address: String, debug: bool) -> Result<()> {
143163 Ok ( ( ) )
144164}
145165
146- /// Run a direct test of a documentation tool from the CLI
147- async fn run_test_tool (
166+ /// Configuration for the test tool
167+ struct TestToolConfig {
148168 tool : String ,
149169 crate_name : Option < String > ,
150170 item_path : Option < String > ,
151171 query : Option < String > ,
152172 version : Option < String > ,
153173 limit : Option < u32 > ,
174+ format : Option < String > ,
175+ output : Option < String > ,
154176 debug : bool ,
155- ) -> Result < ( ) > {
177+ }
178+
179+ /// Run a direct test of a documentation tool from the CLI
180+ async fn run_test_tool ( config : TestToolConfig ) -> Result < ( ) > {
181+ let TestToolConfig {
182+ tool,
183+ crate_name,
184+ item_path,
185+ query,
186+ version,
187+ limit,
188+ format,
189+ output,
190+ debug,
191+ } = config;
156192 // Print help information if the tool is "help"
157193 if tool == "help" {
158194 println ! ( "CrateDocs CLI Tool Tester\n " ) ;
@@ -161,16 +197,22 @@ async fn run_test_tool(
161197 println ! ( " cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --version 1.35.0" ) ;
162198 println ! ( " cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender" ) ;
163199 println ! ( " cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147" ) ;
164- println ! ( " cargo run --bin cratedocs -- test --tool search_crates --query logger\n " ) ;
165- println ! ( "Available tools:" ) ;
200+ println ! ( " cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5" ) ;
201+ println ! ( " cargo run --bin cratedocs -- test --tool search_crates --query logger --format json" ) ;
202+ println ! ( " cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --output tokio-docs.md" ) ;
203+ println ! ( "\n Available tools:" ) ;
166204 println ! ( " lookup_crate - Look up documentation for a Rust crate" ) ;
167205 println ! ( " lookup_item - Look up documentation for a specific item in a crate" ) ;
168206 println ! ( " Format: 'module::path::ItemName' (e.g., 'sync::mpsc::Sender')" ) ;
169207 println ! ( " The tool will try to detect if it's a struct, enum, trait, fn, or macro" ) ;
170208 println ! ( " search_crates - Search for crates on crates.io" ) ;
171- println ! ( " help - Show this help information\n " ) ;
209+ println ! ( " help - Show this help information" ) ;
210+ println ! ( "\n Output options:" ) ;
211+ println ! ( " --format - Output format: markdown (default), text, json" ) ;
212+ println ! ( " --output - Write output to a file instead of stdout" ) ;
172213 return Ok ( ( ) ) ;
173214 }
215+
174216 // Set up console logging
175217 let level = if debug { tracing:: Level :: DEBUG } else { tracing:: Level :: INFO } ;
176218
@@ -185,6 +227,9 @@ async fn run_test_tool(
185227
186228 tracing:: info!( "Testing tool: {}" , tool) ;
187229
230+ // Get format option (default to markdown)
231+ let format = format. unwrap_or_else ( || "markdown" . to_string ( ) ) ;
232+
188233 // Prepare arguments based on the tool being tested
189234 let arguments = match tool. as_str ( ) {
190235 "lookup_crate" => {
@@ -233,22 +278,105 @@ async fn run_test_tool(
233278 eprintln ! ( " - For item lookup: cargo run --bin cratedocs -- test --tool lookup_item --crate-name tokio --item-path sync::mpsc::Sender" ) ;
234279 eprintln ! ( " - For item lookup with version: cargo run --bin cratedocs -- test --tool lookup_item --crate-name serde --item-path Serialize --version 1.0.147" ) ;
235280 eprintln ! ( " - For crate search: cargo run --bin cratedocs -- test --tool search_crates --query logger --limit 5" ) ;
281+ eprintln ! ( " - For output format: cargo run --bin cratedocs -- test --tool search_crates --query logger --format json" ) ;
282+ eprintln ! ( " - For file output: cargo run --bin cratedocs -- test --tool lookup_crate --crate-name tokio --output tokio-docs.md" ) ;
236283 eprintln ! ( " - For help: cargo run --bin cratedocs -- test --tool help" ) ;
237284 return Ok ( ( ) ) ;
238285 }
239286 } ;
240287
241- // Print results
288+ // Process and output results
242289 if !result. is_empty ( ) {
243290 for content in result {
244- match content {
245- Content :: Text ( text) => {
246- println ! ( "\n --- TOOL RESULT ---\n " ) ;
247- // Access the raw string from TextContent.text field
248- println ! ( "{}" , text. text) ;
249- println ! ( "\n --- END RESULT ---" ) ;
250- } ,
251- _ => println ! ( "Received non-text content" ) ,
291+ if let Content :: Text ( text) = content {
292+ let content_str = text. text ;
293+ let formatted_output = match format. as_str ( ) {
294+ "json" => {
295+ // For search_crates, which may return JSON content
296+ if tool == "search_crates" && content_str. trim ( ) . starts_with ( '{' ) {
297+ // If content is already valid JSON, pretty print it
298+ match serde_json:: from_str :: < serde_json:: Value > ( & content_str) {
299+ Ok ( json_value) => serde_json:: to_string_pretty ( & json_value)
300+ . unwrap_or_else ( |_| content_str. clone ( ) ) ,
301+ Err ( _) => {
302+ // If it's not JSON, wrap it in a simple JSON object
303+ json ! ( { "content" : content_str } ) . to_string ( )
304+ }
305+ }
306+ } else {
307+ // For non-JSON content, wrap in a JSON object
308+ json ! ( { "content" : content_str } ) . to_string ( )
309+ }
310+ } ,
311+ "text" => {
312+ // For JSON content, try to extract plain text
313+ if content_str. trim ( ) . starts_with ( '{' ) && tool == "search_crates" {
314+ match serde_json:: from_str :: < serde_json:: Value > ( & content_str) {
315+ Ok ( json_value) => {
316+ // Try to create a simple text representation of search results
317+ if let Some ( crates) = json_value. get ( "crates" ) . and_then ( |v| v. as_array ( ) ) {
318+ let mut text_output = String :: from ( "Search Results:\n \n " ) ;
319+ for ( i, crate_info) in crates. iter ( ) . enumerate ( ) {
320+ let name = crate_info. get ( "name" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "Unknown" ) ;
321+ let description = crate_info. get ( "description" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "No description" ) ;
322+ let downloads = crate_info. get ( "downloads" ) . and_then ( |v| v. as_u64 ( ) ) . unwrap_or ( 0 ) ;
323+
324+ text_output. push_str ( & format ! ( "{}. {} - {} (Downloads: {})\n " ,
325+ i + 1 , name, description, downloads) ) ;
326+ }
327+ text_output
328+ } else {
329+ content_str
330+ }
331+ } ,
332+ Err ( _) => content_str,
333+ }
334+ } else {
335+ // For markdown content, use a simple approach to convert to plain text
336+ // This is a very basic conversion - more sophisticated would need a proper markdown parser
337+ content_str
338+ . replace ( "# " , "" )
339+ . replace ( "## " , "" )
340+ . replace ( "### " , "" )
341+ . replace ( "#### " , "" )
342+ . replace ( "##### " , "" )
343+ . replace ( "###### " , "" )
344+ . replace ( "**" , "" )
345+ . replace ( "*" , "" )
346+ . replace ( "`" , "" )
347+ }
348+ } ,
349+ _ => content_str, // Default to original markdown for "markdown" or any other format
350+ } ;
351+
352+ // Output to file or stdout
353+ match & output {
354+ Some ( file_path) => {
355+ use std:: fs;
356+ use std:: io:: Write ;
357+
358+ tracing:: info!( "Writing output to file: {}" , file_path) ;
359+
360+ // Ensure parent directory exists
361+ if let Some ( parent) = std:: path:: Path :: new ( file_path) . parent ( ) {
362+ if !parent. exists ( ) {
363+ fs:: create_dir_all ( parent) ?;
364+ }
365+ }
366+
367+ let mut file = fs:: File :: create ( file_path) ?;
368+ file. write_all ( formatted_output. as_bytes ( ) ) ?;
369+ println ! ( "Results written to file: {}" , file_path) ;
370+ } ,
371+ None => {
372+ // Print to stdout
373+ println ! ( "\n --- TOOL RESULT ---\n " ) ;
374+ println ! ( "{}" , formatted_output) ;
375+ println ! ( "\n --- END RESULT ---" ) ;
376+ }
377+ }
378+ } else {
379+ println ! ( "Received non-text content" ) ;
252380 }
253381 }
254382 } else {
0 commit comments