From d73f4dc22af963833072ce9af9efee47df065cd1 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Thu, 28 Nov 2024 14:03:26 +0100 Subject: [PATCH] rust/utils: Add 'ExitCodeTrait' and an macro that implements the trait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A manpage auto-generation tool can use this trait to get the exit codes and their documentation of a program. Reviewed-by: Steffen Eiden Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner --- rust/utils/src/exit_code.rs | 171 ++++++++++++++++++++++++++++++++++++ rust/utils/src/lib.rs | 2 + 2 files changed, 173 insertions(+) create mode 100644 rust/utils/src/exit_code.rs diff --git a/rust/utils/src/exit_code.rs b/rust/utils/src/exit_code.rs new file mode 100644 index 00000000..d3cac9b5 --- /dev/null +++ b/rust/utils/src/exit_code.rs @@ -0,0 +1,171 @@ +// 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); + } +} diff --git a/rust/utils/src/lib.rs b/rust/utils/src/lib.rs index ab3b6f02..c14b5183 100644 --- a/rust/utils/src/lib.rs +++ b/rust/utils/src/lib.rs @@ -4,6 +4,7 @@ //! //! Copyright IBM Corp. 2023, 2024 mod cli; +mod exit_code; mod file; mod hexslice; mod log; @@ -16,6 +17,7 @@ pub use crate::{ get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, print_cli_error, print_error, CertificateOptions, DeprecatedVerbosityOptions, VerbosityOptions, STDIN, STDOUT, }, + exit_code::{docstring, ExitCodeDoc, ExitCodeTrait, ExitCodeVariantDoc}, file::{AtomicFile, AtomicFileOperation}, hexslice::HexSlice, log::PvLogger,