Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get_documentation() to EnumMessage. #206

Merged
merged 4 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions strum/src/additional_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,6 @@
//! - `detailed_message=".."`: Adds a more detailed message to a variant. If this value is omitted, then
//! `message` will be used in it's place.
//!
//! - Structured documentation, as in `/// ...`: If using `EnumMessage`, is accessible via get_documentation().
//!
//! - `props(key="value")`: Enables associating additional information with a given variant.
2 changes: 2 additions & 0 deletions strum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ pub trait IntoEnumIterator: Sized {
/// #[strum(message="I have a dog")]
/// #[strum(detailed_message="My dog's name is Spots")]
/// Dog,
/// /// I am documented.
/// #[strum(message="I don't have a cat")]
/// Cat,
/// }
Expand All @@ -123,6 +124,7 @@ pub trait IntoEnumIterator: Sized {
pub trait EnumMessage {
fn get_message(&self) -> Option<&'static str>;
fn get_detailed_message(&self) -> Option<&'static str>;
fn get_documentation(&self) -> Option<&'static str>;
fn get_serializations(&self) -> &'static [&'static str];
}

Expand Down
2 changes: 1 addition & 1 deletion strum_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ rustversion = "1.0"
syn = { version = "1.0", features = ["parsing", "extra-traits"] }

[dev-dependencies]
strum = "0.20"
strum = { path = "../strum" }
16 changes: 14 additions & 2 deletions strum_macros/src/helpers/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use syn::{
parse2, parse_str,
punctuated::Punctuated,
spanned::Spanned,
Attribute, DeriveInput, Ident, LitBool, LitStr, Path, Token, Variant, Visibility,
Attribute, DeriveInput, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path, Token, Variant, Visibility,
};

use super::case_style::CaseStyle;
Expand Down Expand Up @@ -164,6 +164,9 @@ pub enum VariantMeta {
kw: kw::serialize,
value: LitStr,
},
Documentation {
value: LitStr,
},
ToString {
kw: kw::to_string,
value: LitStr,
Expand Down Expand Up @@ -253,6 +256,7 @@ impl Spanned for VariantMeta {
match self {
VariantMeta::Message { kw, .. } => kw.span,
VariantMeta::DetailedMessage { kw, .. } => kw.span,
VariantMeta::Documentation { value } => value.span(),
VariantMeta::Serialize { kw, .. } => kw.span,
VariantMeta::ToString { kw, .. } => kw.span,
VariantMeta::Disabled(kw) => kw.span,
Expand All @@ -270,7 +274,15 @@ pub trait VariantExt {

impl VariantExt for Variant {
fn get_metadata(&self) -> syn::Result<Vec<VariantMeta>> {
get_metadata_inner("strum", &self.attrs)
let result = get_metadata_inner("strum", &self.attrs)?;
self.attrs.iter()
.filter(|attr| attr.path.is_ident("doc"))
.try_fold(result, |mut vec, attr| {
if let Meta::NameValue(MetaNameValue { lit: Lit::Str(value), .. }) = attr.parse_meta()? {
vec.push(VariantMeta::Documentation { value })
}
Ok(vec)
})
}
}

Expand Down
4 changes: 4 additions & 0 deletions strum_macros/src/helpers/variant_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct StrumVariantProperties {
pub ascii_case_insensitive: Option<bool>,
pub message: Option<LitStr>,
pub detailed_message: Option<LitStr>,
pub documentation: Vec<LitStr>,
pub string_props: Vec<(LitStr, LitStr)>,
serialize: Vec<LitStr>,
to_string: Option<LitStr>,
Expand Down Expand Up @@ -85,6 +86,9 @@ impl HasStrumVariantProperties for Variant {
detailed_message_kw = Some(kw);
output.detailed_message = Some(value);
}
VariantMeta::Documentation { value } => {
output.documentation.push(value);
}
VariantMeta::Serialize { value, .. } => {
output.serialize.push(value);
}
Expand Down
11 changes: 10 additions & 1 deletion strum_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ pub fn from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// #[derive(strum_macros::EnumMessage, Debug)]
/// #[allow(dead_code)]
/// enum Color {
/// /// Danger color.
/// #[strum(message = "Red", detailed_message = "This is very red")]
/// Red,
/// #[strum(message = "Simply Green")]
Expand Down Expand Up @@ -506,6 +507,13 @@ pub fn from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// }
/// }
///
/// fn get_documentation(&self) -> ::std::option::Option<&'static str> {
/// match self {
/// &Color::Red => ::std::option::Option::Some("Danger color."),
/// _ => None
/// }
/// }
///
/// fn get_serializations(&self) -> &'static [&'static str] {
/// match self {
/// &Color::Red => {
Expand All @@ -528,6 +536,7 @@ pub fn from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// let c = Color::Red;
/// assert_eq!("Red", c.get_message().unwrap());
/// assert_eq!("This is very red", c.get_detailed_message().unwrap());
/// assert_eq!("Danger color.", c.get_documentation().unwrap());
/// assert_eq!(["Red"], c.get_serializations());
/// ```
#[proc_macro_derive(EnumMessage, attributes(strum))]
Expand Down Expand Up @@ -613,7 +622,7 @@ pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
/// // Bring trait into scope
/// use std::str::FromStr;
/// use strum::{IntoEnumIterator, EnumMessage};
/// use strum_macros::{EnumDiscriminants, EnumIter, EnumString, EnumMessage};
/// use strum_macros::{EnumDiscriminants, EnumIter, EnumString};
///
/// #[derive(Debug)]
/// struct NonDefault;
Expand Down
40 changes: 38 additions & 2 deletions strum_macros/src/macros/enum_messages.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};
use syn::{Data, DeriveInput, LitStr};

use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};

Expand All @@ -17,12 +17,14 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

let mut arms = Vec::new();
let mut detailed_arms = Vec::new();
let mut documentation_arms = Vec::new();
let mut serializations = Vec::new();

for variant in variants {
let variant_properties = variant.get_variant_properties()?;
let messages = variant_properties.message.as_ref();
let detailed_messages = variant_properties.detailed_message.as_ref();
let documentation = &variant_properties.documentation;
let ident = &variant.ident;

use syn::Fields::*;
Expand Down Expand Up @@ -65,10 +67,34 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

if let Some(msg) = detailed_messages {
let params = params.clone();
// Push the simple message.
// Push the detailed message.
detailed_arms
.push(quote! { &#name::#ident #params => ::core::option::Option::Some(#msg) });
}

if !documentation.is_empty() {
let params = params.clone();
// Strip a single leading space from each documentation line.
let documentation: Vec<LitStr> = documentation.iter().map(|lit_str| {
let line = lit_str.value();
if line.starts_with(' ') {
LitStr::new(&line.as_str()[1..], lit_str.span())
} else {
lit_str.clone()
}
}).collect();
if documentation.len() == 1 {
let text = &documentation[0];
documentation_arms
.push(quote! { &#name::#ident #params => ::core::option::Option::Some(#text) });
} else {
// Push the documentation.
documentation_arms
.push(quote! {
&#name::#ident #params => ::core::option::Option::Some(concat!(#(concat!(#documentation, "\n")),*))
});
}
}
}

if arms.len() < variants.len() {
Expand All @@ -79,6 +105,10 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
detailed_arms.push(quote! { _ => ::core::option::Option::None });
}

if documentation_arms.len() < variants.len() {
documentation_arms.push(quote! { _ => ::core::option::Option::None });
}

Ok(quote! {
impl #impl_generics #strum_module_path::EnumMessage for #name #ty_generics #where_clause {
fn get_message(&self) -> ::core::option::Option<&'static str> {
Expand All @@ -93,6 +123,12 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
}

fn get_documentation(&self) -> ::core::option::Option<&'static str> {
match self {
#(#documentation_arms),*
}
}

fn get_serializations(&self) -> &'static [&'static str] {
match self {
#(#serializations),*
Expand Down
24 changes: 24 additions & 0 deletions strum_tests/tests/enum_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ use strum::EnumMessage;

#[derive(Debug, Eq, PartialEq, EnumMessage)]
enum Pets {
// This comment is not collected since it starts with "//" instead of "///".
#[strum(message = "I'm a dog")]
Dog,
/// I eat birds.
///
/// And fish.
#[strum(message = "I'm a cat")]
#[strum(detailed_message = "I'm a very exquisite striped cat")]
Cat,
/// I'm a fish.
#[strum(detailed_message = "My fish is named Charles McFish")]
Fish,
/// I'm a bird.
Bird,
/// This comment is not collected because it is explicitly disabled.
#[strum(disabled)]
Hamster,
}
Expand Down Expand Up @@ -38,6 +45,23 @@ fn only_detailed_message() {
);
}

#[test]
fn documentation() {
assert_eq!("I eat birds.\n\nAnd fish.\n", (Pets::Cat).get_documentation().unwrap());
assert_eq!("I'm a fish.", (Pets::Fish).get_documentation().unwrap());
assert_eq!("I'm a bird.", (Pets::Bird).get_documentation().unwrap());
}

#[test]
fn no_documentation() {
assert_eq!(None, (Pets::Dog).get_documentation());
}

#[test]
fn disabled_documentation() {
assert_eq!(None, (Pets::Hamster).get_documentation());
}

#[test]
fn no_message() {
assert_eq!(None, (Pets::Bird).get_message());
Expand Down