From 56d790559e81d42abc2c0c597ca800499a4e81df Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Wed, 19 May 2021 16:12:57 +0100 Subject: [PATCH] Scribe static str, documentation --- enumscribe/src/lib.rs | 103 ++++++++++++++++++-- enumscribe_derive/src/lib.rs | 114 +++++++++++++++++++++++ enumscribe_examples/examples/airports.rs | 10 +- 3 files changed, 216 insertions(+), 11 deletions(-) diff --git a/enumscribe/src/lib.rs b/enumscribe/src/lib.rs index 3f7b398..a81278b 100644 --- a/enumscribe/src/lib.rs +++ b/enumscribe/src/lib.rs @@ -1,34 +1,125 @@ +//! Traits for converting between enums and strings. This is only useful alongside the +//! [enumscribe_derive] crate, which provides derive macros for these traits. +//! +//! Here is a table to show which traits you should derive for your enum: +//! +//! | `ignore` used? | `other` used? | Conversion to string | Conversion from string | +//! |----------------|---------------|----------------------|------------------------| +//! | No | No | [ScribeStaticStr] | [TryUnscribe] | +//! | Yes | No | [TryScribeStaticStr] | [TryUnscribe] | +//! | No | Yes | [ScribeCowStr] | [Unscribe] | +//! | Yes | Yes | [TryScribeCowStr] | [Unscribe] | +//! +//! There are also [ScribeString] and [TryScribeString] traits which can be used in the same +//! situations as [ScribeCowStr] and [TryScribeCowStr], respectively. These traits produce a +//! `String` rather than a `Cow<'static, str>`, so they will always perform an allocation. +//! Therefore, you should prefer the `ScribeCowStr` traits over the `ScribeString` traits, unless +//! you *really* don't want to use a `Cow` for whatever reason. + #[macro_use] extern crate enumscribe_derive; -use std::borrow::Cow; - pub use enumscribe_derive::*; -//TODO +use std::borrow::Cow; + +/// Trait for converting an enum to a static string slice. +/// +/// Like all of the traits provided by enumscribe, this should not be implemented manually; use +/// `#[derive(ScribeStaticStr)]` provided by the [enumscribe_derive] crate instead. +/// +/// When deriving this trait, you may specify the string that a particular variant should be +/// converted to by annotating it with `#[enumscribe(str = "foo")]`. If this is omitted, the name +/// of the variant will be used instead. +/// +/// This trait can only be used if none of the enum's variants use `ignore` or `other`. If you have +/// variants that use `ignore`, use [TryScribeStaticStr] instead. If you have variants that use +/// `other`, use [ScribeCowStr]. If you have variants that use both, use [TryScribeCowStr]. pub trait ScribeStaticStr { fn scribe(&self) -> &'static str; } -//TODO +/// Trait for converting an enum to a static string slice, or `None` if the conversion fails. +/// +/// Like all of the traits provided by enumscribe, this should not be implemented manually; use +/// `#[derive(ScribeStaticStr)]` provided by the [enumscribe_derive] crate instead. +/// +/// When deriving this trait, you may specify the string that a particular variant should be +/// converted to by annotating it with `#[enumscribe(str = "foo")]`. If this is omitted, the name +/// of the variant will be used instead. +/// +/// You may also annotate a variant with `#[enumscribe(ignore)]`, in which case attempting to +/// convert the variant to a string will always result in `None`. +/// +/// This trait can only be used if none of the enum's variants use `other`. If you have variants +/// that use `other`, use [TryScribeCowStr] instead. pub trait TryScribeStaticStr { fn try_scribe(&self) -> Option<&'static str>; } +/// Trait for converting an enum to an allocated string. Generally, [ScribeCowStr] should be +/// preferred over this trait because it avoids unnecessary allocations. +/// +/// Like all of the traits provided by enumscribe, this should not be implemented manually; use +/// `#[derive(ScribeStaticStr)]` provided by the [enumscribe_derive] crate instead. +/// +/// This trait can only be used if none of the enum's variants use `ignore`. pub trait ScribeString { fn scribe(&self) -> String; } +/// Trait for converting an enum to an allocated string, or `None` if the conversion fails. +/// Generally, [TryScribeCowStr] should be preferred over this trait because it avoids unnecessary +/// allocations. +/// +/// Like all of the traits provided by enumscribe, this should not be implemented manually; use +/// `#[derive(ScribeStaticStr)]` provided by the [enumscribe_derive] crate instead. pub trait TryScribeString { fn try_scribe(&self) -> Option; } -//TODO +/// Trait for converting an enum to a clone-on-write string. +/// +/// Like all of the traits provided by enumscribe, this should not be implemented manually; use +/// `#[derive(ScribeStaticStr)]` provided by the [enumscribe_derive] crate instead. +/// +/// When deriving this trait, you may specify the string that a particular variant should be +/// converted to by annotating it with `#[enumscribe(str = "foo")]`. If this is omitted, the name +/// of the variant will be used instead. +/// +/// A maximum of one variant can be annotated with `#[enumscribe(other)]`. This variant must have +/// exactly one field, which must implement `Into`. Converting this variant to a string +/// will result in whatever the value of its field is. +/// +/// This trait can only be used if none of the enum's variants use `ignore`. If you have variants +/// that use `ignore`, use [TryScribeCowStr] instead. pub trait ScribeCowStr { fn scribe(&self) -> Cow<'static, str>; } -//TODO +// Trait for converting an enum to a clone-on-write string, or `None` if the conversion fails. +/// +/// Like all of the traits provided by enumscribe, this should not be implemented manually; use +/// `#[derive(ScribeStaticStr)]` provided by the [enumscribe_derive] crate instead. +/// +/// When deriving this trait, you may specify the string that a particular variant should be +/// converted to by annotating it with `#[enumscribe(str = "foo")]`. If this is omitted, the name +/// of the variant will be used instead. +/// +/// A maximum of one variant can be annotated with `#[enumscribe(other)]`. This variant must have +/// exactly one field, which must implement `Into`. Converting this variant to a string +/// will result in whatever the value of its field is. +/// +/// You may also annotate a variant with `#[enumscribe(ignore)]`, in which case attempting to +/// convert the variant to a string will always result in `None`. pub trait TryScribeCowStr { fn try_scribe(&self) -> Option>; } + +pub trait Unscribe { + fn unscribe(to_unscribe: &str) -> Self; +} + +pub trait TryUnscribe { + fn try_unscribe(to_unscribe: &str) -> Option; +} diff --git a/enumscribe_derive/src/lib.rs b/enumscribe_derive/src/lib.rs index 39435c1..5d3e443 100644 --- a/enumscribe_derive/src/lib.rs +++ b/enumscribe_derive/src/lib.rs @@ -1,3 +1,6 @@ +//! Derive macros for the traits provided by enumscribe, to help you easily convert your enums +//! to strings and vice-versa. + use proc_macro::TokenStream; use std::iter; @@ -26,6 +29,117 @@ macro_rules! proc_try { }; } +#[proc_macro_derive(ScribeStaticStr, attributes(enumscribe))] +pub fn derive_scribe_static_str(input: TokenStream) -> TokenStream { + let input: DeriveInput = syn::parse(input) + .expect("failed to parse input"); + + let enum_data = proc_try!(get_enum_data(&input)); + let parsed_enum = proc_try!(enums::parse_enum(enum_data)); + + let enum_ident = &input.ident; + let enum_idents = iter::repeat(enum_ident); + + let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); + let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); + + for variant in parsed_enum.variants.iter() { + match variant.match_variant( + |name| Ok(quote! { + #name + }), + |_| Err(MacroError::new(format!( + "cannot derive ScribeStaticStr for {} because the variant {} is marked as {}, so \ + there is no &'static str associated with it\n\ + hint: try deriving ScribeCowStr instead", + enum_ident.to_string(), variant.data.ident.to_string(), OTHER + ), variant.span)), + ) { + Ok(Some((pattern, result))) => { + match_patterns.push(pattern); + match_results.push(result); + }, + + Ok(None) => return MacroError::new(format!( + "cannot derive ScribeStaticStr for {} because the variant {} is marked as {}\n\ + explanation: since {} is ignored, it cannot be guaranteed that the enum can \ + always be successfully converted to a String\n\ + hint: try deriving TryScribeStaticStr instead", + enum_ident.to_string(), variant.data.ident.to_string(), IGNORE, + variant.data.ident.to_string(), + ), variant.span).into(), + + Err(err) => return err.into() + } + } + + (quote! { + impl ::enumscribe::ScribeStaticStr for #enum_ident { + fn scribe(&self) -> &'static str { + match self { + #(#enum_idents::#match_patterns => #match_results,)* + } + } + } + }).into() +} + +#[proc_macro_derive(TryScribeStaticStr, attributes(enumscribe))] +pub fn derive_try_scribe_static_str(input: TokenStream) -> TokenStream { + let input: DeriveInput = syn::parse(input) + .expect("failed to parse input"); + + let enum_data = proc_try!(get_enum_data(&input)); + let parsed_enum = proc_try!(enums::parse_enum(enum_data)); + + let enum_ident = &input.ident; + let enum_idents = iter::repeat(enum_ident); + + let mut ignore_variant = false; + let mut match_patterns = Vec::with_capacity(parsed_enum.variants.len()); + let mut match_results = Vec::with_capacity(parsed_enum.variants.len()); + + for variant in parsed_enum.variants.iter() { + match variant.match_variant( + |name| Ok(quote! { + ::std::option::Option::Some(#name) + }), + |_| Err(MacroError::new(format!( + "cannot derive TryScribeStaticStr for {} because the variant {} is marked as {}, so \ + there is no &'static str associated with it\n\ + hint: try deriving ScribeCowStr instead", + enum_ident.to_string(), variant.data.ident.to_string(), OTHER + ), variant.span)), + ) { + Ok(Some((pattern, result))) => { + match_patterns.push(pattern); + match_results.push(result); + }, + + Ok(None) => ignore_variant = true, + + Err(err) => return err.into() + } + } + + let ignore_arm = if ignore_variant { + quote! { _ => ::std::option::Option::None, } + } else { + quote! {} + }; + + (quote! { + impl ::enumscribe::TryScribeStaticStr for #enum_ident { + fn try_scribe(&self) -> ::std::option::Option<&'static str> { + match self { + #(#enum_idents::#match_patterns => #match_results,)* + #ignore_arm + } + } + } + }).into() +} + #[proc_macro_derive(ScribeString, attributes(enumscribe))] pub fn derive_scribe_string(input: TokenStream) -> TokenStream { let input: DeriveInput = syn::parse(input) diff --git a/enumscribe_examples/examples/airports.rs b/enumscribe_examples/examples/airports.rs index f770d66..8f719a8 100644 --- a/enumscribe_examples/examples/airports.rs +++ b/enumscribe_examples/examples/airports.rs @@ -2,7 +2,7 @@ use enumscribe::*; // #[derive(ScribeString)] -#[derive(TryScribeCowStr)] +#[derive(TryScribeStaticStr)] enum Airport { #[enumscribe(str = "LHR", case_insensitive)] Heathrow, @@ -12,14 +12,14 @@ enum Airport { Luton, #[enumscribe(str = "BHX", case_insensitive, ignore)] BirminghamInternational, - #[enumscribe(other)] - Other(String), + // #[enumscribe(other)] + // Other(String), } fn main() { let luton = Airport::Luton; println!("Hello, {:?}!", luton.try_scribe()); - let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); - println!("Hello, {:?}!", other.try_scribe()); + // let other = Airport::Other("Dedicated EasyJet-only airport".to_owned()); + // println!("Hello, {:?}!", other.try_scribe()); }