Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ let config = codespan_reporting::term::Config::default();

term::emit_to_write_style(&mut writer.lock(), &config, &files, &diagnostic)?;

// For HTML output (e.g. embedding in SVG), use `HtmlWriter`. Access the inner
// buffer with `get_ref()` / `get_mut()`, and recover it with `into_inner()`.
let mut html = term::HtmlWriter::new(Vec::new());
term::emit_to_write_style(&mut html, &config, &files, &diagnostic)?;
let num_lines = html.get_ref().iter().filter(|&&b| b == b'\n').count() + 1;
let _html_bytes = html.into_inner()?;

Ok::<(), codespan_reporting::files::Error>(())
```

Expand Down
168 changes: 8 additions & 160 deletions codespan-reporting/examples/readme_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
//! cargo run --example readme_preview svg > codespan-reporting/assets/readme_preview.svg
//! ```

use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity};
use std::io::Write;

use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term::{self, Config};
use codespan_reporting::term::{GeneralWrite, GeneralWriteResult};
use codespan_reporting::term::{self, Config, HtmlWriter};

#[cfg(feature = "termcolor")]
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
Expand Down Expand Up @@ -99,23 +100,23 @@ fn main() -> anyhow::Result<()> {

match parse_args()? {
Opts::Svg => {
let mut writer = SvgWriter::new();
let config = Config::default();
let mut writer = HtmlWriter::new(Vec::new());

for diagnostic in &diagnostics {
term::emit_to_write_style(&mut writer, &config, &file, diagnostic)?;
}

let num_lines = writer.line_count();
let num_lines = writer.get_ref().iter().filter(|&&b| b == b'\n').count() + 1;
let buffer = writer.into_inner()?;
let content = String::from_utf8(buffer)?;

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 content = writer.into_string();

let stdout = std::io::stdout();
let writer = &mut stdout.lock();

Expand Down Expand Up @@ -217,156 +218,3 @@ fn main() -> anyhow::Result<()> {

Ok(())
}

// This whole example requires the std feature, but below is feature agnostic for reference

#[cfg(feature = "std")]
type WriterBuffer = Vec<u8>;

#[cfg(not(feature = "std"))]
type WriterBuffer = String;

pub struct SvgWriter {
buffer: WriterBuffer,
span_open: bool,
}

impl SvgWriter {
pub fn new() -> Self {
SvgWriter {
buffer: WriterBuffer::default(),
span_open: false,
}
}

#[cfg(feature = "std")]
pub fn into_string(self) -> String {
String::from_utf8(self.buffer).unwrap()
}

#[cfg(not(feature = "std"))]
pub fn into_string(self) -> String {
self.buffer
}

#[cfg(feature = "std")]
pub fn line_count(&self) -> usize {
self.buffer.iter().filter(|byte| **byte == b'\n').count() + 1
}

#[cfg(not(feature = "std"))]
pub fn line_count(&self) -> usize {
self.buffer.lines().count()
}

/// Close any open span
fn close_span(&mut self) -> GeneralWriteResult {
if self.span_open {
write!(self.buffer, "</span>")?;
self.span_open = false;
}
Ok(())
}

/// Open a new span with the given CSS class
fn open_span(&mut self, class: &str) -> GeneralWriteResult {
// close existing first
self.close_span()?;
write!(self.buffer, "<span class=\"{}\">", class)?;
self.span_open = true;
Ok(())
}
}

#[cfg(feature = "std")]
impl std::io::Write for SvgWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut last = 0;
for (i, &b) in buf.iter().enumerate() {
let escape = match b {
b'<' => b"&lt;"[..].as_ref(),
b'>' => b"&gt;"[..].as_ref(),
b'&' => b"&amp;"[..].as_ref(),
_ => continue,
};
self.buffer.write_all(&buf[last..i])?;
self.buffer.write_all(escape)?;
last = i + 1;
}
self.buffer.write_all(&buf[last..])?;
Ok(buf.len())
}

fn flush(&mut self) -> std::io::Result<()> {
self.buffer.flush()
}
}

#[cfg(not(feature = "std"))]
impl core::fmt::Write for SvgWriter {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
let mut last = 0;
// TODO match indices
for (i, b) in s.chars().enumerate() {
let escape = match b {
'<' => "&lt;",
'>' => "&gt;",
'&' => "&amp;",
_ => continue,
};
self.buffer.write_str(&s[last..i])?;
self.buffer.write_str(escape)?;
last = i + 1;
}
self.buffer.write_str(&s[last..])?;
Ok(())
}
}

impl codespan_reporting::term::WriteStyle for SvgWriter {
fn set_header(&mut self, severity: Severity) -> GeneralWriteResult {
let class = match severity {
Severity::Bug => "header-bug",
Severity::Error => "header-error",
Severity::Warning => "header-warning",
Severity::Note => "header-note",
Severity::Help => "header-help",
};
self.open_span(class)
}

fn set_header_message(&mut self) -> GeneralWriteResult {
self.open_span("header-message")
}

fn set_line_number(&mut self) -> GeneralWriteResult {
self.open_span("line-number")
}

fn set_note_bullet(&mut self) -> GeneralWriteResult {
self.open_span("note-bullet")
}

fn set_source_border(&mut self) -> GeneralWriteResult {
self.open_span("source-border")
}

fn set_label(&mut self, severity: Severity, label_style: LabelStyle) -> GeneralWriteResult {
let sev = match severity {
Severity::Bug => "bug",
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Note => "note",
Severity::Help => "help",
};
let typ = match label_style {
LabelStyle::Primary => "primary",
LabelStyle::Secondary => "secondary",
};
self.open_span(&format!("label-{}-{}", typ, sev))
}

fn reset(&mut self) -> GeneralWriteResult {
self.close_span()
}
}
156 changes: 156 additions & 0 deletions codespan-reporting/src/term/html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! HTML writer for emitting diagnostics as styled HTML.

