mod private
{
use crate :: *;
use process_tools ::process;
use error ::
{
untyped ::Context,
typed ::Error,
ErrWith,
};
use core ::fmt;
use std ::
{
ffi ::OsString,
fs,
path ::PathBuf,
};
use toml_edit ::Document;
use rustdoc_md ::rustdoc_json_types ::Crate as RustdocCrate;
use rustdoc_md ::rustdoc_json_to_markdown;
use core ::result ::Result :: { Ok, Err };
#[ derive( Debug, Error ) ]
pub enum CrateDocError
{
#[ error( "I/O error: {0}" ) ]
Io( #[ from ] std ::io ::Error ),
#[ error( "Failed to parse Cargo.toml: {0}" ) ]
Toml( #[ from ] toml_edit ::TomlError ),
#[ error( "Failed to execute cargo doc command: {0}" ) ]
Command( String ),
#[ error( "Failed to deserialize rustdoc JSON: {0}" ) ]
Json( #[ from ] serde_json ::Error ),
#[ error( "Failed to render Markdown: {0}" ) ]
MarkdownRender( String ),
#[ error( "Missing package name in Cargo.toml at {0}" ) ]
MissingPackageName( PathBuf ),
#[ error( "Generated JSON documentation file not found at {0}" ) ]
JsonFileNotFound( PathBuf ),
#[ error( "Path error: {0}" ) ]
Path( #[ from ] PathError ),
#[ error( "Untyped error: {0}" ) ]
Untyped( #[ from ] error ::untyped ::Error ),
}
#[ derive( Debug, Default, Clone ) ]
pub struct CrateDocReport
{
pub crate_dir: Option< CrateDir >,
pub output_path: Option< PathBuf >,
pub status: String,
pub cargo_doc_report: Option< process ::Report >,
}
impl fmt ::Display for CrateDocReport
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> fmt ::Result
{
writeln!( f, "{}", self.status )?;
if let Some( crate_dir ) = &self.crate_dir
{
writeln!( f, " Crate: {}", crate_dir.as_ref().display() )?;
}
if let Some( output_path ) = &self.output_path
{
writeln!( f, " Output: {}", output_path.display() )?;
}
Ok( () )
}
}
#[ allow( clippy ::too_many_lines, clippy ::result_large_err ) ]
pub fn doc
(
workspace: &Workspace,
crate_dir: &CrateDir,
output_path_req: Option< PathBuf >,
) -> ResultWithReport< CrateDocReport, CrateDocError >
{
let mut report = CrateDocReport
{
crate_dir: Some( crate_dir.clone() ),
status: format!( "Starting documentation generation for {}", crate_dir.as_ref().display() ),
..Default ::default()
};
let manifest_path_for_name = crate_dir.as_ref().join( "Cargo.toml" );
let manifest_content_for_name = fs ::read_to_string( &manifest_path_for_name )
.map_err( CrateDocError ::Io )
.context( format!( "Failed to read Cargo.toml at {}", manifest_path_for_name.display() ) )
.err_with_report( &report )?;
let manifest_toml_for_name = manifest_content_for_name.parse :: < Document >()
.map_err( CrateDocError ::Toml )
.context( format!( "Failed to parse Cargo.toml at {}", manifest_path_for_name.display() ) )
.err_with_report( &report )?;
let crate_name = manifest_toml_for_name[ "package" ][ "name" ]
.as_str()
.ok_or_else( || CrateDocError ::MissingPackageName( manifest_path_for_name.clone() ) )
.err_with_report( &report )?;
let args: Vec< OsString > = vec!
[
"doc".into(),
"--no-deps".into(),
"--package".into(),
crate_name.into(),
];
let envs: std ::collections ::HashMap< String, String > =
[
( "RUSTC_BOOTSTRAP".to_string(), "1".to_string() ),
( "RUSTDOCFLAGS".to_string(), "-Z unstable-options --output-format json".to_string() ),
].into();
let cargo_report_result = process ::Run ::former()
.bin_path( "cargo" )
.args( args )
.current_path( workspace.workspace_root().absolute_path() )
.env_variable( envs )
.run();
match &cargo_report_result
{
Ok( r ) => report.cargo_doc_report = Some( r.clone() ),
Err( r ) =>
{
report.cargo_doc_report = Some( r.clone() );
report.status = format!( "Failed during `cargo doc` execution for `{crate_name}`." );
}
}
let _cargo_report = cargo_report_result
.map_err( | report | CrateDocError ::Command( report.to_string() ) )
.err_with_report( &report )?;
let json_path = workspace
.target_directory()
.join( "doc" )
.join( format!( "{crate_name}.json" ) );
if !json_path.exists()
{
report.status = format!( "Generated JSON documentation file not found at {}", json_path.display() );
return Err(( report, CrateDocError ::JsonFileNotFound( json_path ) ));
}
let json_content = fs ::read_to_string( &json_path )
.map_err( CrateDocError ::Io )
.context( format!( "Failed to read JSON documentation file at {}", json_path.display() ) )
.err_with_report( &report )?;
let rustdoc_crate: RustdocCrate = serde_json ::from_str( &json_content )
.map_err( CrateDocError ::Json )
.context( format!( "Failed to deserialize JSON from {}", json_path.display() ) )
.err_with_report( &report )?;
let output_md_abs_path = match output_path_req
{
Some( req_path ) =>
{
if req_path.is_absolute()
{
req_path
}
else
{
std ::env ::current_dir()
.map_err( CrateDocError ::Io )
.context( "Failed to get current directory to resolve output path" )
.err_with_report( &report )?
.join( req_path )
}
}
None =>
{
workspace
.target_directory()
.join( "doc" )
.join( format!( "{crate_name}_doc.md" ) )
}
};
report.output_path = Some( output_md_abs_path.clone() );
let markdown_content = rustdoc_json_to_markdown( rustdoc_crate );
if let Some( parent_dir ) = output_md_abs_path.parent()
{
fs ::create_dir_all( parent_dir )
.map_err( CrateDocError ::Io )
.context( format!( "Failed to create output directory {}", parent_dir.display() ) )
.err_with_report( &report )?;
}
fs ::write( &output_md_abs_path, markdown_content )
.map_err( CrateDocError ::Io )
.context( format!( "Failed to write Markdown documentation to {}", output_md_abs_path.display() ) )
.err_with_report( &report )?;
report.status = format!( "Markdown documentation generated successfully for `{crate_name}`" );
Ok( report )
}
}
crate ::mod_interface!
{
orphan use doc;
orphan use CrateDocReport;
orphan use CrateDocError;
}