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