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