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/
|
||||
.vscode/
|
||||
*.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
|
||||
```
|
||||
|
||||
### 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`.
|
||||
|
@ -127,4 +143,4 @@ This server implements the Model Context Protocol (MCP) which allows it to be ea
|
|||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
MIT License
|
||||
|
|
|
@ -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,
|
||||
|
@ -82,9 +90,21 @@ async fn main() -> Result<()> {
|
|||
item_path,
|
||||
query,
|
||||
version,
|
||||
limit,
|
||||
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 ---");
|
||||
},
|
||||
_ => println!("Received non-text content"),
|
||||
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()
|
||||
}
|
||||
},
|
||||
"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