rust/utils: Add 'ExitCodeTrait' and an macro that implements the trait

A manpage auto-generation tool can use this trait to get the exit codes
and their documentation of a program.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Marc Hartmayer
2024-11-28 14:03:26 +01:00
committed by Jan Höppner
parent ea297ecf84
commit d73f4dc22a
2 changed files with 173 additions and 0 deletions

171
rust/utils/src/exit_code.rs Normal file
View File

@@ -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<N, V, D>(name: N, value: V, doc: D) -> Self
where
N: AsRef<str>,
V: AsRef<str>,
D: AsRef<str>,
{
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<String>,
pub variants: Vec<ExitCodeVariantDoc>,
}
pub trait ExitCodeTrait {
fn exit_code_doc() -> ExitCodeDoc;
}
pub fn docstring(attr: &str) -> Option<String> {
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::<Vec<_>>();
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);
}
}

View File

@@ -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,