diff options
Diffstat (limited to 'third_party/rust/codespan-reporting/examples/readme_preview.rs')
-rw-r--r-- | third_party/rust/codespan-reporting/examples/readme_preview.rs | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/third_party/rust/codespan-reporting/examples/readme_preview.rs b/third_party/rust/codespan-reporting/examples/readme_preview.rs new file mode 100644 index 0000000000..2cf96f60cb --- /dev/null +++ b/third_party/rust/codespan-reporting/examples/readme_preview.rs @@ -0,0 +1,356 @@ +//! 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}; +use codespan_reporting::files::SimpleFile; +use codespan_reporting::term::termcolor::{Color, ColorSpec, StandardStream, WriteColor}; +use codespan_reporting::term::{self, ColorArg}; +use std::io::{self, Write}; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "emit")] +pub enum Opts { + /// Render SVG output + Svg, + /// Render Stderr output + Stderr { + /// Configure coloring of output + #[structopt( + long = "color", + parse(try_from_str), + default_value = "auto", + possible_values = ColorArg::VARIANTS, + case_insensitive = true + )] + color: ColorArg, + }, +} + +fn main() -> anyhow::Result<()> { + 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` + ", + )])]; + + // let mut files = SimpleFiles::new(); + match Opts::from_args() { + Opts::Svg => { + let mut buffer = Vec::new(); + let mut writer = HtmlEscapeWriter::new(SvgWriter::new(&mut buffer)); + let config = codespan_reporting::term::Config { + styles: codespan_reporting::term::Styles::with_blue(Color::Blue), + ..codespan_reporting::term::Config::default() + }; + + for diagnostic in &diagnostics { + term::emit(&mut writer, &config, &file, &diagnostic)?; + } + + let num_lines = buffer.iter().filter(|byte| **byte == b'\n').count() + 1; + + 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 stdout = std::io::stdout(); + let writer = &mut stdout.lock(); + + write!( + writer, + r#"<svg viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg"> + <style> + /* https://github.com/aaron-williamson/base16-alacritty/blob/master/colors/base16-tomorrow-night-256.yml */ + pre {{ + background: #1d1f21; + margin: 0; + padding: {padding}px; + border-radius: 6px; + color: #ffffff; + font: {font_size}px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + }} + + pre .bold {{ font-weight: bold; }} + + pre .fg.black {{ color: #1d1f21; }} + pre .fg.red {{ color: #cc6666; }} + pre .fg.green {{ color: #b5bd68; }} + pre .fg.yellow {{ color: #f0c674; }} + pre .fg.blue {{ color: #81a2be; }} + pre .fg.magenta {{ color: #b294bb; }} + pre .fg.cyan {{ color: #8abeb7; }} + pre .fg.white {{ color: #c5c8c6; }} + + pre .fg.black.bright {{ color: #969896; }} + pre .fg.red.bright {{ color: #cc6666; }} + pre .fg.green.bright {{ color: #b5bd68; }} + pre .fg.yellow.bright {{ color: #f0c674; }} + pre .fg.blue.bright {{ color: #81a2be; }} + pre .fg.magenta.bright {{ color: #b294bb; }} + pre .fg.cyan.bright {{ color: #8abeb7; }} + pre .fg.white.bright {{ color: #ffffff; }} + + pre .bg.black {{ background-color: #1d1f21; }} + pre .bg.red {{ background-color: #cc6666; }} + pre .bg.green {{ background-color: #b5bd68; }} + pre .bg.yellow {{ background-color: #f0c674; }} + pre .bg.blue {{ background-color: #81a2be; }} + pre .bg.magenta {{ background-color: #b294bb; }} + pre .bg.cyan {{ background-color: #8abeb7; }} + pre .bg.white {{ background-color: #c5c8c6; }} + + pre .bg.black.bright {{ background-color: #969896; }} + pre .bg.red.bright {{ background-color: #cc6666; }} + pre .bg.green.bright {{ background-color: #b5bd68; }} + pre .bg.yellow.bright {{ background-color: #f0c674; }} + pre .bg.blue.bright {{ background-color: #81a2be; }} + pre .bg.magenta.bright {{ background-color: #b294bb; }} + pre .bg.cyan.bright {{ background-color: #8abeb7; }} + pre .bg.white.bright {{ background-color: #ffffff; }} + </style> + + <foreignObject x="0" y="0" width="{width}" height="{height}"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <pre>"#, + padding = padding, + font_size = font_size, + width = width, + height = height, + )?; + + writer.write_all(&buffer)?; + + write!( + writer, + "</pre> + </div> + </foreignObject> +</svg> +" + )?; + } + Opts::Stderr { color } => { + let writer = StandardStream::stderr(color.into()); + let config = codespan_reporting::term::Config::default(); + for diagnostic in &diagnostics { + term::emit(&mut writer.lock(), &config, &file, &diagnostic)?; + } + } + } + + Ok(()) +} + +/// Rudimentary HTML escaper which performs the following conversions: +/// +/// - `<` ⇒ `<` +/// - `>` ⇒ `>` +/// - `&` ⇒ `&` +pub struct HtmlEscapeWriter<W> { + upstream: W, +} + +impl<W> HtmlEscapeWriter<W> { + pub fn new(upstream: W) -> HtmlEscapeWriter<W> { + HtmlEscapeWriter { upstream } + } +} + +impl<W: Write> Write for HtmlEscapeWriter<W> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let mut last_term = 0usize; + for (i, byte) in buf.iter().enumerate() { + let escape = match byte { + b'<' => &b"<"[..], + b'>' => &b">"[..], + b'&' => &b"&"[..], + _ => continue, + }; + self.upstream.write_all(&buf[last_term..i])?; + last_term = i + 1; + self.upstream.write_all(escape)?; + } + self.upstream.write_all(&buf[last_term..])?; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.upstream.flush() + } +} + +impl<W: WriteColor> WriteColor for HtmlEscapeWriter<W> { + fn supports_color(&self) -> bool { + self.upstream.supports_color() + } + + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.upstream.set_color(spec) + } + + fn reset(&mut self) -> io::Result<()> { + self.upstream.reset() + } +} + +pub struct SvgWriter<W> { + upstream: W, + color: ColorSpec, +} + +impl<W> SvgWriter<W> { + pub fn new(upstream: W) -> SvgWriter<W> { + SvgWriter { + upstream, + color: ColorSpec::new(), + } + } +} + +impl<W: Write> Write for SvgWriter<W> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.upstream.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.upstream.flush() + } +} + +impl<W: Write> WriteColor for SvgWriter<W> { + fn supports_color(&self) -> bool { + true + } + + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + #![allow(unused_assignments)] + + if self.color == *spec { + return Ok(()); + } else { + if !self.color.is_none() { + write!(self, "</span>")?; + } + self.color = spec.clone(); + } + + if spec.is_none() { + write!(self, "</span>")?; + return Ok(()); + } else { + write!(self, "<span class=\"")?; + } + + let mut first = true; + + fn write_first<W: Write>(first: bool, writer: &mut SvgWriter<W>) -> io::Result<bool> { + if !first { + write!(writer, " ")?; + } + + Ok(false) + }; + + fn write_color<W: Write>(color: &Color, writer: &mut SvgWriter<W>) -> io::Result<()> { + match color { + Color::Black => write!(writer, "black"), + Color::Blue => write!(writer, "blue"), + Color::Green => write!(writer, "green"), + Color::Red => write!(writer, "red"), + Color::Cyan => write!(writer, "cyan"), + Color::Magenta => write!(writer, "magenta"), + Color::Yellow => write!(writer, "yellow"), + Color::White => write!(writer, "white"), + // TODO: other colors + _ => Ok(()), + } + }; + + if let Some(fg) = spec.fg() { + first = write_first(first, self)?; + write!(self, "fg ")?; + write_color(fg, self)?; + } + + if let Some(bg) = spec.bg() { + first = write_first(first, self)?; + write!(self, "bg ")?; + write_color(bg, self)?; + } + + if spec.bold() { + first = write_first(first, self)?; + write!(self, "bold")?; + } + + if spec.underline() { + first = write_first(first, self)?; + write!(self, "underline")?; + } + + if spec.intense() { + first = write_first(first, self)?; + write!(self, "bright")?; + } + + write!(self, "\">")?; + + Ok(()) + } + + fn reset(&mut self) -> io::Result<()> { + let color = self.color.clone(); + + if color != ColorSpec::new() { + write!(self, "</span>")?; + self.color = ColorSpec::new(); + } + + Ok(()) + } +} |