#[cfg(not(feature = "std"))]
use alloc::format;

use crate::diagnostic::{LabelStyle, Severity};

use super::renderer::{GeneralWrite, GeneralWriteResult, WriteStyle};

/// Writer that emits diagnostics as HTML with `<span class="...">` for styling.
pub struct HtmlWriter<W> {
upstream: W,
span_open: bool,
}

impl<W: GeneralWrite> HtmlWriter<W> {
pub fn new(upstream: W) -> Self {
HtmlWriter {
upstream,
span_open: false,
}
}

/// Get a reference to the underlying writer.
pub fn get_ref(&self) -> &W {
&self.upstream
}

/// Get a mutable reference to the underlying writer.
pub fn get_mut(&mut self) -> &mut W {
&mut self.upstream
}

/// Return the underlying writer, closing any open span first.
#[cfg(feature = "std")]
pub fn into_inner(mut self) -> std::io::Result<W> {
self.close_span()?;
Ok(self.upstream)
}

/// Return the underlying writer, closing any open span first.
#[cfg(not(feature = "std"))]
pub fn into_inner(mut self) -> Result<W, core::fmt::Error> {
self.close_span()?;
Ok(self.upstream)
}

/// Close any open span
fn close_span(&mut self) -> GeneralWriteResult {
if self.span_open {
write!(self.upstream, "</span>")?;
self.span_open = false;
}
Ok(())
}

/// Open a new span with the given CSS class
fn open_span(&mut self, class: &str) -> GeneralWriteResult {
self.close_span()?;
write!(self.upstream, "<span class=\"{}\">", class)?;
self.span_open = true;
Ok(())
}
}

#[cfg(feature = "std")]
impl<W: std::io::Write> std::io::Write for HtmlWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut last = 0;
for (i, &b) in buf.iter().enumerate() {
let escape = match b {
b'<' => b"&lt;"[..].as_ref(),
b'>' => b"&gt;"[..].as_ref(),
b'&' => b"&amp;"[..].as_ref(),
_ => continue,
};
self.upstream.write_all(&buf[last..i])?;
self.upstream.write_all(escape)?;
last = i + 1;
}
self.upstream.write_all(&buf[last..])?;
Ok(buf.len())
}

fn flush(&mut self) -> std::io::Result<()> {
self.upstream.flush()
}
}

#[cfg(not(feature = "std"))]
impl<W: core::fmt::Write> core::fmt::Write for HtmlWriter<W> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let mut last = 0;
for (i, c) in s.char_indices() {
let escape = match c {
'<' => "&lt;",
'>' => "&gt;",
'&' => "&amp;",
_ => continue,
};
self.upstream.write_str(&s[last..i])?;
self.upstream.write_str(escape)?;
last = i + c.len_utf8();
}
self.upstream.write_str(&s[last..])?;
Ok(())
}
}

impl<W: GeneralWrite> WriteStyle for HtmlWriter<W> {
fn set_header(&mut self, severity: Severity) -> GeneralWriteResult {
let class = match severity {
Severity::Bug => "header-bug",
Severity::Error => "header-error",
Severity::Warning => "header-warning",
Severity::Note => "header-note",
Severity::Help => "header-help",
};
self.open_span(class)
}

fn set_header_message(&mut self) -> GeneralWriteResult {
self.open_span("header-message")
}

fn set_line_number(&mut self) -> GeneralWriteResult {
self.open_span("line-number")
}

fn set_note_bullet(&mut self) -> GeneralWriteResult {
self.open_span("note-bullet")
}

fn set_source_border(&mut self) -> GeneralWriteResult {
self.open_span("source-border")
}

fn set_label(&mut self, severity: Severity, label_style: LabelStyle) -> GeneralWriteResult {
let sev = match severity {
Severity::Bug => "bug",
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Note => "note",
Severity::Help => "help",
};
let typ = match label_style {
LabelStyle::Primary => "primary",
LabelStyle::Secondary => "secondary",
};
self.open_span(&format!("label-{}-{}", typ, sev))
}

fn reset(&mut self) -> GeneralWriteResult {
self.close_span()
}
}
2 changes: 2 additions & 0 deletions codespan-reporting/src/term/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::diagnostic::Diagnostic;
use crate::files::Files;

mod config;
mod html;
mod renderer;
mod views;

Expand All @@ -15,6 +16,7 @@ pub use config::{Chars, Config, DisplayStyle};
// re-export
#[cfg(feature = "termcolor")]
pub use config::styles::{termcolor, Styles, StylesWriter};
pub use html::HtmlWriter;

pub use renderer::{GeneralWrite, GeneralWriteResult, Renderer, WriteStyle};
pub use views::{RichDiagnostic, ShortDiagnostic};
Expand Down
Loading