11use 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
33use 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]
5876name = "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
0 commit comments