// SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2024 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ExitCodeVariantDoc { pub name: String, pub value: String, pub doc: String, } impl ExitCodeVariantDoc { pub fn new(name: N, value: V, doc: D) -> Self where N: AsRef, V: AsRef, D: AsRef, { Self { name: name.as_ref().to_string(), value: value.as_ref().to_string(), doc: doc.as_ref().to_string(), } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ExitCodeDoc { pub doc: Option, pub variants: Vec, } pub trait ExitCodeTrait { fn exit_code_doc() -> ExitCodeDoc; } pub fn docstring(attr: &str) -> Option { if !attr.starts_with("doc = r\"") { return None; } let mut doc = attr .strip_prefix("doc = r\"") .unwrap() .strip_suffix("\"") .unwrap() .to_string(); if doc.starts_with(" ") { doc = doc.strip_prefix(" ").unwrap().to_string(); } Some(doc) } #[macro_export] macro_rules! impl_exitcodetrait { ($(#[$attr:meta])* $vis:vis enum $name:ident $(<$($gen:ident),*>)? { $( $(#[$variantattr:meta])+ $variant:ident = $tvalue:literal ),* $(,)? } ) => { $(#[$attr])* $vis enum $name $(<$($gen),*>)? { $( $(#[$variantattr])+ $variant = $tvalue ),* } impl ExitCodeTrait for $(<$($gen),*>)? $name $(<$($gen),*>)? { fn exit_code_doc() -> $crate::ExitCodeDoc { let enum_doc_vec = [$(stringify!($attr)),*].into_iter().map($crate::docstring).filter_map(std::convert::identity).collect::>(); let enum_doc = (!enum_doc_vec.is_empty()).then_some(enum_doc_vec.join("\n")); let mut variants = vec![]; $( let name = stringify!($variant).to_string(); let value = stringify!($tvalue).to_string(); let docs: Vec<_> = [$(stringify!($variantattr)),+].into_iter().map($crate::docstring).filter_map(std::convert::identity).collect(); assert!(!docs.is_empty(), "Please add a docstring to enum variant '{name}' of '{}'", stringify!($name)); variants.push($crate::ExitCodeVariantDoc { name, value, doc: docs.join("\n")}); )* $crate::ExitCodeDoc { doc: enum_doc, variants, } } } }; } #[cfg(test)] mod tests { use crate::{exit_code::ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc}; #[test] fn test_impl_exitcodetrait_with_doc() { impl_exitcodetrait!( /// Program exit codes /// /// Multiline. #[repr(u8)] #[allow(unused)] #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub enum OwnExitCode { /// Program finished successfully /// /// Long description. Success = 0, /// Generic error #[default] GenericError = 1, /// Usage error UsageError = 2, // same exit code as used by `Clap` crate } ); assert_eq!( OwnExitCode::exit_code_doc(), ExitCodeDoc { doc: Some("Program exit codes\n\nMultiline.".to_string()), variants: vec![ ExitCodeVariantDoc::new( "Success", "0", "Program finished successfully\n\nLong description." ), ExitCodeVariantDoc::new("GenericError", "1", "Generic error"), ExitCodeVariantDoc::new("UsageError", "2", "Usage error") ] } ); assert_eq!(OwnExitCode::default(), OwnExitCode::GenericError); } #[test] fn test_impl_exitcodetrait_without_doc() { impl_exitcodetrait!( #[repr(u8)] #[allow(unused)] #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub enum OwnExitCode { /// Program finished successfully /// /// Long description. Success = 0, /// Generic error #[default] GenericError = 1, /// Usage error UsageError = 2, // same exit code as used by `Clap` crate } ); assert_eq!( OwnExitCode::exit_code_doc(), ExitCodeDoc { doc: None, variants: vec![ ExitCodeVariantDoc::new( "Success", "0", "Program finished successfully\n\nLong description." ), ExitCodeVariantDoc::new("GenericError", "1", "Generic error"), ExitCodeVariantDoc::new("UsageError", "2", "Usage error") ] } ); assert_eq!(OwnExitCode::default(), OwnExitCode::GenericError); } }