willbe/action/
crate_doc.rs1mod private
3{
4 #[ allow( clippy::wildcard_imports ) ]
5 use crate::*;
6
7 use process_tools::process;
8 use error::
9 {
10 untyped::Context,
11 typed::Error,
12 ErrWith,
13 };
14 use core::fmt;
15 use std::
16 {
17 ffi::OsString,
18 fs,
19 path::PathBuf,
20 };
21 use collection_tools::HashMap;
22 use toml_edit::Document;
23 use rustdoc_md::rustdoc_json_types::Crate as RustdocCrate;
24 use rustdoc_md::rustdoc_json_to_markdown;
25
26 #[ derive( Debug, Error ) ]
28 pub enum CrateDocError
29 {
30 #[ error( "I/O error: {0}" ) ]
32 Io( #[ from ] std::io::Error ),
33 #[ error( "Failed to parse Cargo.toml: {0}" ) ]
35 Toml( #[ from ] toml_edit::TomlError ),
36 #[ error( "Failed to execute cargo doc command: {0}" ) ]
38 Command( String ),
39 #[ error( "Failed to deserialize rustdoc JSON: {0}" ) ]
41 Json( #[ from ] serde_json::Error ),
42 #[ error( "Failed to render Markdown: {0}" ) ]
44 MarkdownRender( String ),
45 #[ error( "Missing package name in Cargo.toml at {0}" ) ]
47 MissingPackageName( PathBuf ),
48 #[ error( "Generated JSON documentation file not found at {0}" ) ]
50 JsonFileNotFound( PathBuf ),
51 #[ error( "Path error: {0}" ) ]
53 Path( #[ from ] PathError ),
54 #[ error( "Untyped error: {0}" ) ]
56 Untyped( #[ from ] error::untyped::Error ),
57 }
58
59 #[ derive( Debug, Default, Clone ) ]
61 pub struct CrateDocReport
62 {
63 pub crate_dir : Option< CrateDir >,
65 pub output_path : Option< PathBuf >,
67 pub status : String,
69 pub cargo_doc_report : Option< process::Report >,
71 }
72
73 impl fmt::Display for CrateDocReport
74 {
75 fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
76 {
77 writeln!( f, "{}", self.status )?;
79 if let Some( crate_dir ) = &self.crate_dir
81 {
82 writeln!( f, " Crate: {}", crate_dir.as_ref().display() )?;
83 }
84 if let Some( output_path ) = &self.output_path
85 {
86 writeln!( f, " Output: {}", output_path.display() )?;
87 }
88 Ok( () )
89 }
90 }
91
92 #[ allow( clippy::too_many_lines, clippy::result_large_err ) ]
108 pub fn doc
109 (
110 workspace : &Workspace,
111 crate_dir : &CrateDir,
112 output_path_req : Option< PathBuf >,
113 ) -> ResultWithReport< CrateDocReport, CrateDocError >
114 {
115 let mut report = CrateDocReport
116 {
117 crate_dir : Some( crate_dir.clone() ),
118 status : format!( "Starting documentation generation for {}", crate_dir.as_ref().display() ),
119 ..Default::default()
120 };
121
122
123 let manifest_path_for_name = crate_dir.as_ref().join( "Cargo.toml" );
125 let manifest_content_for_name = fs::read_to_string( &manifest_path_for_name )
126 .map_err( CrateDocError::Io )
127 .context( format!( "Failed to read Cargo.toml at {}", manifest_path_for_name.display() ) )
128 .err_with_report( &report )?;
129 let manifest_toml_for_name = manifest_content_for_name.parse::< Document >()
130 .map_err( CrateDocError::Toml )
131 .context( format!( "Failed to parse Cargo.toml at {}", manifest_path_for_name.display() ) )
132 .err_with_report( &report )?;
133 let crate_name = manifest_toml_for_name[ "package" ][ "name" ]
134 .as_str()
135 .ok_or_else( || CrateDocError::MissingPackageName( manifest_path_for_name.clone() ) )
136 .err_with_report( &report )?;
137 let args: Vec< OsString > = vec!
141 [
142 "doc".into(),
143 "--no-deps".into(),
144 "--package".into(),
145 crate_name.into(),
146 ];
147
148 let envs: HashMap< String, String > =
150 [
151 ( "RUSTC_BOOTSTRAP".to_string(), "1".to_string() ),
152 ( "RUSTDOCFLAGS".to_string(), "-Z unstable-options --output-format json".to_string() ),
153 ].into();
154
155 let cargo_report_result = process::Run::former()
157 .bin_path( "cargo" )
158 .args( args )
159 .current_path( workspace.workspace_root().absolute_path() )
160 .env_variable( envs )
161 .run();
162
163 match &cargo_report_result
165 {
166 Ok( r ) => report.cargo_doc_report = Some( r.clone() ),
167 Err( r ) =>
168 {
169 report.cargo_doc_report = Some( r.clone() );
170 report.status = format!( "Failed during `cargo doc` execution for `{crate_name}`." );
171 }
172 }
173
174 let _cargo_report = cargo_report_result
176 .map_err( | report | CrateDocError::Command( report.to_string() ) )
177 .err_with_report( &report )?;
178
179 let json_path = workspace
181 .target_directory()
182 .join( "doc" )
183 .join( format!( "{crate_name}.json" ) );
184
185 if !json_path.exists()
187 {
188 report.status = format!( "Generated JSON documentation file not found at {}", json_path.display() );
189 return Err(( report, CrateDocError::JsonFileNotFound( json_path ) ));
190 }
191 let json_content = fs::read_to_string( &json_path )
192 .map_err( CrateDocError::Io )
193 .context( format!( "Failed to read JSON documentation file at {}", json_path.display() ) )
194 .err_with_report( &report )?;
195
196 let rustdoc_crate: RustdocCrate = serde_json::from_str( &json_content )
198 .map_err( CrateDocError::Json )
199 .context( format!( "Failed to deserialize JSON from {}", json_path.display() ) )
200 .err_with_report( &report )?;
201
202 let output_md_abs_path = match output_path_req
204 {
205 Some( req_path ) =>
207 {
208 if req_path.is_absolute()
209 {
210 req_path
212 }
213 else
214 {
215 std::env::current_dir()
217 .map_err( CrateDocError::Io )
218 .context( "Failed to get current directory to resolve output path" )
219 .err_with_report( &report )?
220 .join( req_path )
221 }
223 }
224 None =>
226 {
227 workspace
228 .target_directory()
229 .join( "doc" )
230 .join( format!( "{crate_name}_doc.md" ) )
231 }
232 };
233
234 report.output_path = Some( output_md_abs_path.clone() );
235
236 let markdown_content = rustdoc_json_to_markdown( rustdoc_crate );
238
239 if let Some( parent_dir ) = output_md_abs_path.parent()
241 {
242 fs::create_dir_all( parent_dir )
243 .map_err( CrateDocError::Io )
244 .context( format!( "Failed to create output directory {}", parent_dir.display() ) )
245 .err_with_report( &report )?;
246 }
247 fs::write( &output_md_abs_path, markdown_content )
248 .map_err( CrateDocError::Io )
249 .context( format!( "Failed to write Markdown documentation to {}", output_md_abs_path.display() ) )
250 .err_with_report( &report )?;
251
252 report.status = format!( "Markdown documentation generated successfully for `{crate_name}`" );
253
254 Ok( report )
255 }
256}
257
258crate::mod_interface!
259{
260 orphan use doc;
262 orphan use CrateDocReport;
264 orphan use CrateDocError;
266}