diff --git a/Cargo.toml b/Cargo.toml index e34de3c..fba25c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ authors = ["Dean J. Karstedt "] license-file = "LICENSE" [dependencies] -pmp-emitter = { version = "1.0.0", path = "crates/pmp-emitter" } +pmp-emitter = { path = "crates/pmp-emitter" } clap = { version = "^4", features = ["derive"] } owo-colors = { version = "^4", features = ["supports-colors"] } diff --git a/crates/pmp-emitter/Cargo.toml b/crates/pmp-emitter/Cargo.toml index cd6de45..210ecb5 100644 --- a/crates/pmp-emitter/Cargo.toml +++ b/crates/pmp-emitter/Cargo.toml @@ -6,11 +6,6 @@ edition.workspace = true authors.workspace = true license-file.workspace = true -[lib] -proc-macro = true - [dependencies] -syn = { version = "^2", features = ["full"] } -quote = "^1" -darling = "^0" -proc-macro2 = "^1" +pmp-emitter-core = { path = "crates/pmp-emitter-core" } +pmp-emitter-misc = { path = "crates/pmp-emitter-misc" } diff --git a/crates/pmp-emitter/crates/pmp-emitter-misc/Cargo.toml b/crates/pmp-emitter/crates/pmp-emitter-misc/Cargo.toml new file mode 100644 index 0000000..1e16e2f --- /dev/null +++ b/crates/pmp-emitter/crates/pmp-emitter-misc/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pmp-emitter-misc" +version = "1.0.0" + +edition.workspace = true +authors.workspace = true +license-file.workspace = true + +[dependencies] diff --git a/crates/pmp-emitter/crates/pmp-emitter-misc/src/lib.rs b/crates/pmp-emitter/crates/pmp-emitter-misc/src/lib.rs new file mode 100644 index 0000000..0669415 --- /dev/null +++ b/crates/pmp-emitter/crates/pmp-emitter-misc/src/lib.rs @@ -0,0 +1,4 @@ + +pub trait Emittable { + fn emit(&self); +} diff --git a/crates/pmp-emitter/src/lib.rs b/crates/pmp-emitter/src/lib.rs index 29633a3..b92d606 100644 --- a/crates/pmp-emitter/src/lib.rs +++ b/crates/pmp-emitter/src/lib.rs @@ -1,185 +1,5 @@ -use quote::quote; -use proc_macro::TokenStream; -use syn::{ Lit, Path, Type, Ident, DeriveInput, parse_macro_input }; -use darling::{ ast, Error, Result, FromMeta, FromVariant, FromDeriveInput }; +#[doc(hidden)] +pub extern crate pmp_emitter_misc; -// ---------------------------------------------------------------------------------------------- // - -#[derive(Debug, FromDeriveInput)] -#[darling(forward_attrs(pmp_emitter))] -struct EmitterInput { - ident: Ident, - data: ast::Data, -} - -#[derive(Debug, FromVariant)] -#[darling(forward_attrs(pmp_emitter))] -struct VariantReceiver { - ident: Ident, - fields: ast::Fields, - attrs: Vec, -} - -// ---------------------------------------------------------------------------------------------- // - -#[derive(Debug, Clone, Copy)] -struct StrictBool(bool); - -/// - `descriptor = "..."` - prefix shown before all variant messages (default: enum name) -/// - `exit = true|false` - whether to call process::exit (default: false) -#[derive(Debug, FromMeta)] -struct EnumAttrs { - #[darling(default)] - descriptor: Option, - #[darling(default)] - exit: Option, -} - -/// - `message = "..."` - error message (default: variant name as title case) -/// - `colorizer = fn` - optional fn(String) -> String for formatting -/// - `exit = true|false` - overrides enum-level exit -#[derive(Debug, FromMeta)] -struct VariantAttrs { - #[darling(default)] - exit: Option, - #[darling(default)] - message: Option, - #[darling(default)] - colorizer: Option, -} - -// ---------------------------------------------------------------------------------------------- // - -impl FromMeta for StrictBool -{ - fn from_value(value: &Lit) -> Result - { - match value { - Lit::Bool(b) => Ok(StrictBool(b.value)), - _ => Err(Error::unexpected_lit_type(value)), - } - } - - // intentionally NOT implementing from_word() -} - -fn parse_pmp_attr(attrs: &[syn::Attribute]) -> T -where - T: Default, -{ - attrs - .iter() - .find(|a| a.path().is_ident("pmp_emitter")) - .map(|a| T::from_meta(&a.meta).expect("invalid pmp_emitter attribute")) - .unwrap_or_default() -} - -impl Default for EnumAttrs { - fn default() -> Self { - Self { descriptor: None, exit: None } - } -} - -impl Default for VariantAttrs { - fn default() -> Self { - Self { message: None, exit: None, colorizer: None } - } -} - -// ---------------------------------------------------------------------------------------------- // - -fn pascal_to_title(s: &str) -> String -{ - let mut out = String::new(); - - for (i, ch) in s.chars().enumerate() - { - if ch.is_uppercase() && i > 0 { out.push(' '); } - out.push(ch); - } - - out -} - -// ---------------------------------------------------------------------------------------------- // - -#[proc_macro_derive(PmpEmitter, attributes(pmp_emitter))] -pub fn pmp_emitter(item: TokenStream) -> TokenStream -{ - let input = parse_macro_input!(item as DeriveInput); - - // parse enum shape - let shape = match EmitterInput::from_derive_input(&input) { - Ok(a) => a, - Err(e) => return e.write_errors().into(), - }; - - // parse enum-level #[pmp_emitter(...)] directly from the raw attrs - let enum_attrs: EnumAttrs = parse_pmp_attr(&input.attrs); - - let enum_ident = &shape.ident; - let descriptor = enum_attrs.descriptor - .unwrap_or_else(|| pascal_to_title(&enum_ident.to_string())); - let global_exit = enum_attrs.exit.map(|b| b.0).unwrap_or(false); - - let variants = shape - .data - .take_enum() - .expect("PmpEmitter can only be derived on enums"); - - let match_arms = variants.iter().map(|v| - { - let var_ident = &v.ident; - let title = pascal_to_title(&var_ident.to_string()); - - // parse variant-level #[pmp_emitter(...)] from the variant's raw attrs - let vattrs: VariantAttrs = parse_pmp_attr(&v.attrs); - - let message = vattrs.message.unwrap_or_else(|| title.clone()); - let should_exit = vattrs.exit.map(|b| b.0).unwrap_or(global_exit); - - // ┌─ Output format ───────────────────────────────┐ - // │ {descriptor}: {Variant Title} ("{message}") │ - // │ │ - // │ {stacktrace} │ - // └───────────────────────────────────────────────┘ - let header = format!("{}: {} (\"{}\")", descriptor, title, message); - - let formatted_header = match &vattrs.colorizer - { - Some(path) => quote! { #path(#header) }, - None => quote! { #header.to_string() }, - }; - - let exit_tokens = if should_exit { quote! { std::process::exit(1); } } else { quote! {} }; - - match v.fields.style - { - ast::Style::Tuple => quote! { - #enum_ident::#var_ident(trace) => - { - println!("{}\n\n{}", #formatted_header, trace); - #exit_tokens - } - }, - _ => quote! { - #enum_ident::#var_ident => - { - println!("{}", #formatted_header); - #exit_tokens - } - }, - } - }); - - quote! { - impl #enum_ident - { - pub fn emit(&self) { - match self { - #(#match_arms),* - } - } - } - }.into() -} +pub use pmp_emitter_misc::Emittable; +pub use pmp_emitter_core::PmpEmitter;