Skip to content

Commit 4cc9eeb

Browse files
committed
feat: add feature flags to args
1 parent c4ef2b9 commit 4cc9eeb

File tree

5 files changed

+206
-103
lines changed

5 files changed

+206
-103
lines changed

Cargo.lock

Lines changed: 21 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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rustdocs_mcp_server"
3-
version = "1.0.0"
3+
version = "1.1.0"
44
edition = "2024"
55

66
[dependencies]
@@ -23,6 +23,7 @@ cargo = { version = "0.86.0", default-features = false, features = ["vendored-op
2323
tempfile = "3.19.1"
2424
anyhow = "1.0.97"
2525
schemars = "0.8.22"
26+
clap = { version = "4.5.34", features = ["cargo", "derive", "env"] }
2627

2728

2829
# --- Platform Specific Dependencies ---

src/doc_loader.rs

Lines changed: 87 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use scraper::{Html, Selector};
2-
use std::{fs::{self, File, create_dir_all}, io::Write}; // Added File, create_dir_all, and Write
2+
use std::{fs::{self, File, create_dir_all}, io::Write, path::PathBuf}; // Added PathBuf
33
use cargo::core::resolver::features::CliFeatures;
44
// use cargo::core::SourceId; // Removed unused import
55
// use cargo::util::Filesystem; // Removed unused import
@@ -25,6 +25,8 @@ pub enum DocLoaderError {
2525
TempDirCreationFailed(std::io::Error),
2626
#[error("Cargo library error: {0}")]
2727
CargoLib(#[from] AnyhowError), // Re-add CargoLib variant
28+
#[error("Failed to strip prefix '{prefix}' from path '{path}': {source}")] // Improved error
29+
StripPrefix { prefix: PathBuf, path: PathBuf, source: std::path::StripPrefixError },
2830
}
2931

3032
// Simple struct to hold document content, maybe add path later if needed
@@ -37,22 +39,38 @@ pub struct Document {
3739
/// Generates documentation for a given crate in a temporary directory,
3840
/// then loads and parses the HTML documents.
3941
/// Extracts text content from the main content area of rustdoc generated HTML.
40-
pub fn load_documents(crate_name: &str, crate_version_req: &str) -> Result<Vec<Document>, DocLoaderError> { // Use crate_version_req
41-
eprintln!("[DEBUG] load_documents called with crate_name: '{}', crate_version_req: '{}'", crate_name, crate_version_req); // Update log
42+
pub fn load_documents(
43+
crate_name: &str,
44+
crate_version_req: &str,
45+
features: Option<&Vec<String>>, // Add optional features parameter
46+
) -> Result<Vec<Document>, DocLoaderError> {
47+
eprintln!(
48+
"[DEBUG] load_documents called with crate_name: '{}', crate_version_req: '{}', features: {:?}",
49+
crate_name, crate_version_req, features
50+
);
4251
let mut documents = Vec::new();
4352

4453
let temp_dir = tempdir().map_err(DocLoaderError::TempDirCreationFailed)?;
4554
let temp_dir_path = temp_dir.path();
4655
let temp_manifest_path = temp_dir_path.join("Cargo.toml");
4756

4857
eprintln!(
49-
"Generating documentation for crate '{}' (Version Req: '{}') in temporary directory: {}", // Update log message
58+
"Generating documentation for crate '{}' (Version Req: '{}', Features: {:?}) in temporary directory: {}",
5059
crate_name,
5160
crate_version_req,
61+
features, // Log features
5262
temp_dir_path.display()
5363
);
5464

55-
// Create a temporary Cargo.toml using the version requirement
65+
// Create a temporary Cargo.toml using the version requirement and features
66+
let features_string = features
67+
.filter(|f| !f.is_empty()) // Only add features if provided and not empty
68+
.map(|f| {
69+
let feature_list = f.iter().map(|feat| format!("\"{}\"", feat)).collect::<Vec<_>>().join(", ");
70+
format!(", features = [{}]", feature_list)
71+
})
72+
.unwrap_or_default(); // Use empty string if no features
73+
5674
let cargo_toml_content = format!(
5775
r#"[package]
5876
name = "temp-doc-crate"
@@ -62,9 +80,9 @@ edition = "2021"
6280
[lib] # Add an empty lib target to satisfy Cargo
6381
6482
[dependencies]
65-
{} = "{}"
83+
{} = {{ version = "{}"{} }}
6684
"#,
67-
crate_name, crate_version_req // Use the version requirement string here
85+
crate_name, crate_version_req, features_string // Use the version requirement string and features string here
6886
);
6987

7088
// Create the src directory and an empty lib.rs file
@@ -76,14 +94,15 @@ edition = "2021"
7694
let mut temp_manifest_file = File::create(&temp_manifest_path)?;
7795
temp_manifest_file.write_all(cargo_toml_content.as_bytes())?;
7896
eprintln!("[DEBUG] Created temporary manifest at: {}", temp_manifest_path.display());
97+
eprintln!("[DEBUG] Temporary Manifest Content:\n{}", cargo_toml_content); // Log content
7998

8099

81100
// --- Use Cargo API ---
82101
let mut config = GlobalContext::default()?; // Make mutable
83-
// Configure context for quiet operation
102+
// Configure context (set quiet to false for more detailed errors)
84103
config.configure(
85104
0, // verbose
86-
true, // quiet
105+
true, // quiet
87106
None, // color
88107
false, // frozen
89108
false, // locked
@@ -105,7 +124,7 @@ edition = "2021"
105124
let mut compile_opts = CompileOptions::new(&config, cargo::core::compiler::CompileMode::Doc { deps: false, json: false })?;
106125
// Specify the package explicitly
107126
let package_spec = crate_name.to_string(); // Just use name (with underscores)
108-
compile_opts.cli_features = CliFeatures::new_all(false); // Use new_all(false)
127+
compile_opts.cli_features = CliFeatures::new_all(false); // Use new_all(false) - applies to the temp crate, not dependency
109128
compile_opts.spec = Packages::Packages(vec![package_spec.clone()]); // Clone spec
110129

111130
// Create DocOptions: Pass compile options
@@ -116,27 +135,64 @@ edition = "2021"
116135
};
117136
eprintln!("[DEBUG] package_spec for CompileOptions: '{}'", package_spec);
118137

119-
ops::doc(&ws, &doc_opts).map_err(DocLoaderError::CargoLib)?; // Use ws
120-
// --- End Cargo API ---
121-
// Construct the path to the generated documentation within the temp directory
122-
// Cargo uses underscores in the directory path if the crate name has hyphens
123-
let crate_name_underscores = crate_name.replace('-', "_");
124-
let docs_path = temp_dir_path.join("doc").join(&crate_name_underscores);
125-
126138
// Debug print relevant options before calling ops::doc
127139
eprintln!("[DEBUG] CompileOptions spec: {:?}", doc_opts.compile_opts.spec);
128-
eprintln!("[DEBUG] CompileOptions cli_features: {:?}", doc_opts.compile_opts.cli_features);
140+
eprintln!("[DEBUG] CompileOptions cli_features: {:?}", doc_opts.compile_opts.cli_features); // Features for temp crate
129141
eprintln!("[DEBUG] CompileOptions build_config mode: {:?}", doc_opts.compile_opts.build_config.mode);
130142
eprintln!("[DEBUG] DocOptions output_format: {:?}", doc_opts.output_format);
131-
if !docs_path.exists() || !docs_path.is_dir() {
132-
return Err(DocLoaderError::CargoLib(anyhow::anyhow!(
133-
"Generated documentation not found at expected path: {}. Check crate name and cargo doc output.",
134-
docs_path.display()
135-
)));
143+
144+
ops::doc(&ws, &doc_opts).map_err(DocLoaderError::CargoLib)?; // Use ws
145+
// --- End Cargo API ---
146+
147+
// --- Find the actual documentation directory ---
148+
// Iterate through subdirectories in `target/doc` and find the one containing `index.html`.
149+
let base_doc_path = temp_dir_path.join("doc");
150+
eprintln!("[DEBUG] Base doc path: {}", base_doc_path.display());
151+
152+
let mut target_docs_path: Option<PathBuf> = None;
153+
let mut found_count = 0;
154+
155+
if base_doc_path.is_dir() {
156+
for entry_result in fs::read_dir(&base_doc_path)? {
157+
let entry = entry_result?;
158+
eprintln!("[DEBUG] Checking directory entry: {}", entry.path().display()); // Log entry being checked
159+
if entry.file_type()?.is_dir() {
160+
let dir_path = entry.path();
161+
let index_html_path = dir_path.join("index.html");
162+
if index_html_path.is_file() {
163+
eprintln!("[DEBUG] Found potential docs directory with index.html: {}", dir_path.display());
164+
if target_docs_path.is_none() {
165+
target_docs_path = Some(dir_path);
166+
}
167+
found_count += 1;
168+
} else {
169+
eprintln!("[DEBUG] Skipping directory without index.html: {}", dir_path.display());
170+
}
171+
}
172+
}
136173
}
137-
eprintln!("Generated documentation path: {}", docs_path.display());
138174

139-
eprintln!("[DEBUG] ops::doc called successfully.");
175+
let docs_path = match (found_count, target_docs_path) {
176+
(1, Some(path)) => {
177+
eprintln!("[DEBUG] Confirmed unique documentation directory: {}", path.display());
178+
path
179+
},
180+
(0, _) => {
181+
return Err(DocLoaderError::CargoLib(anyhow::anyhow!(
182+
"Could not find any subdirectory containing index.html within '{}'. Cargo doc might have failed or produced unexpected output.",
183+
base_doc_path.display()
184+
)));
185+
},
186+
(count, _) => {
187+
return Err(DocLoaderError::CargoLib(anyhow::anyhow!(
188+
"Expected exactly one subdirectory containing index.html within '{}', but found {}. Cannot determine the correct documentation path.",
189+
base_doc_path.display(), count
190+
)));
191+
}
192+
};
193+
// --- End finding documentation directory ---
194+
195+
eprintln!("Using documentation path: {}", docs_path.display()); // Log the path we are actually using
140196

141197
// Define the CSS selector for the main content area in rustdoc HTML
142198
// This might need adjustment based on the exact rustdoc version/theme
@@ -145,7 +201,6 @@ edition = "2021"
145201
eprintln!("[DEBUG] Calculated final docs_path: {}", docs_path.display());
146202

147203
eprintln!("Starting document loading from: {}", docs_path.display());
148-
eprintln!("[DEBUG] docs_path does not exist or is not a directory.");
149204

150205
for entry in WalkDir::new(&docs_path)
151206
.into_iter()
@@ -155,11 +210,12 @@ edition = "2021"
155210
let path = entry.path();
156211
// Calculate path relative to the docs_path root
157212
let relative_path = path.strip_prefix(&docs_path).map_err(|e| {
158-
// Provide more context in the error message
159-
DocLoaderError::Io(std::io::Error::new(
160-
std::io::ErrorKind::Other,
161-
format!("Failed to strip prefix '{}' from path '{}': {}", docs_path.display(), path.display(), e)
162-
))
213+
// Provide more context in the error message using the new error variant
214+
DocLoaderError::StripPrefix {
215+
prefix: docs_path.to_path_buf(),
216+
path: path.to_path_buf(),
217+
source: e,
218+
}
163219
})?;
164220
let path_str = relative_path.to_string_lossy().to_string(); // Use the relative path
165221
// eprintln!("Processing file: {} (relative: {})", path.display(), path_str); // Updated debug log

src/error.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ use crate::doc_loader::DocLoaderError; // Need to import DocLoaderError from the
66
pub enum ServerError {
77
#[error("Environment variable not set: {0}")]
88
MissingEnvVar(String),
9-
#[error("Missing command line argument: {0}")]
10-
MissingArgument(String),
9+
// MissingArgument removed as clap handles this now
1110
#[error("Configuration Error: {0}")]
1211
Config(String),
1312

0 commit comments

Comments
 (0)