use pathdiff::diff_paths; use ra_ap_vfs::{AbsPath, AbsPathBuf}; use std::collections::HashMap; use std::{io::Write, path::PathBuf}; use synoptic::TokOpt; use crate::{html_generator, utils::print_err, ProjectInfo}; pub fn genereate_project_manfiests( source_dir: &AbsPath, manifests: Vec, data_path: &AbsPath, output_root: &AbsPath, project_info: &ProjectInfo, crate_name_by_versioned_name: &HashMap, ) { let file_index = output_root.join("fileIndex").to_string(); let mut file_index = std::fs::OpenOptions::new() .create(true) .write(true) .append(true) .open(file_index) .unwrap(); let total = manifests.len(); let mut i = 0; for manifest in manifests { let is_lib = !manifest.starts_with(source_dir); let output_path = if is_lib { output_root.join("crates") } else { output_root.join(&project_info.name) }; // use real one for crates let manifest = if is_lib { let Some(real_manifest) = manifest.parent().map(|p| p.join("Cargo.toml.orig")) else { continue; }; if std::fs::metadata(&real_manifest).is_ok() { real_manifest } else { manifest } } else { manifest }; let out_file = if is_lib { let Some(registry) = manifest.parent().and_then(|p| p.parent()) else { continue; }; get_output_file_path( &manifest, registry, &output_path, crate_name_by_versioned_name, ) } else { get_output_file_path( &manifest, &source_dir, &output_path, crate_name_by_versioned_name, ) }; let Some((out_file, out_dir)) = out_file else { continue; }; let reader = std::fs::OpenOptions::new().read(true).open(&manifest); if let Err(e) = reader { print_err(&format!("Failed to open file {manifest}, error: {e}")); continue; } let reader = reader.unwrap(); let source = std::io::read_to_string(&reader); if let Err(e) = source { print_err(&format!("Failed read file {manifest}, error: {e}")); continue; } let source = source.unwrap(); let out = std::fs::OpenOptions::new() .create(true) .write(true) .truncate(true) .open(&format!("{out_file}.html")); if let Err(e) = out { print_err(&format!("Failed to open file for writing: {e}")); continue; } let mut out = out.unwrap(); let Some(data_path) = diff_paths(&data_path, &out_dir) else { continue; }; if let Err(e) = gen_highlighted_html(&mut out, &data_path, &source, project_info) { print_err(&format!("Failed to write file {manifest}. Error: {e}")); } // get relative path from output root directory and write to fileIndex if let Err(e) = out_file .strip_prefix(&output_root) .map(|p| p.as_str()) .ok_or("Failed to convert path to string".to_string()) .and_then(|p| writeln!(&mut file_index, "{p}").map_err(|e| e.to_string())) { print_err(&format!( "Failed to write {out_file} to fileIndex. Error: {e}" )); } i += 1; } println!("Wrote {i}/{total} Cargo.toml files"); } fn get_output_file_path( file: &AbsPathBuf, base: &ra_ap_vfs::AbsPath, output_path: &AbsPathBuf, crate_name_by_versioned_name: &HashMap, ) -> Option<(AbsPathBuf, AbsPathBuf)> { let par = file.parent()?; let rel = par.strip_prefix(base)?; let mut rel = rel.as_str().to_string(); // replace the versioned crate name (abc-1.0.0) to versionless (abc) let crate_name = crate_name_by_versioned_name.get(&rel); if let Some(crate_name) = crate_name { rel = crate_name.clone(); } let output_dir = output_path.join(rel); std::fs::create_dir_all(&output_dir).ok()?; Some((output_dir.join("Cargo.toml"), output_dir)) } fn write_html_escaped_string(out: &mut dyn Write, s: &str) -> Result<(), std::io::Error> { for c in s.chars() { match c { '<' => write!(out, "<"), '>' => write!(out, ">"), '&' => write!(out, "&"), _ => write!(out, "{c}"), }? } Ok(()) } fn write_tag(out: &mut dyn Write, tag: &str, inner: &str) -> Result<(), std::io::Error> { write!(out, "<{tag}>")?; write_html_escaped_string(out, inner)?; write!(out, "")?; Ok(()) } fn gen_highlighted_html( out: &mut dyn Write, data_path: &PathBuf, source: &str, project_info: &ProjectInfo, ) -> Result<(), std::io::Error> { let header = html_generator::create_header( "Cargo.toml".into(), data_path.to_str().unwrap(), "", "", project_info, None, ); write!(out, "{header}")?; write!(out, "
\n")?; write!(out, "\n")?; write!(out, "\n")?; let mut hl = synoptic::from_extension("toml", 4).unwrap(); let lines = source.split("\n").map(|l| l.to_string()).collect(); hl.run(&lines); for (line_num, text) in lines.iter().enumerate() { write!( out, "\n")?; } write!(out, "
{}", line_num + 1, line_num + 1 )?; for token in hl.line(line_num, &text) { match token { TokOpt::Some(s, n) => match n.as_str() { "string" => write_tag(out, "q", &s), "table" => write_tag(out, "b", &s), "comment" => write_tag(out, "i", &s), "boolean" | "digit" => write_tag(out, "var", &s), _ => write_html_escaped_string(out, &s), }, TokOpt::None(s) => write_html_escaped_string(out, &s), }? } write!(out, "
\n")?; Ok(()) }