• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
10 use codespan_reporting::diagnostic::{Diagnostic, Label};
11 use codespan_reporting::files::SimpleFile;
12 use codespan_reporting::term::termcolor::{Color, ColorSpec, StandardStream, WriteColor};
13 use codespan_reporting::term::{self, ColorArg};
14 use std::io::{self, Write};
15 use structopt::StructOpt;
16 
17 #[derive(Debug, StructOpt)]
18 #[structopt(name = "emit")]
19 pub 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 
main() -> anyhow::Result<()>36 fn main() -> anyhow::Result<()> {
37     let file = SimpleFile::new(
38         "FizzBuzz.fun",
39         unindent::unindent(
40             r#"
41                 module FizzBuzz where
42 
43                 fizz₁ : Nat → String
44                 fizz₁ num = case (mod num 5) (mod num 3) of
45                     0 0 => "FizzBuzz"
46                     0 _ => "Fizz"
47                     _ 0 => "Buzz"
48                     _ _ => num
49 
50                 fizz₂ : Nat → String
51                 fizz₂ num =
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;`
194 pub struct HtmlEscapeWriter<W> {
195     upstream: W,
196 }
197 
198 impl<W> HtmlEscapeWriter<W> {
new(upstream: W) -> HtmlEscapeWriter<W>199     pub fn new(upstream: W) -> HtmlEscapeWriter<W> {
200         HtmlEscapeWriter { upstream }
201     }
202 }
203 
204 impl<W: Write> Write for HtmlEscapeWriter<W> {
write(&mut self, buf: &[u8]) -> io::Result<usize>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 
flush(&mut self) -> io::Result<()>222     fn flush(&mut self) -> io::Result<()> {
223         self.upstream.flush()
224     }
225 }
226 
227 impl<W: WriteColor> WriteColor for HtmlEscapeWriter<W> {
supports_color(&self) -> bool228     fn supports_color(&self) -> bool {
229         self.upstream.supports_color()
230     }
231 
set_color(&mut self, spec: &ColorSpec) -> io::Result<()>232     fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
233         self.upstream.set_color(spec)
234     }
235 
reset(&mut self) -> io::Result<()>236     fn reset(&mut self) -> io::Result<()> {
237         self.upstream.reset()
238     }
239 }
240 
241 pub struct SvgWriter<W> {
242     upstream: W,
243     color: ColorSpec,
244 }
245 
246 impl<W> SvgWriter<W> {
new(upstream: W) -> SvgWriter<W>247     pub fn new(upstream: W) -> SvgWriter<W> {
248         SvgWriter {
249             upstream,
250             color: ColorSpec::new(),
251         }
252     }
253 }
254 
255 impl<W: Write> Write for SvgWriter<W> {
write(&mut self, buf: &[u8]) -> io::Result<usize>256     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
257         self.upstream.write(buf)
258     }
259 
flush(&mut self) -> io::Result<()>260     fn flush(&mut self) -> io::Result<()> {
261         self.upstream.flush()
262     }
263 }
264 
265 impl<W: Write> WriteColor for SvgWriter<W> {
supports_color(&self) -> bool266     fn supports_color(&self) -> bool {
267         true
268     }
269 
set_color(&mut self, spec: &ColorSpec) -> io::Result<()>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 
reset(&mut self) -> io::Result<()>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