mirror of https://github.com/zkat/miette.git
feat(help): update macro to allow optional help text
Fixes: https://github.com/zkat/miette/issues/148
This commit is contained in:
parent
5e54b29acf
commit
cb10790d8e
|
|
@ -9,3 +9,9 @@ workspace=false
|
||||||
install_crate="cargo-release"
|
install_crate="cargo-release"
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
args = ["release", "--workspace", "${@}"]
|
args = ["release", "--workspace", "${@}"]
|
||||||
|
|
||||||
|
[tasks.readme]
|
||||||
|
workspace=false
|
||||||
|
install_crate="cargo-readme"
|
||||||
|
command = "cargo"
|
||||||
|
args = ["readme"]
|
||||||
|
|
|
||||||
28
README.md
28
README.md
|
|
@ -372,6 +372,34 @@ pub struct MyErrorType {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### ... help text
|
||||||
|
|
||||||
|
`miette` provides two facilities for supplying help text for your errors:
|
||||||
|
|
||||||
|
The first is the `#[help()]` format attribute that applies to structs or enum variants:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic(help("try doing this instead"))]
|
||||||
|
struct Foo;
|
||||||
|
```
|
||||||
|
|
||||||
|
The other is by programmatically supplying the help text as a field to your
|
||||||
|
diagnostic:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic()]
|
||||||
|
struct Foo {
|
||||||
|
#[help]
|
||||||
|
advice: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = Foo { advice: Some("try doing this instead".to_string()) };
|
||||||
|
```
|
||||||
|
|
||||||
#### ... multiple related errors
|
#### ... multiple related errors
|
||||||
|
|
||||||
`miette` supports collecting multiple errors into a single diagnostic, and
|
`miette` supports collecting multiple errors into a single diagnostic, and
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,10 @@ impl DiagnosticConcreteArgs {
|
||||||
let labels = Labels::from_fields(fields)?;
|
let labels = Labels::from_fields(fields)?;
|
||||||
let source_code = SourceCode::from_fields(fields)?;
|
let source_code = SourceCode::from_fields(fields)?;
|
||||||
let related = Related::from_fields(fields)?;
|
let related = Related::from_fields(fields)?;
|
||||||
|
let help = Help::from_fields(fields)?;
|
||||||
Ok(DiagnosticConcreteArgs {
|
Ok(DiagnosticConcreteArgs {
|
||||||
code: None,
|
code: None,
|
||||||
help: None,
|
help,
|
||||||
related,
|
related,
|
||||||
severity: None,
|
severity: None,
|
||||||
labels,
|
labels,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::{format_ident, quote};
|
||||||
use syn::{
|
use syn::{
|
||||||
parenthesized,
|
parenthesized,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned,
|
||||||
Fields, Token,
|
Fields, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -15,8 +16,9 @@ use crate::{
|
||||||
forward::WhichFn,
|
forward::WhichFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Help {
|
pub enum Help {
|
||||||
pub display: Display,
|
Display(Display),
|
||||||
|
Field(syn::Member),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Help {
|
impl Parse for Help {
|
||||||
|
|
@ -38,16 +40,14 @@ impl Parse for Help {
|
||||||
args,
|
args,
|
||||||
has_bonus_display: false,
|
has_bonus_display: false,
|
||||||
};
|
};
|
||||||
Ok(Help { display })
|
Ok(Help::Display(display))
|
||||||
} else {
|
} else {
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
Ok(Help {
|
Ok(Help::Display(Display {
|
||||||
display: Display {
|
fmt: input.parse()?,
|
||||||
fmt: input.parse()?,
|
args: TokenStream::new(),
|
||||||
args: TokenStream::new(),
|
has_bonus_display: false,
|
||||||
has_bonus_display: false,
|
}))
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(syn::Error::new(ident.span(), "not a help"))
|
Err(syn::Error::new(ident.span(), "not a help"))
|
||||||
|
|
@ -56,30 +56,83 @@ impl Parse for Help {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Help {
|
impl Help {
|
||||||
|
pub(crate) fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||||
|
match fields {
|
||||||
|
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||||
|
syn::Fields::Unnamed(unnamed) => {
|
||||||
|
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||||
|
for (i, field) in fields.iter().enumerate() {
|
||||||
|
for attr in &field.attrs {
|
||||||
|
if attr.path.is_ident("help") {
|
||||||
|
let help = if let Some(ident) = field.ident.clone() {
|
||||||
|
syn::Member::Named(ident)
|
||||||
|
} else {
|
||||||
|
syn::Member::Unnamed(syn::Index {
|
||||||
|
index: i as u32,
|
||||||
|
span: field.span(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return Ok(Some(Help::Field(help)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
gen_all_variants_with(
|
gen_all_variants_with(
|
||||||
variants,
|
variants,
|
||||||
WhichFn::Help,
|
WhichFn::Help,
|
||||||
|ident, fields, DiagnosticConcreteArgs { help, .. }| {
|
|ident, fields, DiagnosticConcreteArgs { help, .. }| {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let display = &help.as_ref()?.display;
|
match &help.as_ref()? {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
Help::Display(display) => {
|
||||||
Some(quote! {
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
|
Some(quote! {
|
||||||
})
|
Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Help::Field(member) => {
|
||||||
|
let help = match &member {
|
||||||
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(quote! {
|
||||||
|
Self::#ident #display_pat => #help.as_ref().map(|h| -> std::boxed::Box<dyn std::fmt::Display + 'a> { std::boxed::Box::new(format!("{}", h)) }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let (fmt, args) = self.display.expand_shorthand_cloned(&display_members);
|
match self {
|
||||||
Some(quote! {
|
Help::Display(display) => {
|
||||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
#[allow(unused_variables, deprecated)]
|
Some(quote! {
|
||||||
let Self #display_pat = self;
|
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
#[allow(unused_variables, deprecated)]
|
||||||
|
let Self #display_pat = self;
|
||||||
|
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
Help::Field(member) => Some(quote! {
|
||||||
|
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||||
|
#[allow(unused_variables, deprecated)]
|
||||||
|
let Self #display_pat = self;
|
||||||
|
self.#member.as_ref().map(|h| -> std::boxed::Box<dyn std::fmt::Display + 'a> { std::boxed::Box::new(format!("{}", h)) })
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ mod source_code;
|
||||||
mod url;
|
mod url;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic, source_code, label, related))]
|
#[proc_macro_derive(Diagnostic, attributes(diagnostic, source_code, label, related, help))]
|
||||||
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let cmd = match Diagnostic::from_derive_input(input) {
|
let cmd = match Diagnostic::from_derive_input(input) {
|
||||||
|
|
|
||||||
36
src/lib.rs
36
src/lib.rs
|
|
@ -371,6 +371,42 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! #### ... help text
|
||||||
|
//! `miette` provides two facilities for supplying help text for your errors:
|
||||||
|
//
|
||||||
|
//! The first is the `#[help()]` format attribute that applies to structs or
|
||||||
|
//! enum variants:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use miette::Diagnostic;
|
||||||
|
//! use thiserror::Error;
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug, Diagnostic, Error)]
|
||||||
|
//! #[error("welp")]
|
||||||
|
//! #[diagnostic(help("try doing this instead"))]
|
||||||
|
//! struct Foo;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The other is by programmatically supplying the help text as a field to your
|
||||||
|
//! diagnostic:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use miette::Diagnostic;
|
||||||
|
//! use thiserror::Error;
|
||||||
|
//!
|
||||||
|
//! #[derive(Debug, Diagnostic, Error)]
|
||||||
|
//! #[error("welp")]
|
||||||
|
//! #[diagnostic()]
|
||||||
|
//! struct Foo {
|
||||||
|
//! #[help]
|
||||||
|
//! advice: Option<String>,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! let err = Foo {
|
||||||
|
//! advice: Some("try doing this instead".to_string()),
|
||||||
|
//! };
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! ### ... multiple related errors
|
//! ### ... multiple related errors
|
||||||
//!
|
//!
|
||||||
//! `miette` supports collecting multiple errors into a single diagnostic, and
|
//! `miette` supports collecting multiple errors into a single diagnostic, and
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,62 @@ fn fmt_help() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_field() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic()]
|
||||||
|
struct Foo {
|
||||||
|
#[help]
|
||||||
|
do_this: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"x".to_string(),
|
||||||
|
Foo {
|
||||||
|
do_this: Some("x".into())
|
||||||
|
}
|
||||||
|
.help()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic()]
|
||||||
|
enum Bar {
|
||||||
|
A(#[help] Option<String>),
|
||||||
|
B {
|
||||||
|
#[help]
|
||||||
|
do_this: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"x".to_string(),
|
||||||
|
Bar::A(Some("x".into())).help().unwrap().to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"x".to_string(),
|
||||||
|
Bar::B {
|
||||||
|
do_this: Some("x".into())
|
||||||
|
}
|
||||||
|
.help()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic()]
|
||||||
|
struct Baz(#[help] Option<String>);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"x".to_string(),
|
||||||
|
Baz(Some("x".into())).help().unwrap().to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_named_struct() {
|
fn test_snippet_named_struct() {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue