Skip to content

Commit faa44e7

Browse files
--tldr checkpoint 1
1 parent dca96d2 commit faa44e7

File tree

4 files changed

+110
-14
lines changed

4 files changed

+110
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ Thumbs.db
2323
*.swp
2424
*.swo
2525
output_tests
26+
target/*

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
[package]
22
name = "cratedocs-mcp"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
description = "Rust Documentation MCP Server for LLM crate assistance"
6-
authors = ["Claude <[email protected]>"]
6+
authors = ["Brian Horakh <[email protected]>",
7+
"Claude <[email protected]>"]
78
license = "MIT"
89
repository = "https://github.com/d6e/cratedocs-mcp"
910

@@ -42,6 +43,7 @@ futures = "0.3"
4243
rand = "0.8"
4344
clap = { version = "4.4", features = ["derive"] }
4445
html2md = "0.2.14"
46+
regex = "1"
4547

4648
[dev-dependencies]
4749
# Testing utilities

src/bin/cratedocs.rs

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ use std::net::SocketAddr;
99
use tokio::io::{stdin, stdout};
1010
use tracing_appender::rolling::{RollingFileAppender, Rotation};
1111
use tracing_subscriber::{self, EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
12+
use regex::Regex;
1213

1314
#[derive(Parser)]
14-
#[command(author, version = "0.1.0", about, long_about = None)]
15+
#[command(author, version = "0.2.0", about, long_about = None)]
1516
#[command(propagate_version = true)]
1617
#[command(disable_version_flag = true)]
1718
struct Cli {
@@ -70,6 +71,10 @@ enum Commands {
7071
/// Output file path (if not specified, results will be printed to stdout)
7172
#[arg(long)]
7273
output: Option<String>,
74+
75+
/// Summarize output by stripping LICENSE and VERSION sections (TL;DR mode)
76+
#[arg(long)]
77+
tldr: bool,
7378

7479
/// Enable debug logging
7580
#[arg(short, long)]
@@ -84,16 +89,17 @@ async fn main() -> Result<()> {
8489
match cli.command {
8590
Commands::Stdio { debug } => run_stdio_server(debug).await,
8691
Commands::Http { address, debug } => run_http_server(address, debug).await,
87-
Commands::Test {
88-
tool,
89-
crate_name,
90-
item_path,
91-
query,
92-
version,
92+
Commands::Test {
93+
tool,
94+
crate_name,
95+
item_path,
96+
query,
97+
version,
9398
limit,
9499
format,
95100
output,
96-
debug
101+
tldr,
102+
debug
97103
} => run_test_tool(TestToolConfig {
98104
tool,
99105
crate_name,
@@ -103,6 +109,7 @@ async fn main() -> Result<()> {
103109
limit,
104110
format,
105111
output,
112+
tldr,
106113
debug
107114
}).await,
108115
}
@@ -163,6 +170,41 @@ async fn run_http_server(address: String, debug: bool) -> Result<()> {
163170
Ok(())
164171
}
165172

173+
// --- TLDR Helper Function ---
174+
fn apply_tldr(input: &str) -> String {
175+
// Remove LICENSE and VERSION(S) sections by skipping lines between those headings and the next heading or EOF.
176+
let mut output = Vec::new();
177+
let mut skip = false;
178+
179+
let license_re = Regex::new(r"(?i)^\s*#+\s*license\b").unwrap();
180+
let version_re = Regex::new(r"(?i)^\s*#+\s*version(s)?\b").unwrap();
181+
let heading_re = Regex::new(r"^\s*#+\s*\S+").unwrap();
182+
183+
let mut just_skipped_section = false;
184+
for line in input.lines() {
185+
// Start skipping if we hit a LICENSE or VERSION(S) heading
186+
if !skip && (license_re.is_match(line) || version_re.is_match(line)) {
187+
skip = true;
188+
just_skipped_section = true;
189+
continue; // skip the heading line itself
190+
}
191+
// If we just skipped a section heading, also skip blank lines and lines containing only "license" or "versions"
192+
if just_skipped_section && (line.trim().is_empty() || line.trim().eq_ignore_ascii_case("license") || line.trim().eq_ignore_ascii_case("versions") || line.trim().eq_ignore_ascii_case("version")) {
193+
continue;
194+
}
195+
// Stop skipping at the next heading (but do not skip the heading itself)
196+
if skip && heading_re.is_match(line) {
197+
skip = false;
198+
just_skipped_section = false;
199+
}
200+
if !skip {
201+
output.push(line);
202+
}
203+
}
204+
// If the section to skip is at the end, skip will remain true and those lines will be omitted.
205+
output.join("\n")
206+
}
207+
166208
/// Configuration for the test tool
167209
struct TestToolConfig {
168210
tool: String,
@@ -173,6 +215,7 @@ struct TestToolConfig {
173215
limit: Option<u32>,
174216
format: Option<String>,
175217
output: Option<String>,
218+
tldr: bool,
176219
debug: bool,
177220
}
178221

@@ -187,6 +230,7 @@ async fn run_test_tool(config: TestToolConfig) -> Result<()> {
187230
limit,
188231
format,
189232
output,
233+
tldr,
190234
debug,
191235
} = config;
192236
// Print help information if the tool is "help"
@@ -210,6 +254,7 @@ async fn run_test_tool(config: TestToolConfig) -> Result<()> {
210254
println!("\nOutput options:");
211255
println!(" --format - Output format: markdown (default), text, json");
212256
println!(" --output - Write output to a file instead of stdout");
257+
println!(" --tldr - Summarize output by stripping LICENSE and VERSION sections");
213258
return Ok(());
214259
}
215260

@@ -289,7 +334,13 @@ async fn run_test_tool(config: TestToolConfig) -> Result<()> {
289334
if !result.is_empty() {
290335
for content in result {
291336
if let Content::Text(text) = content {
292-
let content_str = text.text;
337+
let mut content_str = text.text;
338+
339+
// TL;DR processing: strip LICENSE and VERSION(S) sections if --tldr is set
340+
if tldr {
341+
content_str = apply_tldr(&content_str);
342+
}
343+
293344
let formatted_output = match format.as_str() {
294345
"json" => {
295346
// For search_crates, which may return JSON content
@@ -321,7 +372,7 @@ async fn run_test_tool(config: TestToolConfig) -> Result<()> {
321372
let description = crate_info.get("description").and_then(|v| v.as_str()).unwrap_or("No description");
322373
let downloads = crate_info.get("downloads").and_then(|v| v.as_u64()).unwrap_or(0);
323374

324-
text_output.push_str(&format!("{}. {} - {} (Downloads: {})\n",
375+
text_output.push_str(&format!("{}. {} - {} (Downloads: {})\n",
325376
i + 1, name, description, downloads));
326377
}
327378
text_output
@@ -384,4 +435,45 @@ async fn run_test_tool(config: TestToolConfig) -> Result<()> {
384435
}
385436

386437
Ok(())
387-
}
438+
}
439+
#[cfg(test)]
440+
mod tldr_tests {
441+
use super::apply_tldr;
442+
443+
#[test]
444+
fn test_apply_tldr_removes_license_and_versions() {
445+
let input = r#"
446+
# Versions
447+
This is version info.
448+
449+
# LICENSE
450+
MIT License text.
451+
452+
# Usage
453+
Some real documentation here.
454+
455+
# Another Section
456+
More docs.
457+
"#;
458+
let output = apply_tldr(input);
459+
assert!(!output.to_lowercase().contains("license"));
460+
assert!(!output.to_lowercase().contains("version"));
461+
assert!(output.contains("Usage"));
462+
assert!(output.contains("Another Section"));
463+
assert!(output.contains("Some real documentation here."));
464+
// Debug print for failure analysis
465+
if output.to_lowercase().contains("license") {
466+
println!("DEBUG OUTPUT:\n{}", output);
467+
}
468+
}
469+
470+
#[test]
471+
fn test_apply_tldr_handles_no_license_or_versions() {
472+
let input = r#"
473+
# Usage
474+
Some real documentation here.
475+
"#;
476+
let output = apply_tldr(input);
477+
assert_eq!(output.trim(), input.trim());
478+
}
479+
}

0 commit comments

Comments
 (0)