feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+73
@@ -0,0 +1,73 @@
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
|
||||
#[derive(FromMeta, PartialEq, Eq, Debug)]
|
||||
enum Volume {
|
||||
Whisper,
|
||||
Talk,
|
||||
Shout,
|
||||
}
|
||||
|
||||
/// A more complex example showing the ability to skip at a field or struct
|
||||
/// level while still tracking which type parameters need to be bounded.
|
||||
/// This can be seen by expanding this example using `cargo expand`.
|
||||
#[derive(FromMeta)]
|
||||
#[allow(dead_code)]
|
||||
enum Emphasis<T> {
|
||||
Constant(Volume),
|
||||
Variable(darling::util::PathList),
|
||||
#[darling(skip)]
|
||||
PerPhoneme(Option<T>),
|
||||
Strided {
|
||||
#[darling(skip)]
|
||||
step: Vec<T>,
|
||||
#[darling(multiple)]
|
||||
volume: Vec<Volume>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(speak))]
|
||||
struct SpeakingOptions<T, U> {
|
||||
max_volume: U,
|
||||
#[darling(skip, default)]
|
||||
additional_data: Vec<T>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Phoneme {
|
||||
#[allow(dead_code)]
|
||||
first: String,
|
||||
}
|
||||
|
||||
// This is probably the holy grail for `darling`'s own internal use-case:
|
||||
// Auto-apply `Default` bound to skipped *field* types in `where` clause.
|
||||
impl<T, U> Default for SpeakingOptions<T, U>
|
||||
where
|
||||
Vec<T>: Default,
|
||||
U: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_volume: Default::default(),
|
||||
additional_data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let derive_input = syn::parse_str(
|
||||
r#"
|
||||
#[derive(Speak)]
|
||||
#[speak(max_volume = "shout")]
|
||||
enum HtmlElement {
|
||||
Div(String)
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let parsed: SpeakingOptions<Phoneme, Volume> =
|
||||
FromDeriveInput::from_derive_input(&derive_input).unwrap();
|
||||
assert_eq!(parsed.max_volume, Volume::Shout);
|
||||
assert_eq!(parsed.additional_data.len(), 0);
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
// The use of fields in debug print commands does not count as "used",
|
||||
// which causes the fields to trigger an unwanted dead code warning.
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! This example shows how to do struct and field parsing using darling.
|
||||
|
||||
use darling::{ast, FromDeriveInput, FromField, FromMeta};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::parse_str;
|
||||
|
||||
/// A speaking volume. Deriving `FromMeta` will cause this to be usable
|
||||
/// as a string value for a meta-item key.
|
||||
#[derive(Debug, Clone, Copy, FromMeta)]
|
||||
#[darling(default)]
|
||||
enum Volume {
|
||||
Normal,
|
||||
Whisper,
|
||||
Shout,
|
||||
}
|
||||
|
||||
impl Default for Volume {
|
||||
fn default() -> Self {
|
||||
Volume::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// Support parsing from a full derive input. Unlike FromMeta, this isn't
|
||||
/// composable; each darling-dependent crate should have its own struct to handle
|
||||
/// when its trait is derived.
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
// This line says that we want to process all attributes declared with `my_trait`,
|
||||
// and that darling should panic if this receiver is given an enum.
|
||||
#[darling(attributes(my_trait), supports(struct_any))]
|
||||
struct MyInputReceiver {
|
||||
/// The struct ident.
|
||||
ident: syn::Ident,
|
||||
|
||||
/// The type's generics. You'll need these any time your trait is expected
|
||||
/// to work with types that declare generics.
|
||||
generics: syn::Generics,
|
||||
|
||||
/// Receives the body of the struct or enum. We don't care about
|
||||
/// struct fields because we previously told darling we only accept structs.
|
||||
data: ast::Data<(), MyFieldReceiver>,
|
||||
|
||||
/// The Input Receiver demands a volume, so use `Volume::Normal` if the
|
||||
/// caller doesn't provide one.
|
||||
#[darling(default)]
|
||||
volume: Volume,
|
||||
}
|
||||
|
||||
impl ToTokens for MyInputReceiver {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let MyInputReceiver {
|
||||
ref ident,
|
||||
ref generics,
|
||||
ref data,
|
||||
volume,
|
||||
} = *self;
|
||||
|
||||
let (imp, ty, wher) = generics.split_for_impl();
|
||||
let fields = data
|
||||
.as_ref()
|
||||
.take_struct()
|
||||
.expect("Should never be enum")
|
||||
.fields;
|
||||
|
||||
// Generate the format string which shows each field and its name
|
||||
let fmt_string = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
// We have to preformat the ident in this case so we can fall back
|
||||
// to the field index for unnamed fields. It's not easy to read,
|
||||
// unfortunately.
|
||||
format!(
|
||||
"{} = {{}}",
|
||||
f.ident
|
||||
.as_ref()
|
||||
.map(|v| format!("{}", v))
|
||||
.unwrap_or_else(|| format!("{}", i))
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
// Generate the actual values to fill the format string.
|
||||
let field_list = fields
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
let field_volume = f.volume.unwrap_or(volume);
|
||||
|
||||
// This works with named or indexed fields, so we'll fall back to the index so we can
|
||||
// write the output as a key-value pair.
|
||||
let field_ident = f.ident
|
||||
.as_ref()
|
||||
.map(|v| quote!(#v))
|
||||
.unwrap_or_else(|| {
|
||||
let i = syn::Index::from(i);
|
||||
quote!(#i)
|
||||
});
|
||||
|
||||
match field_volume {
|
||||
Volume::Normal => quote!(self.#field_ident),
|
||||
Volume::Shout => {
|
||||
quote!(::std::string::ToString::to_string(&self.#field_ident).to_uppercase())
|
||||
}
|
||||
Volume::Whisper => {
|
||||
quote!(::std::string::ToString::to_string(&self.#field_ident).to_lowercase())
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tokens.extend(quote! {
|
||||
impl #imp Speak for #ident #ty #wher {
|
||||
fn speak(&self, writer: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
write!(writer, #fmt_string, #(#field_list),*)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromField)]
|
||||
#[darling(attributes(my_trait))]
|
||||
struct MyFieldReceiver {
|
||||
/// Get the ident of the field. For fields in tuple or newtype structs or
|
||||
/// enum bodies, this can be `None`.
|
||||
ident: Option<syn::Ident>,
|
||||
|
||||
/// This magic field name pulls the type from the input.
|
||||
ty: syn::Type,
|
||||
|
||||
/// We declare this as an `Option` so that during tokenization we can write
|
||||
/// `field.volume.unwrap_or(derive_input.volume)` to facilitate field-level
|
||||
/// overrides of struct-level settings.
|
||||
///
|
||||
/// Because this field is an `Option`, we don't need to include `#[darling(default)]`
|
||||
volume: Option<Volume>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = r#"#[derive(MyTrait)]
|
||||
#[my_trait(volume = "shout")]
|
||||
pub struct Foo {
|
||||
#[my_trait(volume = "whisper")]
|
||||
bar: bool,
|
||||
|
||||
baz: i64,
|
||||
}"#;
|
||||
|
||||
let parsed = parse_str(input).unwrap();
|
||||
let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap();
|
||||
let tokens = quote!(#receiver);
|
||||
|
||||
println!(
|
||||
r#"
|
||||
INPUT:
|
||||
|
||||
{}
|
||||
|
||||
PARSED AS:
|
||||
|
||||
{:?}
|
||||
|
||||
EMITS:
|
||||
|
||||
{}
|
||||
"#,
|
||||
input, receiver, tokens
|
||||
);
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
use darling::{util::parse_expr, FromDeriveInput, FromMeta};
|
||||
use syn::{parse_quote, Expr};
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(demo))]
|
||||
pub struct Receiver {
|
||||
#[darling(with = parse_expr::preserve_str_literal, map = Some)]
|
||||
example1: Option<Expr>,
|
||||
#[darling(
|
||||
// A closure can be used in lieu of a path.
|
||||
with = |m| Ok(String::from_meta(m)?.to_uppercase()),
|
||||
default
|
||||
)]
|
||||
example2: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = Receiver::from_derive_input(&parse_quote! {
|
||||
#[demo(example1 = test::path, example2 = "hello")]
|
||||
struct Example;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(input.example1, Some(parse_quote!(test::path)));
|
||||
assert_eq!(input.example2, "HELLO".to_string());
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//! This example demonstrates techniques for performing custom error handling
|
||||
//! in a derive-input receiver.
|
||||
//!
|
||||
//! 1. Using `darling::Result` as a carrier to preserve the error for later display
|
||||
//! 1. Using `Result<T, syn::Meta>` to attempt a recovery in imperative code
|
||||
//! 1. Using the `map` darling meta-item to post-process a field before returning
|
||||
//! 1. Using the `and_then` darling meta-item to post-process the receiver before returning
|
||||
|
||||
use darling::{FromDeriveInput, FromMeta};
|
||||
use syn::parse_str;
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(my_trait), and_then = MyInputReceiver::autocorrect)]
|
||||
pub struct MyInputReceiver {
|
||||
/// This field must be present and a string or else parsing will panic.
|
||||
#[darling(map = MyInputReceiver::make_string_shouty)]
|
||||
name: String,
|
||||
|
||||
/// If this field fails to parse, the struct can still be built; the field
|
||||
/// will contain the error. The consuming struct can then decide if this
|
||||
/// blocks code generation. If so, panic or fail in `and_then`.
|
||||
frequency: darling::Result<i64>,
|
||||
|
||||
/// If this field fails to parse, the struct can still be built; the field
|
||||
/// will contain an `Err` with the original `syn::Meta`. This can be used
|
||||
/// for alternate parsing attempts before panicking.
|
||||
amplitude: Result<u64, syn::Meta>,
|
||||
}
|
||||
|
||||
impl MyInputReceiver {
|
||||
/// This function will be called by `darling` _after_ it's finished parsing the
|
||||
/// `name` field but before initializing `name` with the resulting value. It's
|
||||
/// a good place for transforms that are easiest to express on already-built
|
||||
/// types.
|
||||
fn make_string_shouty(s: String) -> String {
|
||||
s.to_uppercase()
|
||||
}
|
||||
|
||||
/// This function will be called by `darling` _after_ it's finished parsing the
|
||||
/// input but before returning to the caller. This is a good place to initialize
|
||||
/// skipped fields or to perform corrections that don't lend themselves to being
|
||||
/// done elsewhere.
|
||||
fn autocorrect(self) -> darling::Result<Self> {
|
||||
let Self {
|
||||
name,
|
||||
frequency,
|
||||
amplitude,
|
||||
} = self;
|
||||
|
||||
// Amplitude doesn't have a sign, so if we received a negative number then
|
||||
// we'll go ahead and make it positive.
|
||||
let amplitude = match amplitude {
|
||||
Ok(amp) => amp,
|
||||
Err(mi) => (i64::from_meta(&mi)?).unsigned_abs(),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
frequency,
|
||||
amplitude: Ok(amplitude),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = r#"#[derive(MyTrait)]
|
||||
#[my_trait(name="Jon", amplitude = "-1", frequency = 1)]
|
||||
pub struct Foo;"#;
|
||||
|
||||
let parsed = parse_str(input).unwrap();
|
||||
let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap();
|
||||
|
||||
println!(
|
||||
r#"
|
||||
INPUT:
|
||||
|
||||
{}
|
||||
|
||||
PARSED AS:
|
||||
|
||||
{:?}
|
||||
"#,
|
||||
input, receiver
|
||||
);
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
use darling::{ast::Data, util::Flag, FromDeriveInput, FromMeta};
|
||||
use darling_macro::FromField;
|
||||
|
||||
#[derive(Default, FromMeta)]
|
||||
#[darling(from_word = || Ok(Default::default()), from_expr = |expr| Ok(ErrorPolicy::from(expr)))]
|
||||
struct ErrorPolicy {
|
||||
warn: Flag,
|
||||
value: Option<syn::Expr>,
|
||||
}
|
||||
|
||||
impl From<&'_ syn::Expr> for ErrorPolicy {
|
||||
fn from(expr: &'_ syn::Expr) -> Self {
|
||||
ErrorPolicy {
|
||||
warn: Flag::default(),
|
||||
value: Some(expr.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromField)]
|
||||
#[darling(attributes(toml))]
|
||||
struct Field {
|
||||
default: Option<ErrorPolicy>,
|
||||
recover: Option<ErrorPolicy>,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(toml))]
|
||||
struct TomlConfig {
|
||||
data: Data<(), Field>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = TomlConfig::from_derive_input(&syn::parse_quote! {
|
||||
struct Config {
|
||||
#[toml(default, recover(warn))]
|
||||
field1: String,
|
||||
#[toml(default = String::new())]
|
||||
field2: String,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(input.data.is_struct());
|
||||
let fields = input.data.take_struct().expect("input is struct").fields;
|
||||
assert!(fields[0].default.is_some());
|
||||
assert!(fields[0]
|
||||
.recover
|
||||
.as_ref()
|
||||
.map(|r| r.warn.is_present())
|
||||
.unwrap_or(false));
|
||||
assert!(fields[1]
|
||||
.default
|
||||
.as_ref()
|
||||
.map(|d| d.value.is_some())
|
||||
.unwrap_or(false));
|
||||
assert!(fields[1].recover.is_none());
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//! This example demonstrates:
|
||||
//!
|
||||
//! - The behavior of a derived `FromMeta` implementation for heterogeneous enums
|
||||
//! (i.e. enums that include a mix of unit, newtype and struct variants).
|
||||
//! - Using `#[darling(word)]` to specify a unit variant to use when a receiver field
|
||||
//! is specified without a value (i.e. a unit variant to use for deriving the
|
||||
//! `FromMeta::from_word` method).
|
||||
//! - Using `#[darling(default)]` on a receiver field to fall back to `Default::default()`
|
||||
//! for the enum's value when the receiver field is not specified by the caller.
|
||||
|
||||
use darling::{Error, FromDeriveInput, FromMeta};
|
||||
use syn::parse_quote;
|
||||
|
||||
/// A playback volume.
|
||||
#[derive(Debug, FromMeta, PartialEq, Eq)]
|
||||
enum Volume {
|
||||
Normal,
|
||||
#[darling(word)]
|
||||
Low,
|
||||
High,
|
||||
#[darling(rename = "dB")]
|
||||
Decibels(u8),
|
||||
}
|
||||
|
||||
impl Default for Volume {
|
||||
fn default() -> Self {
|
||||
Volume::Normal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(play))]
|
||||
struct PlayReceiver {
|
||||
#[darling(default)]
|
||||
volume: Volume,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// `Default::default()` is used when `volume` is not specified.
|
||||
let missing_volume = PlayReceiver::from_derive_input(&parse_quote! {
|
||||
#[play]
|
||||
struct Player;
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(Volume::Normal, missing_volume.volume);
|
||||
|
||||
// `#[darling(word)]` unit variant is used when `volume` is specified as a word with no value.
|
||||
let empty_volume = PlayReceiver::from_derive_input(&parse_quote! {
|
||||
#[play(volume)]
|
||||
struct Player;
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(Volume::Low, empty_volume.volume);
|
||||
|
||||
// Specified `volume` value is used when provided.
|
||||
let unit_variant_volume = PlayReceiver::from_derive_input(&parse_quote! {
|
||||
#[play(volume(high))]
|
||||
struct Player;
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(Volume::High, unit_variant_volume.volume);
|
||||
let newtype_volume = PlayReceiver::from_derive_input(&parse_quote! {
|
||||
#[play(volume(dB = 100))]
|
||||
struct Player;
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(Volume::Decibels(100), newtype_volume.volume);
|
||||
|
||||
// Multiple `volume` values result in an error.
|
||||
let err = PlayReceiver::from_derive_input(&parse_quote! {
|
||||
#[play(volume(low, dB = 20))]
|
||||
struct Player;
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
Error::too_many_items(1).at("volume").to_string()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//! Example showing potentially-nested meta item parsing with `darling::util::Override`.
|
||||
//!
|
||||
//! Based on https://stackoverflow.com/q/68046070/86381 by https://github.com/peterjoel
|
||||
|
||||
// The use of fields in debug print commands does not count as "used",
|
||||
// which causes the fields to trigger an unwanted dead code warning.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use darling::{util::Override, FromDeriveInput, FromMeta};
|
||||
use syn::{Ident, Path};
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(myderive))]
|
||||
struct MyDeriveInput {
|
||||
ident: Ident,
|
||||
/// We can infer the right "table" behavior for this derive, but we want the caller to be
|
||||
/// explicit that they're expecting the inference behavior to avoid cluttering some hypothetical
|
||||
/// database. Therefore this field is required, but can take word form or key-value form.
|
||||
///
|
||||
/// To make this field optional, we could add `#[darling(default)]`, or we could
|
||||
/// wrap it in `Option` if the presence or absence of the word makes a difference.
|
||||
table: Override<Table>,
|
||||
}
|
||||
|
||||
impl MyDeriveInput {
|
||||
fn table(&self) -> Cow<'_, Table> {
|
||||
match &self.table {
|
||||
Override::Explicit(value) => Cow::Borrowed(value),
|
||||
Override::Inherit => Cow::Owned(Table {
|
||||
name: self.ident.to_string(),
|
||||
value: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromMeta)]
|
||||
struct Table {
|
||||
name: String,
|
||||
value: Option<Path>,
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> darling::Result<MyDeriveInput> {
|
||||
FromDeriveInput::from_derive_input(&syn::parse_str(s)?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let missing = from_str(
|
||||
r#"
|
||||
#[derive(MyTrait)]
|
||||
struct Foo(u64);
|
||||
"#,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
let short_form = from_str(
|
||||
r#"
|
||||
#[derive(MyTrait)]
|
||||
#[myderive(table)]
|
||||
struct Foo(u64);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let long_form = from_str(
|
||||
r#"
|
||||
#[derive(MyTrait)]
|
||||
#[myderive(table(name = "Custom"))]
|
||||
struct Foo(u64);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("Error when missing: {}", missing);
|
||||
println!("Short form: {:?}", short_form.table());
|
||||
println!("Long form: {:?}", long_form.table());
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// The use of fields in debug print commands does not count as "used",
|
||||
// which causes the fields to trigger an unwanted dead code warning.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use darling::{ast, util, FromDeriveInput, FromField};
|
||||
use syn::{Ident, Type};
|
||||
|
||||
#[derive(Debug, FromField)]
|
||||
#[darling(attributes(lorem))]
|
||||
pub struct LoremField {
|
||||
ident: Option<Ident>,
|
||||
ty: Type,
|
||||
#[darling(default)]
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromDeriveInput)]
|
||||
#[darling(attributes(lorem), supports(struct_named))]
|
||||
pub struct Lorem {
|
||||
ident: Ident,
|
||||
data: ast::Data<util::Ignored, LoremField>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let good_input = r#"#[derive(Lorem)]
|
||||
pub struct Foo {
|
||||
#[lorem(skip)]
|
||||
bar: bool,
|
||||
|
||||
baz: i64,
|
||||
}"#;
|
||||
|
||||
let bad_input = r#"#[derive(Lorem)]
|
||||
pub struct BadFoo(String, u32);"#;
|
||||
|
||||
let parsed = syn::parse_str(good_input).unwrap();
|
||||
let receiver = Lorem::from_derive_input(&parsed).unwrap();
|
||||
let wrong_shape_parsed = syn::parse_str(bad_input).unwrap();
|
||||
let wrong_shape = Lorem::from_derive_input(&wrong_shape_parsed).expect_err("Shape was wrong");
|
||||
|
||||
println!(
|
||||
r#"
|
||||
INPUT:
|
||||
|
||||
{}
|
||||
|
||||
PARSED AS:
|
||||
|
||||
{:?}
|
||||
|
||||
BAD INPUT:
|
||||
|
||||
{}
|
||||
|
||||
PRODUCED ERROR:
|
||||
|
||||
{}
|
||||
"#,
|
||||
good_input, receiver, bad_input, wrong_shape
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user