//! Renders the preview SVG for the README. //! //! To update the preview, execute the following command from the top level of //! the repository: //! //! ```sh //! cargo run --example readme_preview svg > codespan-reporting/assets/readme_preview.svg //! ``` use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity}; use codespan_reporting::files::SimpleFile; use codespan_reporting::term::{self, Config}; use codespan_reporting::term::{GeneralWrite, GeneralWriteResult}; #[cfg(feature = "termcolor")] use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; #[cfg(not(feature = "std"))] fn main() { panic!("example requires std feature"); } #[cfg(feature = "std")] fn main() -> anyhow::Result<()> { #[derive(Debug)] pub enum Opts { /// Render SVG output Svg, /// Render Stderr output #[cfg(feature = "termcolor")] Stderr { /// Configure coloring of output color: ColorChoice, }, } fn parse_args() -> Result { let mut pargs = pico_args::Arguments::from_env(); match pargs.subcommand()? { Some(value) => match value.as_str() { "svg" => Ok(Opts::Svg), #[cfg(feature = "termcolor")] "stderr" => { let color = pargs .opt_value_from_str("--color")? .unwrap_or(ColorChoice::Auto); Ok(Opts::Stderr { color }) } _ => Err(pico_args::Error::Utf8ArgumentParsingFailed { value, cause: "not a valid subcommand".to_owned(), }), }, None => Err(pico_args::Error::MissingArgument), } } let file = SimpleFile::new( "FizzBuzz.fun", unindent::unindent( r#" module FizzBuzz where fizz₁ : Nat → String fizz₁ num = case (mod num 5) (mod num 3) of 0 0 => "FizzBuzz" 0 _ => "Fizz" _ 0 => "Buzz" _ _ => num fizz₂ : Nat → String fizz₂ num = case (mod num 5) (mod num 3) of 0 0 => "FizzBuzz" 0 _ => "Fizz" _ 0 => "Buzz" _ _ => num "#, ), ); let diagnostics = [Diagnostic::error() .with_message("`case` clauses have incompatible types") .with_code("E0308") .with_labels(vec![ Label::primary((), 328..331).with_message("expected `String`, found `Nat`"), Label::secondary((), 211..331).with_message("`case` clauses have incompatible types"), Label::secondary((), 258..268).with_message("this is found to be of type `String`"), Label::secondary((), 284..290).with_message("this is found to be of type `String`"), Label::secondary((), 306..312).with_message("this is found to be of type `String`"), Label::secondary((), 186..192).with_message("expected type `String` found here"), ]) .with_notes(vec![unindent::unindent( " expected type `String` found type `Nat` ", )])]; match parse_args()? { Opts::Svg => { let mut writer = SvgWriter::new(); let config = Config::default(); for diagnostic in &diagnostics { term::emit_to_write_style(&mut writer, &config, &file, diagnostic)?; } let num_lines = writer.line_count(); let padding = 10; let font_size = 12; let line_spacing = 3; let width = 882; let height = padding + num_lines * (font_size + line_spacing) + padding; let content = writer.into_string(); let stdout = std::io::stdout(); let writer = &mut stdout.lock(); write!( writer, r#"
        {content}
      
"# )?; } #[cfg(feature = "termcolor")] Opts::Stderr { color } => { let writer = StandardStream::stderr(color); let config = Config::default(); for diagnostic in &diagnostics { term::emit_to_write_style(&mut writer.lock(), &config, &file, diagnostic)?; } } } Ok(()) } // This whole example requires the std feature, but below is feature agnostic for reference #[cfg(feature = "std")] type WriterBuffer = Vec; #[cfg(not(feature = "std"))] type WriterBuffer = String; pub struct SvgWriter { buffer: WriterBuffer, span_open: bool, } impl SvgWriter { pub fn new() -> Self { SvgWriter { buffer: WriterBuffer::default(), span_open: false, } } #[cfg(feature = "std")] pub fn into_string(self) -> String { String::from_utf8(self.buffer).unwrap() } #[cfg(not(feature = "std"))] pub fn into_string(self) -> String { self.buffer } #[cfg(feature = "std")] pub fn line_count(&self) -> usize { self.buffer.iter().filter(|byte| **byte == b'\n').count() + 1 } #[cfg(not(feature = "std"))] pub fn line_count(&self) -> usize { self.buffer.lines().count() } /// Close any open span fn close_span(&mut self) -> GeneralWriteResult { if self.span_open { write!(self.buffer, "")?; self.span_open = false; } Ok(()) } /// Open a new span with the given CSS class fn open_span(&mut self, class: &str) -> GeneralWriteResult { // close existing first self.close_span()?; write!(self.buffer, "", class)?; self.span_open = true; Ok(()) } } #[cfg(feature = "std")] impl std::io::Write for SvgWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { let mut last = 0; for (i, &b) in buf.iter().enumerate() { let escape = match b { b'<' => b"<"[..].as_ref(), b'>' => b">"[..].as_ref(), b'&' => b"&"[..].as_ref(), _ => continue, }; self.buffer.write_all(&buf[last..i])?; self.buffer.write_all(escape)?; last = i + 1; } self.buffer.write_all(&buf[last..])?; Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { self.buffer.flush() } } #[cfg(not(feature = "std"))] impl core::fmt::Write for SvgWriter { fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { let mut last = 0; // TODO match indices for (i, b) in s.chars().enumerate() { let escape = match b { '<' => "<", '>' => ">", '&' => "&", _ => continue, }; self.buffer.write_str(&s[last..i])?; self.buffer.write_str(escape)?; last = i + 1; } self.buffer.write_str(&s[last..])?; Ok(()) } } impl codespan_reporting::term::WriteStyle for SvgWriter { fn set_header(&mut self, severity: Severity) -> GeneralWriteResult { let class = match severity { Severity::Bug => "header-bug", Severity::Error => "header-error", Severity::Warning => "header-warning", Severity::Note => "header-note", Severity::Help => "header-help", }; self.open_span(class) } fn set_header_message(&mut self) -> GeneralWriteResult { self.open_span("header-message") } fn set_line_number(&mut self) -> GeneralWriteResult { self.open_span("line-number") } fn set_note_bullet(&mut self) -> GeneralWriteResult { self.open_span("note-bullet") } fn set_source_border(&mut self) -> GeneralWriteResult { self.open_span("source-border") } fn set_label(&mut self, severity: Severity, label_style: LabelStyle) -> GeneralWriteResult { let sev = match severity { Severity::Bug => "bug", Severity::Error => "error", Severity::Warning => "warning", Severity::Note => "note", Severity::Help => "help", }; let typ = match label_style { LabelStyle::Primary => "primary", LabelStyle::Secondary => "secondary", }; self.open_span(&format!("label-{}-{}", typ, sev)) } fn reset(&mut self) -> GeneralWriteResult { self.close_span() } }