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 /// - `<` ⇒ `<`
192 /// - `>` ⇒ `>`
193 /// - `&` ⇒ `&`
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"<"[..],
210 b'>' => &b">"[..],
211 b'&' => &b"&"[..],
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