1//! Renders the preview SVG for the README.
2//!
3//! To update the preview, execute the following command from the top level of
4//! the repository:
5//!
6//! ```sh
7//! cargo run --example readme_preview svg > codespan-reporting/assets/readme_preview.svg
8//! ```
9
10use codespan_reporting::diagnostic::{Diagnostic, Label};
11use codespan_reporting::files::SimpleFile;
12use codespan_reporting::term::termcolor::{Color, ColorSpec, StandardStream, WriteColor};
13use codespan_reporting::term::{self, ColorArg};
14use std::io::{self, Write};
15use structopt::StructOpt;
16
17#[derive(Debug, StructOpt)]
18#[structopt(name = "emit")]
19pub enum Opts {
20    /// Render SVG output
21    Svg,
22    /// Render Stderr output
23    Stderr {
24        /// Configure coloring of output
25        #[structopt(
26            long = "color",
27            parse(try_from_str),
28            default_value = "auto",
29            possible_values = ColorArg::VARIANTS,
30            case_insensitive = true
31        )]
32        color: ColorArg,
33    },
34}
35
36fn main() -> anyhow::Result<()> {
37    let file = SimpleFile::new(
38        "FizzBuzz.fun",
39        unindent::unindent(
40            r#"
41                module FizzBuzz where
42
43                fizz₁ : NatString
44                fizznum = case (mod num 5) (mod num 3) of
45                    0 0 => "FizzBuzz"
46                    0 _ => "Fizz"
47                    _ 0 => "Buzz"
48                    _ _ => num
49
50                fizz₂ : NatString
51                fizznum =
52                    case (mod num 5) (mod num 3) of
53                        0 0 => "FizzBuzz"
54                        0 _ => "Fizz"
55                        _ 0 => "Buzz"
56                        _ _ => num
57            "#,
58        ),
59    );
60
61    let diagnostics = [Diagnostic::error()
62        .with_message("`case` clauses have incompatible types")
63        .with_code("E0308")
64        .with_labels(vec![
65            Label::primary((), 328..331).with_message("expected `String`, found `Nat`"),
66            Label::secondary((), 211..331).with_message("`case` clauses have incompatible types"),
67            Label::secondary((), 258..268).with_message("this is found to be of type `String`"),
68            Label::secondary((), 284..290).with_message("this is found to be of type `String`"),
69            Label::secondary((), 306..312).with_message("this is found to be of type `String`"),
70            Label::secondary((), 186..192).with_message("expected type `String` found here"),
71        ])
72        .with_notes(vec![unindent::unindent(
73            "
74                expected type `String`
75                   found type `Nat`
76            ",
77        )])];
78
79    // let mut files = SimpleFiles::new();
80    match Opts::from_args() {
81        Opts::Svg => {
82            let mut buffer = Vec::new();
83            let mut writer = HtmlEscapeWriter::new(SvgWriter::new(&mut buffer));
84            let config = codespan_reporting::term::Config {
85                styles: codespan_reporting::term::Styles::with_blue(Color::Blue),
86                ..codespan_reporting::term::Config::default()
87            };
88
89            for diagnostic in &diagnostics {
90                term::emit(&mut writer, &config, &file, &diagnostic)?;
91            }
92
93            let num_lines = buffer.iter().filter(|byte| **byte == b'\n').count() + 1;
94
95            let padding = 10;
96            let font_size = 12;
97            let line_spacing = 3;
98            let width = 882;
99            let height = padding + num_lines * (font_size + line_spacing) + padding;
100
101            let stdout = std::io::stdout();
102            let writer = &mut stdout.lock();
103
104            write!(
105                writer,
106                r#"<svg viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
107  <style>
108    /* https://github.com/aaron-williamson/base16-alacritty/blob/master/colors/base16-tomorrow-night-256.yml */
109    pre {{
110      background: #1d1f21;
111      margin: 0;
112      padding: {padding}px;
113      border-radius: 6px;
114      color: #ffffff;
115      font: {font_size}px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
116    }}
117
118    pre .bold {{ font-weight: bold; }}
119
120    pre .fg.black   {{ color: #1d1f21; }}
121    pre .fg.red     {{ color: #cc6666; }}
122    pre .fg.green   {{ color: #b5bd68; }}
123    pre .fg.yellow  {{ color: #f0c674; }}
124    pre .fg.blue    {{ color: #81a2be; }}
125    pre .fg.magenta {{ color: #b294bb; }}
126    pre .fg.cyan    {{ color: #8abeb7; }}
127    pre .fg.white   {{ color: #c5c8c6; }}
128
129    pre .fg.black.bright    {{ color: #969896; }}
130    pre .fg.red.bright      {{ color: #cc6666; }}
131    pre .fg.green.bright    {{ color: #b5bd68; }}
132    pre .fg.yellow.bright   {{ color: #f0c674; }}
133    pre .fg.blue.bright     {{ color: #81a2be; }}
134    pre .fg.magenta.bright  {{ color: #b294bb; }}
135    pre .fg.cyan.bright     {{ color: #8abeb7; }}
136    pre .fg.white.bright    {{ color: #ffffff; }}
137
138    pre .bg.black   {{ background-color: #1d1f21; }}
139    pre .bg.red     {{ background-color: #cc6666; }}
140    pre .bg.green   {{ background-color: #b5bd68; }}
141    pre .bg.yellow  {{ background-color: #f0c674; }}
142    pre .bg.blue    {{ background-color: #81a2be; }}
143    pre .bg.magenta {{ background-color: #b294bb; }}
144    pre .bg.cyan    {{ background-color: #8abeb7; }}
145    pre .bg.white   {{ background-color: #c5c8c6; }}
146
147    pre .bg.black.bright    {{ background-color: #969896; }}
148    pre .bg.red.bright      {{ background-color: #cc6666; }}
149    pre .bg.green.bright    {{ background-color: #b5bd68; }}
150    pre .bg.yellow.bright   {{ background-color: #f0c674; }}
151    pre .bg.blue.bright     {{ background-color: #81a2be; }}
152    pre .bg.magenta.bright  {{ background-color: #b294bb; }}
153    pre .bg.cyan.bright     {{ background-color: #8abeb7; }}
154    pre .bg.white.bright    {{ background-color: #ffffff; }}
155  </style>
156
157  <foreignObject x="0" y="0" width="{width}" height="{height}">
158    <div xmlns="http://www.w3.org/1999/xhtml">
159      <pre>"#,
160                padding = padding,
161                font_size = font_size,
162                width = width,
163                height = height,
164            )?;
165
166            writer.write_all(&buffer)?;
167
168            write!(
169                writer,
170                "</pre>
171    </div>
172  </foreignObject>
173</svg>
174"
175            )?;
176        }
177        Opts::Stderr { color } => {
178            let writer = StandardStream::stderr(color.into());
179            let config = codespan_reporting::term::Config::default();
180            for diagnostic in &diagnostics {
181                term::emit(&mut writer.lock(), &config, &file, &diagnostic)?;
182            }
183        }
184    }
185
186    Ok(())
187}
188
189/// Rudimentary HTML escaper which performs the following conversions:
190///
191/// - `<` ⇒ `&lt;`
192/// - `>` ⇒ `&gt;`
193/// - `&` ⇒ `&amp;`
194pub struct HtmlEscapeWriter<W> {
195    upstream: W,
196}
197
198impl<W> HtmlEscapeWriter<W> {
199    pub fn new(upstream: W) -> HtmlEscapeWriter<W> {
200        HtmlEscapeWriter { upstream }
201    }
202}
203
204impl<W: Write> Write for HtmlEscapeWriter<W> {
205    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
206        let mut last_term = 0usize;
207        for (i, byte) in buf.iter().enumerate() {
208            let escape = match byte {
209                b'<' => &b"&lt;"[..],
210                b'>' => &b"&gt;"[..],
211                b'&' => &b"&amp;"[..],
212                _ => continue,
213            };
214            self.upstream.write_all(&buf[last_term..i])?;
215            last_term = i + 1;
216            self.upstream.write_all(escape)?;
217        }
218        self.upstream.write_all(&buf[last_term..])?;
219        Ok(buf.len())
220    }
221
222    fn flush(&mut self) -> io::Result<()> {
223        self.upstream.flush()
224    }
225}
226
227impl<W: WriteColor> WriteColor for HtmlEscapeWriter<W> {
228    fn supports_color(&self) -> bool {
229        self.upstream.supports_color()
230    }
231
232    fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
233        self.upstream.set_color(spec)
234    }
235
236    fn reset(&mut self) -> io::Result<()> {
237        self.upstream.reset()
238    }
239}
240
241pub struct SvgWriter<W> {
242    upstream: W,
243    color: ColorSpec,
244}
245
246impl<W> SvgWriter<W> {
247    pub fn new(upstream: W) -> SvgWriter<W> {
248        SvgWriter {
249            upstream,
250            color: ColorSpec::new(),
251        }
252    }
253}
254
255impl<W: Write> Write for SvgWriter<W> {
256    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
257        self.upstream.write(buf)
258    }
259
260    fn flush(&mut self) -> io::Result<()> {
261        self.upstream.flush()
262    }
263}
264
265impl<W: Write> WriteColor for SvgWriter<W> {
266    fn supports_color(&self) -> bool {
267        true
268    }
269
270    fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
271        #![allow(unused_assignments)]
272
273        if self.color == *spec {
274            return Ok(());
275        } else {
276            if !self.color.is_none() {
277                write!(self, "</span>")?;
278            }
279            self.color = spec.clone();
280        }
281
282        if spec.is_none() {
283            write!(self, "</span>")?;
284            return Ok(());
285        } else {
286            write!(self, "<span class=\"")?;
287        }
288
289        let mut first = true;
290
291        fn write_first<W: Write>(first: bool, writer: &mut SvgWriter<W>) -> io::Result<bool> {
292            if !first {
293                write!(writer, " ")?;
294            }
295
296            Ok(false)
297        };
298
299        fn write_color<W: Write>(color: &Color, writer: &mut SvgWriter<W>) -> io::Result<()> {
300            match color {
301                Color::Black => write!(writer, "black"),
302                Color::Blue => write!(writer, "blue"),
303                Color::Green => write!(writer, "green"),
304                Color::Red => write!(writer, "red"),
305                Color::Cyan => write!(writer, "cyan"),
306                Color::Magenta => write!(writer, "magenta"),
307                Color::Yellow => write!(writer, "yellow"),
308                Color::White => write!(writer, "white"),
309                // TODO: other colors
310                _ => Ok(()),
311            }
312        };
313
314        if let Some(fg) = spec.fg() {
315            first = write_first(first, self)?;
316            write!(self, "fg ")?;
317            write_color(fg, self)?;
318        }
319
320        if let Some(bg) = spec.bg() {
321            first = write_first(first, self)?;
322            write!(self, "bg ")?;
323            write_color(bg, self)?;
324        }
325
326        if spec.bold() {
327            first = write_first(first, self)?;
328            write!(self, "bold")?;
329        }
330
331        if spec.underline() {
332            first = write_first(first, self)?;
333            write!(self, "underline")?;
334        }
335
336        if spec.intense() {
337            first = write_first(first, self)?;
338            write!(self, "bright")?;
339        }
340
341        write!(self, "\">")?;
342
343        Ok(())
344    }
345
346    fn reset(&mut self) -> io::Result<()> {
347        let color = self.color.clone();
348
349        if color != ColorSpec::new() {
350            write!(self, "</span>")?;
351            self.color = ColorSpec::new();
352        }
353
354        Ok(())
355    }
356}
357