use anyhow::Context as _; use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel}; use codesnip_core::SnippetMap; use console::{colors_enabled_stderr, strip_ansi_codes, style, Color}; use indicatif::{ProgressBar, ProgressStyle}; use rayon::prelude::*; use std::{fs::File, io::Write as _, sync::atomic::AtomicBool}; use std::{ iter::repeat, process::{Command, Stdio}, }; use tempfile::tempdir; pub fn execute( map: SnippetMap, toolchain: &str, edition: &str, verbose: bool, ) -> anyhow::Result<()> { let ok = AtomicBool::new(true); let pb = ProgressBar::new(map.map.len() as u64); pb.set_style( ProgressStyle::default_bar() .template("{prefix:>12.green} [{bar:57}] {pos}/{len}: {msg}") .unwrap() .progress_chars("=> "), ); pb.set_prefix("Checking"); let is_hidden = pb.is_hidden(); let colors_enabled = colors_enabled_stderr(); macro_rules! pb_println { ($($t:tt)*) => { if is_hidden { if colors_enabled { eprintln!($($t)*); } else { eprintln!("{}", strip_ansi_codes(&format!($($t)*))); } } else { pb.println(format!($($t)*)); } } } map.map.par_iter().for_each(|(name, link)| { pb.set_message(name.to_owned()); for include in link.includes.iter() { if !map.map.contains_key(include) { ok.store(false, std::sync::atomic::Ordering::Relaxed); pb_println!( "{}: Invalid include `{}` in {}.", style("warning").yellow(), include, name ); } } let contents = map.bundle(name, link, Default::default(), false); match check(name, &contents, toolchain, edition) { Ok((success, messages)) => { if !success { ok.store(false, std::sync::atomic::Ordering::Relaxed); pb_println!("{:>12} {}", style("Failed").red(), name); } else if verbose { pb_println!( "{:>12} {:.<45}.{:.>8} Byte", style("Verified").green().bright(), name, contents.bytes().len() ); } if verbose { for message in messages { if let Some(message) = format_error_message(name, message) { pb_println!("{}", message); } } } } Err(err) => { ok.store(false, std::sync::atomic::Ordering::Relaxed); pb_println!("{}: {}", style("error").red(), err); } } pb.inc(1); }); pb.finish_and_clear(); pb_println!( "{:>12} {} Snippets", style("Finished").green().bright(), map.map.len() ); if ok.load(std::sync::atomic::Ordering::Relaxed) { Ok(()) } else { None.with_context(|| "verify failed") } } fn check( name: &str, contents: &str, toolchain: &str, edition: &str, ) -> anyhow::Result<(bool, Vec)> { let dir = tempdir()?; let lib = dir.path().join(name); { let mut file = File::create(&lib)?; file.write_all(contents.as_bytes())?; } let mut out_dir: std::ffi::OsString = "--out-dir=".to_owned().into(); out_dir.push(dir.path().as_os_str()); let output = Command::new("rustc") .args(&[ format!("+{}", toolchain).as_ref(), lib.as_os_str(), format!("--edition={}", edition).as_ref(), "--crate-type=lib".as_ref(), "--error-format=json".as_ref(), out_dir.as_ref(), ]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output()?; let messages: Vec = String::from_utf8_lossy(&output.stderr) .lines() .filter_map(|line| serde_json::from_str(line).ok()) .collect(); Ok((output.status.success(), messages)) } fn format_error_message(name: &str, message: Diagnostic) -> Option { let mut s = String::new(); let code = message .code .as_ref() .map(|code| format!("[{}]", code.code)) .unwrap_or_default(); let (color, status) = match message.level { DiagnosticLevel::Error => (Color::Red, "error"), DiagnosticLevel::Warning => (Color::Yellow, "warning"), _ => { return None; } }; s.push_str(&format!( "{}: {}\n", style(format!("{}{}", status, code)).fg(color), &message.message )); for span in message.spans.iter() { let k = format!("{}", span.line_end).len(); s.push_str(&format!( "{:>k$} {}:{}:{}\n{:>k$}", style("-->").cyan().bright(), name, span.line_start, span.column_start, style(" | ").cyan().bright(), k = k + 3, )); for (line, text) in (span.line_start..=span.line_end).zip(span.text.iter()) { s.push_str(&format!( "\n{}{}\n{:>k$}", style(format!("{:>k$} | ", line, k = k)).cyan().bright(), &text.text, style(" | ").cyan().bright(), k = k + 3, )); s.extend(repeat(' ').take(text.highlight_start - 1)); s.push_str( &style("^".repeat(text.highlight_end - text.highlight_start)) .fg(color) .to_string(), ); } s.push_str(&format!( " {}\n", style(span.label.as_ref().cloned().unwrap_or_default()).fg(color) )); } Some(s) }