mirror of https://github.com/zkat/miette.git
feat(protocol) overhauled entire protocol to be based on byte offsets (#1)
Includes other improvements! BREAKING CHANGE: Yeah this is pretty much a rewrite.
This commit is contained in:
parent
3b3a07bcb5
commit
6b51694733
|
|
@ -12,6 +12,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
indenter = "0.3.3"
|
indenter = "0.3.3"
|
||||||
|
thiserror = "1.0.26"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
thiserror = "1.0.26"
|
thiserror = "1.0.26"
|
||||||
|
|
|
||||||
29
README.md
29
README.md
|
|
@ -6,7 +6,7 @@ you FAIL miette? you fail her compiler like the unsafe C program? oh! oh! jail f
|
||||||
|
|
||||||
Here's an example of using something like `thisdiagnostic` to define Diagnostics declaratively.
|
Here's an example of using something like `thisdiagnostic` to define Diagnostics declaratively.
|
||||||
|
|
||||||
```rust
|
```ignore
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use thisdiagnostic::Diagnostic;
|
use thisdiagnostic::Diagnostic;
|
||||||
|
|
||||||
|
|
@ -40,24 +40,27 @@ pub enum MyDiagnostic {
|
||||||
)]
|
)]
|
||||||
#[error("Tried to add a {bad_type} to a {good_type}")]
|
#[error("Tried to add a {bad_type} to a {good_type}")]
|
||||||
BadArithmetic {
|
BadArithmetic {
|
||||||
src: PathBuf,
|
// Regular error metadata for programmatic use.
|
||||||
other_src: PathBuf,
|
|
||||||
|
|
||||||
good_type: Type,
|
good_type: Type,
|
||||||
bad_type: Type,
|
bad_type: Type,
|
||||||
bad_var: Var,
|
bad_var: Var,
|
||||||
|
|
||||||
#[span_source(src)]
|
// Anything implementing the Source trait can be used as a source.
|
||||||
#[label("This is a {bad_type}")]
|
src: PathBuf,
|
||||||
bad_var_span: SourceSpan,
|
other_src: String,
|
||||||
|
|
||||||
#[span_source(src)]
|
// The context is the span of code that will be rendered.
|
||||||
#[label("This is a {good_type}")]
|
// There can be multiple contexts in a Diagnostic.
|
||||||
|
#[context(src, "This region is where things went wrong.")]
|
||||||
|
ctx: SourceSpan,
|
||||||
|
|
||||||
|
// Highlights underline and label specific subsections of the context.
|
||||||
|
#[highlight(ctx, "This is a {bad_type}")]
|
||||||
|
bad_var_span: SourceSpan, // These can span multiple lines!
|
||||||
|
|
||||||
|
// They can be optional!
|
||||||
|
#[highlight(ctx, "This is a {good_type}")]
|
||||||
good_var_span: Option<SourceSpan>,
|
good_var_span: Option<SourceSpan>,
|
||||||
|
|
||||||
#[span(other_src)]
|
|
||||||
#[label("{bad_var} is defined here")]
|
|
||||||
bad_var_definition_span: SourceSpan, // multiline span
|
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*!
|
/*!
|
||||||
Iterate over error `.source()` chains.
|
Iterate over error `.source()` chains.
|
||||||
|
|
||||||
NOTE: This module is taken wholesale from https://crates.io/crates/eyre.
|
NOTE: This module is taken wholesale from <https://crates.io/crates/eyre>.
|
||||||
*/
|
*/
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub use std::io;
|
||||||
|
|
||||||
|
pub use thiserror::Error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Error enum for miette. Used by certain operations in the protocol.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum MietteError {
|
||||||
|
#[error(transparent)]
|
||||||
|
IoError(#[from] io::Error),
|
||||||
|
#[error("The given offset is outside the bounds of its Source")]
|
||||||
|
OutOfBounds
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
pub use chain::*;
|
pub use error::*;
|
||||||
pub use protocol::*;
|
pub use protocol::*;
|
||||||
pub use reporter::*;
|
pub use reporter::*;
|
||||||
|
|
||||||
mod chain;
|
mod chain;
|
||||||
|
mod error;
|
||||||
mod source_impls;
|
mod source_impls;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod reporter;
|
mod reporter;
|
||||||
|
|
|
||||||
150
src/protocol.rs
150
src/protocol.rs
|
|
@ -1,5 +1,12 @@
|
||||||
|
/*!
|
||||||
|
This module defines the core of the miette protocol: a series of types and traits
|
||||||
|
that you can implement to get access to miette's (and related library's) full
|
||||||
|
reporting and such features.
|
||||||
|
*/
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::{self, Read};
|
|
||||||
|
use crate::MietteError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Adds rich metadata to your Error that can be used by [DiagnosticReporter] to print
|
Adds rich metadata to your Error that can be used by [DiagnosticReporter] to print
|
||||||
|
|
@ -13,7 +20,7 @@ pub trait Diagnostic: std::error::Error + Send + Sync + 'static {
|
||||||
/// `E0123` or Enums will work just fine.
|
/// `E0123` or Enums will work just fine.
|
||||||
fn code(&self) -> &(dyn Display + 'static);
|
fn code(&self) -> &(dyn Display + 'static);
|
||||||
|
|
||||||
/// Diagnostic severity. This may be used by [Reporter]s to change the
|
/// Diagnostic severity. This may be used by [DiagnosticReporter]s to change the
|
||||||
/// display format of this diagnostic.
|
/// display format of this diagnostic.
|
||||||
fn severity(&self) -> Severity;
|
fn severity(&self) -> Severity;
|
||||||
|
|
||||||
|
|
@ -23,9 +30,9 @@ pub trait Diagnostic: std::error::Error + Send + Sync + 'static {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional contextual details. This is typically used for adding
|
/// Additional contextual snippets. This is typically used for adding
|
||||||
/// marked-up source file output the way compilers often do.
|
/// marked-up source file output the way compilers often do.
|
||||||
fn details(&self) -> Option<&[DiagnosticDetail]> {
|
fn snippets(&self) -> Option<&[DiagnosticSnippet]> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,52 +76,137 @@ pub enum Severity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Represents a readable source of some sort: a source file, a String, etc.
|
Represents a readable source of some sort.
|
||||||
|
|
||||||
|
This trait is able to support simple Source types like [String]s, as well
|
||||||
|
as more involved types like indexes into centralized `SourceMap`-like types,
|
||||||
|
file handles, and even network streams.
|
||||||
|
|
||||||
|
If you can read it, you can source it,
|
||||||
|
and it's not necessary to read the whole thing--meaning you should be able to
|
||||||
|
support Sources which are gigabytes or larger in size.
|
||||||
*/
|
*/
|
||||||
pub trait Source: std::fmt::Debug + Send + Sync + 'static {
|
pub trait Source: std::fmt::Debug + Send + Sync + 'static {
|
||||||
/// Get a `Read`er from a given [Source].
|
/// Read the bytes for a specific span from this Source.
|
||||||
fn open(&self) -> io::Result<Box<dyn Read>>;
|
fn read_span<'a>(&'a self, span: &SourceSpan)
|
||||||
|
-> Result<Box<dyn SpanContents<'a> + '_>, MietteError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Details and additional context to be displayed.
|
Contents of a [Source] covered by [SourceSpan].
|
||||||
|
|
||||||
|
Includes line and column information to optimize highlight calculations.
|
||||||
|
*/
|
||||||
|
pub trait SpanContents<'a> {
|
||||||
|
/// Reference to the data inside the associated span, in bytes.
|
||||||
|
fn data(&self) -> &[u8];
|
||||||
|
/// The 0-indexed line in the associated [Source] where the data begins.
|
||||||
|
fn line(&self) -> usize;
|
||||||
|
/// The 0-indexed column in the associated [Source] where the data begins,
|
||||||
|
/// relative to `line`.
|
||||||
|
fn column(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Basic implementation of the [SpanContents] trait, for convenience.
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MietteSpanContents<'a> {
|
||||||
|
/// Data from a [Source], in bytes.
|
||||||
|
data: &'a [u8],
|
||||||
|
// The 0-indexed line where the associated [SourceSpan] _starts_.
|
||||||
|
line: usize,
|
||||||
|
// The 0-indexed column where the associated [SourceSpan] _starts_.
|
||||||
|
column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MietteSpanContents<'a> {
|
||||||
|
/// Make a new [MietteSpanContents] object.
|
||||||
|
pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> {
|
||||||
|
MietteSpanContents { data, line, column }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
|
fn data(&self) -> &[u8] {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
fn line(&self) -> usize {
|
||||||
|
self.line
|
||||||
|
}
|
||||||
|
fn column(&self) -> usize {
|
||||||
|
self.column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A snippet from a [Source] to be displayed with a message and possibly some highlights.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DiagnosticDetail {
|
pub struct DiagnosticSnippet {
|
||||||
/// Explanation of this specific diagnostic detail.
|
/// Explanation of this specific diagnostic snippet.
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
/// The "filename" for this diagnostic.
|
/// The "filename" for this snippet.
|
||||||
pub source_name: String,
|
pub source_name: String,
|
||||||
/// A [Source] that can be used to read the actual text of a source.
|
/// A [Source] that can be used to read the actual text of a source.
|
||||||
pub source: Box<dyn Source>,
|
pub source: Box<dyn Source>,
|
||||||
/// The primary [SourceSpan] where this diagnostic is located.
|
/// The primary [SourceSpan] where this diagnostic is located.
|
||||||
pub span: SourceSpan,
|
pub context: SourceSpan,
|
||||||
/// Additional [SourceSpan]s that can add secondary context.
|
/// Additional [SourceSpan]s that mark specific sections of the span, for
|
||||||
pub other_spans: Option<Vec<SourceSpan>>,
|
/// example, to underline specific text within the larger span. They're
|
||||||
|
/// paired with labels that should be applied to those sections.
|
||||||
|
pub highlights: Option<Vec<(String, SourceSpan)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Span within a [Source] with an associated message.
|
Span within a [Source] with an associated message.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SourceSpan {
|
pub struct SourceSpan {
|
||||||
/// A name for the thing this SourceSpan is actually pointing to.
|
|
||||||
pub label: String,
|
|
||||||
/// The start of the span.
|
/// The start of the span.
|
||||||
pub start: SourceLocation,
|
pub start: SourceOffset,
|
||||||
/// The end of the span. Optional
|
/// The (exclusive) end of the span.
|
||||||
pub end: Option<SourceLocation>,
|
pub end: SourceOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceSpan {
|
||||||
|
pub fn new(start: SourceOffset, end: SourceOffset) -> Self {
|
||||||
|
assert!(
|
||||||
|
start.offset() <= end.offset(),
|
||||||
|
"Starting offset must come before the end offset."
|
||||||
|
);
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.end.offset() - self.start.offset() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.start.offset() == self.end.offset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Specific location in a [SourceSpan]
|
"Raw" type for the byte offset from the beginning of a [Source].
|
||||||
*/
|
*/
|
||||||
#[derive(Debug)]
|
pub type ByteOffset = usize;
|
||||||
pub struct SourceLocation {
|
|
||||||
/// 0-indexed column of location.
|
/**
|
||||||
pub column: usize,
|
Newtype that represents the [ByteOffset] from the beginning of a [Source]
|
||||||
/// 0-indexed line of location.
|
*/
|
||||||
pub line: usize,
|
#[derive(Clone, Copy, Debug)]
|
||||||
/// 0-indexed _character_ offset of location.
|
pub struct SourceOffset(ByteOffset);
|
||||||
pub offset: usize,
|
|
||||||
|
impl SourceOffset {
|
||||||
|
/// Actual byte offset.
|
||||||
|
pub fn offset(&self) -> ByteOffset {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ByteOffset> for SourceOffset {
|
||||||
|
fn from(bytes: ByteOffset) -> Self {
|
||||||
|
SourceOffset(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
137
src/reporter.rs
137
src/reporter.rs
|
|
@ -2,56 +2,104 @@
|
||||||
Basic reporter for Diagnostics. Probably good enough for most use-cases,
|
Basic reporter for Diagnostics. Probably good enough for most use-cases,
|
||||||
but largely meant to be an example.
|
but largely meant to be an example.
|
||||||
*/
|
*/
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use indenter::indented;
|
use indenter::indented;
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::chain::Chain;
|
||||||
use crate::protocol::{Diagnostic, DiagnosticDetail, DiagnosticReporter, Severity};
|
use crate::protocol::{Diagnostic, DiagnosticSnippet, DiagnosticReporter, Severity};
|
||||||
|
|
||||||
pub struct Reporter;
|
/**
|
||||||
|
Reference implementation of the [DiagnosticReporter] trait. This is generally
|
||||||
|
good enough for simple use-cases, but you might want to implement your own if
|
||||||
|
you want custom reporting for your tool or app.
|
||||||
|
*/
|
||||||
|
pub struct MietteReporter;
|
||||||
|
|
||||||
impl Reporter {
|
impl MietteReporter {
|
||||||
fn render_detail(
|
fn render_snippet(&self, f: &mut fmt::Formatter<'_>, snippet: &DiagnosticSnippet) -> fmt::Result {
|
||||||
&self,
|
use fmt::Write as _;
|
||||||
f: &mut core::fmt::Formatter<'_>,
|
write!(f, "\n[{}]", snippet.source_name)?;
|
||||||
detail: &DiagnosticDetail,
|
if let Some(msg) = &snippet.message {
|
||||||
) -> core::fmt::Result {
|
|
||||||
use core::fmt::Write as _;
|
|
||||||
write!(f, "\n[{}]", detail.source_name)?;
|
|
||||||
if let Some(msg) = &detail.message {
|
|
||||||
write!(f, " {}:", msg)?;
|
write!(f, " {}:", msg)?;
|
||||||
}
|
}
|
||||||
writeln!(
|
writeln!(f)?;
|
||||||
indented(f),
|
writeln!(f)?;
|
||||||
"\n\n({}) @ line {}, col {} ",
|
let context_data = snippet
|
||||||
detail.span.label,
|
.source
|
||||||
detail.span.start.line + 1,
|
.read_span(&snippet.context)
|
||||||
detail.span.start.column + 1
|
.map_err(|_| fmt::Error)?;
|
||||||
)?;
|
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
||||||
if let Some(other_spans) = &detail.other_spans {
|
let mut line = context_data.line();
|
||||||
for span in other_spans {
|
let mut column = context_data.column();
|
||||||
writeln!(
|
let mut offset = snippet.context.start.offset();
|
||||||
indented(f),
|
let mut line_offset = offset;
|
||||||
"\n{} @ line {}, col {} ",
|
let mut iter = context.chars().peekable();
|
||||||
span.label,
|
let mut line_str = String::new();
|
||||||
span.start.line + 1,
|
let highlights = snippet.highlights.as_ref();
|
||||||
span.start.column + 1
|
while let Some(char) = iter.next() {
|
||||||
)?;
|
offset += char.len_utf8();
|
||||||
|
match char {
|
||||||
|
'\r' => {
|
||||||
|
if iter.next_if_eq(&'\n').is_some() {
|
||||||
|
offset += 1;
|
||||||
|
line += 1;
|
||||||
|
column = 0;
|
||||||
|
} else {
|
||||||
|
line_str.push(char);
|
||||||
|
column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\n' => {
|
||||||
|
line += 1;
|
||||||
|
column = 0;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
line_str.push(char);
|
||||||
|
column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.peek().is_none() {
|
||||||
|
line += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if column == 0 || iter.peek().is_none() {
|
||||||
|
writeln!(indented(f), "{: <2} | {}", line, line_str)?;
|
||||||
|
line_str.clear();
|
||||||
|
if let Some(highlights) = highlights {
|
||||||
|
for (label, span) in highlights {
|
||||||
|
if span.start.offset() >= line_offset && span.end.offset() < offset {
|
||||||
|
// Highlight only covers one line.
|
||||||
|
write!(indented(f), "{: <2} | ", "⫶")?;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{} ",
|
||||||
|
" ".repeat(span.start.offset() - line_offset),
|
||||||
|
"^".repeat(span.len())
|
||||||
|
)?;
|
||||||
|
writeln!(f, "{}", label)?;
|
||||||
|
} else if span.start.offset() < offset
|
||||||
|
&& span.start.offset() >= line_offset
|
||||||
|
&& span.end.offset() >= offset
|
||||||
|
{
|
||||||
|
// Multiline highlight.
|
||||||
|
todo!("Multiline highlights.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line_offset = offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticReporter for Reporter {
|
impl DiagnosticReporter for MietteReporter {
|
||||||
fn debug(
|
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
&self,
|
use fmt::Write as _;
|
||||||
diagnostic: &(dyn Diagnostic),
|
|
||||||
f: &mut core::fmt::Formatter<'_>,
|
|
||||||
) -> core::fmt::Result {
|
|
||||||
use core::fmt::Write as _;
|
|
||||||
|
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return core::fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sev = match diagnostic.severity() {
|
let sev = match diagnostic.severity() {
|
||||||
|
|
@ -75,10 +123,10 @@ impl DiagnosticReporter for Reporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(details) = diagnostic.details() {
|
if let Some(snippets) = diagnostic.snippets() {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
for detail in details {
|
for snippet in snippets {
|
||||||
self.render_detail(f, detail)?;
|
self.render_snippet(f, snippet)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,16 +141,13 @@ impl DiagnosticReporter for Reporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Literally what it says on the tin.
|
||||||
pub struct JokeReporter;
|
pub struct JokeReporter;
|
||||||
|
|
||||||
impl DiagnosticReporter for JokeReporter {
|
impl DiagnosticReporter for JokeReporter {
|
||||||
fn debug(
|
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
&self,
|
|
||||||
diagnostic: &(dyn Diagnostic),
|
|
||||||
f: &mut core::fmt::Formatter<'_>,
|
|
||||||
) -> core::fmt::Result {
|
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
return core::fmt::Debug::fmt(diagnostic, f);
|
return fmt::Debug::fmt(diagnostic, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sev = match diagnostic.severity() {
|
let sev = match diagnostic.severity() {
|
||||||
|
|
@ -124,8 +169,8 @@ impl DiagnosticReporter for JokeReporter {
|
||||||
f,
|
f,
|
||||||
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
|
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
|
||||||
diagnostic.code(),
|
diagnostic.code(),
|
||||||
diagnostic.details().map(|details| {
|
diagnostic.snippets().map(|snippets| {
|
||||||
details.iter().map(|detail| detail.message.clone()).collect::<Option<Vec<String>>>()
|
snippets.iter().map(|snippet| snippet.message.clone()).collect::<Option<Vec<String>>>()
|
||||||
}).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into())
|
}).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into())
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,82 @@
|
||||||
/*!
|
/*!
|
||||||
Default trait implementations for [Source].
|
Default trait implementations for [Source].
|
||||||
*/
|
*/
|
||||||
use std::fs::File;
|
use crate::{MietteError, MietteSpanContents, Source, SourceSpan, SpanContents};
|
||||||
use std::io::{Cursor, Read, Result};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use crate::{Source};
|
|
||||||
|
|
||||||
impl Source for String {
|
impl Source for String {
|
||||||
fn open(&self) -> Result<Box<dyn Read>> {
|
fn read_span(&self, span: &SourceSpan) -> Result<Box<dyn SpanContents + '_>, MietteError> {
|
||||||
Ok(Box::new(Cursor::new(self.clone())))
|
let mut offset = 0usize;
|
||||||
|
let mut start_line = 0usize;
|
||||||
|
let mut start_column = 0usize;
|
||||||
|
let mut iter = self.chars().peekable();
|
||||||
|
while let Some(char) = iter.next() {
|
||||||
|
if offset < span.start.offset() {
|
||||||
|
match char {
|
||||||
|
'\r' => {
|
||||||
|
if iter.next_if_eq(&'\n').is_some() {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
start_line += 1;
|
||||||
|
start_column = 0;
|
||||||
|
}
|
||||||
|
'\n' => {
|
||||||
|
start_line += 1;
|
||||||
|
start_column = 0;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
start_column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset >= span.end.offset() {
|
||||||
|
return Ok(Box::new(MietteSpanContents::new(
|
||||||
|
&self.as_bytes()[span.start.offset()..=span.end.offset()],
|
||||||
|
start_line,
|
||||||
|
start_column,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += char.len_utf8();
|
||||||
|
}
|
||||||
|
Err(MietteError::OutOfBounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source for Path {
|
#[cfg(test)]
|
||||||
fn open(&self) -> Result<Box<dyn Read>> {
|
mod tests {
|
||||||
Ok(Box::new(File::open(self)?))
|
use super::*;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Source for PathBuf {
|
#[test]
|
||||||
fn open(&self) -> Result<Box<dyn Read>> {
|
fn basic() -> Result<(), MietteError> {
|
||||||
Ok(Box::new(File::open(self)?))
|
let src = String::from("foo\n");
|
||||||
|
let contents = src.read_span(&SourceSpan {
|
||||||
|
start: 0.into(),
|
||||||
|
end: 3.into(),
|
||||||
|
})?;
|
||||||
|
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn middle() -> Result<(), MietteError> {
|
||||||
|
let src = String::from("foo\nbar\nbaz\n");
|
||||||
|
let contents = src.read_span(&SourceSpan {
|
||||||
|
start: 4.into(),
|
||||||
|
end: 7.into(),
|
||||||
|
})?;
|
||||||
|
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_crlf() -> Result<(), MietteError> {
|
||||||
|
let src = String::from("foo\r\nbar\r\nbaz\r\n");
|
||||||
|
let contents = src.read_span(&SourceSpan {
|
||||||
|
start: 5.into(),
|
||||||
|
end: 9.into(),
|
||||||
|
})?;
|
||||||
|
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
use std::{fmt, io};
|
use std::fmt;
|
||||||
|
|
||||||
use miette::{
|
use miette::{
|
||||||
Diagnostic, DiagnosticDetail, DiagnosticReporter, Reporter, Severity, SourceLocation,
|
Diagnostic, DiagnosticSnippet, DiagnosticReporter, MietteError, MietteReporter, Severity, SourceSpan,
|
||||||
SourceSpan,
|
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error)]
|
#[derive(Error)]
|
||||||
#[error("oops!")]
|
#[error("oops!")]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
details: Vec<DiagnosticDetail>,
|
details: Vec<DiagnosticSnippet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for MyBad {
|
impl fmt::Debug for MyBad {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
Reporter.debug(self, f)
|
MietteReporter.debug(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,13 +30,13 @@ impl Diagnostic for MyBad {
|
||||||
Some(&["try doing it better next time?"])
|
Some(&["try doing it better next time?"])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn details(&self) -> Option<&[DiagnosticDetail]> {
|
fn snippets(&self) -> Option<&[DiagnosticSnippet]> {
|
||||||
Some(&self.details)
|
Some(&self.details)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic() -> io::Result<()> {
|
fn basic() -> Result<(), MietteError> {
|
||||||
let err = MyBad {
|
let err = MyBad {
|
||||||
details: Vec::new(),
|
details: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
@ -50,26 +49,27 @@ fn basic() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fancy() -> io::Result<()> {
|
fn fancy() -> Result<(), MietteError> {
|
||||||
|
let src = "source\n text\n here".to_string();
|
||||||
let err = MyBad {
|
let err = MyBad {
|
||||||
details: vec![DiagnosticDetail {
|
details: vec![DiagnosticSnippet {
|
||||||
message: Some("This is the part that broke".into()),
|
message: Some("This is the part that broke".into()),
|
||||||
source_name: "bad_file.rs".into(),
|
source_name: "bad_file.rs".into(),
|
||||||
source: Box::new("source_text".to_string()),
|
source: Box::new(src.clone()),
|
||||||
other_spans: None,
|
highlights: Some(vec![
|
||||||
span: SourceSpan {
|
("this bit here".into(), SourceSpan {
|
||||||
label: "this thing here is bad".into(),
|
start: 9.into(),
|
||||||
start: SourceLocation {
|
end: 12.into(),
|
||||||
line: 0,
|
})
|
||||||
column: 0,
|
]),
|
||||||
offset: 0,
|
context: SourceSpan {
|
||||||
},
|
start: 0.into(),
|
||||||
end: None,
|
end: (src.len() - 1).into(),
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
let out = format!("{:?}", err);
|
let out = format!("{:?}", err);
|
||||||
// println!("{}", out);
|
// println!("{}", out);
|
||||||
assert_eq!("Error[oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n (this thing here is bad) @ line 1, col 1 \n\n﹦try doing it better next time?\n".to_string(), out);
|
assert_eq!("Error[oops::my::bad]: oops!\n\n[bad_file.rs] This is the part that broke:\n\n 1 | source\n 2 | text\n ⫶ | ^^^^ this bit here\n 3 | here\n\n﹦try doing it better next time?\n".to_string(), out);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue