mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
utils: Add utils_macros: Implement ValueEnumDisplay and ValueEnumFromStr macros
Add new derive macros ValueEnumDisplay and ValueEnumFromStr which can be used to derive 'Display' and 'FromStr' for enums implementing 'clap::ValueEnum'. It helps in reducing boilerplate code and keep things in sync. Assisted-by: IBM Bob:1.0.4 Signed-off-by: Marc Hartmayer <marc@linux.ibm.com> Reviewed-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
committed by
Steffen Eiden
parent
173a055372
commit
618e22e38b
11
rust/Cargo.lock
generated
11
rust/Cargo.lock
generated
@@ -1225,6 +1225,17 @@ dependencies = [
|
||||
"s390_pv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utils_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utils_macros"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -11,6 +11,7 @@ members = [
|
||||
"pvsecret",
|
||||
"pvverify",
|
||||
"utils",
|
||||
"utils_macros",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -12,5 +12,7 @@ log = { version = "0.4.29", features = ["std", "release_max_level_debug"] }
|
||||
pv = { path = "../pv", package = "s390_pv" }
|
||||
serde = { version = "1.0.228"}
|
||||
|
||||
utils_macros = { path = "../utils_macros" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.149"
|
||||
|
||||
@@ -13,6 +13,8 @@ mod log;
|
||||
mod tmpfile;
|
||||
|
||||
pub use ::log::LevelFilter;
|
||||
// Re-export procedural macros from utils_macros
|
||||
pub use utils_macros::{ValueEnumDisplay, ValueEnumFromStr};
|
||||
|
||||
pub use crate::cli::{
|
||||
combined_path_opt, combined_path_req, get_reader_from_cli_file_arg,
|
||||
|
||||
17
rust/utils_macros/Cargo.toml
Normal file
17
rust/utils_macros/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "utils_macros"
|
||||
version = "0.12.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.6", features = ["derive"] }
|
||||
158
rust/utils_macros/src/lib.rs
Normal file
158
rust/utils_macros/src/lib.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp.
|
||||
|
||||
//! Procedural macros for the utils crate.
|
||||
//!
|
||||
//! This crate provides derive macros to reduce boilerplate in enum definitions.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
/// Derive `std::fmt::Display` for enums implementing `clap::ValueEnum`.
|
||||
///
|
||||
/// This macro generates a `Display` implementation that delegates to
|
||||
/// `ValueEnum::to_possible_value()`, ensuring that the formatted output
|
||||
/// matches the CLI representation used by clap (e.g. for help text,
|
||||
/// completions, and parsing).
|
||||
///
|
||||
/// # Behavior
|
||||
///
|
||||
/// - Uses the canonical CLI name of each variant (as defined by `#[value(name = "...")]` or the
|
||||
/// default casing).
|
||||
/// - Fails at runtime if a variant is marked with `#[value(skip)]` and therefore has no CLI
|
||||
/// representation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use clap::ValueEnum;
|
||||
/// use utils_macros::ValueEnumDisplay;
|
||||
///
|
||||
/// #[derive(ValueEnum, ValueEnumDisplay, Clone)]
|
||||
/// enum Mode {
|
||||
/// #[value(name = "very-fast")]
|
||||
/// Fast,
|
||||
///
|
||||
/// #[value(name = "slow")]
|
||||
/// Slow,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Mode::Fast.to_string(), "very-fast");
|
||||
/// ```
|
||||
///
|
||||
/// # Rationale
|
||||
///
|
||||
/// clap requires `Display` for features like `default_value_t`. However,
|
||||
/// `ValueEnum` already defines the canonical string representation via
|
||||
/// `to_possible_value()`. This derive avoids duplicating those strings
|
||||
/// and guarantees consistency between parsing, help output, and display.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if called on a variant with `#[value(skip)]`, as such variants
|
||||
/// have no associated CLI representation.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// - [`clap::ValueEnum`]
|
||||
/// - [`clap::builder::PossibleValue`]
|
||||
#[proc_macro_derive(ValueEnumDisplay)]
|
||||
pub fn derive_value_enum_display(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = input.ident;
|
||||
|
||||
let expanded = quote! {
|
||||
impl std::fmt::Display for #name {
|
||||
fn fmt(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
let value = self
|
||||
.to_possible_value()
|
||||
.expect("skipped ValueEnum variant cannot be displayed");
|
||||
|
||||
write!(f, "{}", value.get_name())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
/// Derives a `std::str::FromStr` implementation for enums implementing
|
||||
/// [`clap::ValueEnum`].
|
||||
///
|
||||
/// This macro generates a `FromStr` implementation that delegates to
|
||||
/// [`ValueEnum::from_str`], ensuring that parsing behavior is identical
|
||||
/// to clap's CLI parsing.
|
||||
///
|
||||
/// # Behavior
|
||||
///
|
||||
/// - Parses input strings using the canonical CLI representation defined by `ValueEnum` (including
|
||||
/// `#[value(name = "...")]` and aliases).
|
||||
/// - Supports the same parsing semantics as clap (e.g. case sensitivity, if enabled).
|
||||
/// - Returns a human-readable error if parsing fails.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use clap::ValueEnum;
|
||||
/// use utils_macros::ValueEnumFromStr;
|
||||
///
|
||||
/// #[derive(ValueEnum, ValueEnumFromStr, Clone, Debug, PartialEq)]
|
||||
/// enum Mode {
|
||||
/// #[value(name = "fast")]
|
||||
/// Fast,
|
||||
///
|
||||
/// #[value(name = "slow")]
|
||||
/// Slow,
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!("fast".parse::<Mode>().unwrap(), Mode::Fast);
|
||||
/// assert!("invalid".parse::<Mode>().is_err());
|
||||
/// ```
|
||||
///
|
||||
/// # Rationale
|
||||
///
|
||||
/// clap's [`ValueEnum`] trait already defines the canonical mapping
|
||||
/// between strings and enum variants. This derive avoids duplicating
|
||||
/// that logic in manual `FromStr` implementations and guarantees that
|
||||
/// CLI parsing and programmatic parsing remain consistent.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the input does not match any of the allowed values
|
||||
/// defined by `ValueEnum`.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// - [`clap::ValueEnum`]
|
||||
/// - [`std::str::FromStr`]
|
||||
#[proc_macro_derive(ValueEnumFromStr)]
|
||||
pub fn derive_value_enum_from_str(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = input.ident;
|
||||
|
||||
let expanded = quote! {
|
||||
impl std::str::FromStr for #name {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
<Self as clap::ValueEnum>::from_str(s, false).map_err(|_| {
|
||||
let possible = <Self as clap::ValueEnum>::value_variants()
|
||||
.iter()
|
||||
.filter_map(|v| v.to_possible_value())
|
||||
.map(|v| v.get_name().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
format!("invalid value '{}', expected one of: {}", s, possible)
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
Reference in New Issue
Block a user