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 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions strum/src/additional_attributes.rs
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
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
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" }
Peternator7 marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 12 additions & 2 deletions strum_macros/src/helpers/metadata.rs
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,13 @@ 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| {
Peternator7 marked this conversation as resolved.
Show resolved Hide resolved
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
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
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
21 changes: 20 additions & 1 deletion strum_macros/src/macros/enum_messages.rs
Expand Up @@ -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,17 @@ 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();
// Push the documentation.
documentation_arms
.push(quote! { &#name::#ident #params => ::core::option::Option::Some(concat!(#( #documentation ),*).trim()) });
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on concatenating vs using newlines? Obviously this won't be a full markdown parser, but I'm wondering if just concatenating lines is too simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this friendlier to markup, I remove the leading space from each line, and only if there are multiple lines, I suffix each one with a new line when I concatenate them. This way a single-line comment becomes a simple string (w/o a final newline) but a multi-line comment preserves the indentation structure and the final newline in each line.

}
}

if arms.len() < variants.len() {
Expand All @@ -79,6 +88,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 +106,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
Expand Up @@ -2,14 +2,21 @@ use strum::EnumMessage;

#[derive(Debug, Eq, PartialEq, EnumMessage)]
enum Pets {
// I'm a silent dog.
Peternator7 marked this conversation as resolved.
Show resolved Hide resolved
#[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,
/// I'm disabled.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May as well make the comment overly explicit since it's just a test case.

/// get_documentation() will return None because #[strum(disabled)] is present

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

#[strum(disabled)]
Hamster,
}
Expand Down Expand Up @@ -38,6 +45,23 @@ fn only_detailed_message() {
);
}

#[test]
fn documentation() {
assert_eq!("I eat birds. And fish.", (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