xref: /aosp_15_r20/external/crosvm/argh_helpers/src/lib.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker // Copyright 2022 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker // found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker 
5*bb4ee6a4SAndroid Build Coastguard Worker use std::fmt::Write;
6*bb4ee6a4SAndroid Build Coastguard Worker 
7*bb4ee6a4SAndroid Build Coastguard Worker use quote::quote;
8*bb4ee6a4SAndroid Build Coastguard Worker 
9*bb4ee6a4SAndroid Build Coastguard Worker /// A helper derive proc macro to flatten multiple subcommand enums into one
10*bb4ee6a4SAndroid Build Coastguard Worker /// Note that it is unable to check for duplicate commands and they will be
11*bb4ee6a4SAndroid Build Coastguard Worker /// tried in order of declaration
12*bb4ee6a4SAndroid Build Coastguard Worker #[proc_macro_derive(FlattenSubcommand)]
flatten_subcommand(input: proc_macro::TokenStream) -> proc_macro::TokenStream13*bb4ee6a4SAndroid Build Coastguard Worker pub fn flatten_subcommand(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
14*bb4ee6a4SAndroid Build Coastguard Worker     let ast = syn::parse_macro_input!(input as syn::DeriveInput);
15*bb4ee6a4SAndroid Build Coastguard Worker     let de = match ast.data {
16*bb4ee6a4SAndroid Build Coastguard Worker         syn::Data::Enum(v) => v,
17*bb4ee6a4SAndroid Build Coastguard Worker         _ => unreachable!(),
18*bb4ee6a4SAndroid Build Coastguard Worker     };
19*bb4ee6a4SAndroid Build Coastguard Worker     let name = &ast.ident;
20*bb4ee6a4SAndroid Build Coastguard Worker 
21*bb4ee6a4SAndroid Build Coastguard Worker     // An enum variant like `<name>(<ty>)`
22*bb4ee6a4SAndroid Build Coastguard Worker     struct SubCommandVariant<'a> {
23*bb4ee6a4SAndroid Build Coastguard Worker         name: &'a syn::Ident,
24*bb4ee6a4SAndroid Build Coastguard Worker         ty: &'a syn::Type,
25*bb4ee6a4SAndroid Build Coastguard Worker     }
26*bb4ee6a4SAndroid Build Coastguard Worker 
27*bb4ee6a4SAndroid Build Coastguard Worker     let variants: Vec<SubCommandVariant<'_>> = de
28*bb4ee6a4SAndroid Build Coastguard Worker         .variants
29*bb4ee6a4SAndroid Build Coastguard Worker         .iter()
30*bb4ee6a4SAndroid Build Coastguard Worker         .map(|variant| {
31*bb4ee6a4SAndroid Build Coastguard Worker             let name = &variant.ident;
32*bb4ee6a4SAndroid Build Coastguard Worker             let ty = match &variant.fields {
33*bb4ee6a4SAndroid Build Coastguard Worker                 syn::Fields::Unnamed(field) => {
34*bb4ee6a4SAndroid Build Coastguard Worker                     if field.unnamed.len() != 1 {
35*bb4ee6a4SAndroid Build Coastguard Worker                         unreachable!()
36*bb4ee6a4SAndroid Build Coastguard Worker                     }
37*bb4ee6a4SAndroid Build Coastguard Worker 
38*bb4ee6a4SAndroid Build Coastguard Worker                     &field.unnamed.first().unwrap().ty
39*bb4ee6a4SAndroid Build Coastguard Worker                 }
40*bb4ee6a4SAndroid Build Coastguard Worker                 _ => unreachable!(),
41*bb4ee6a4SAndroid Build Coastguard Worker             };
42*bb4ee6a4SAndroid Build Coastguard Worker             SubCommandVariant { name, ty }
43*bb4ee6a4SAndroid Build Coastguard Worker         })
44*bb4ee6a4SAndroid Build Coastguard Worker         .collect();
45*bb4ee6a4SAndroid Build Coastguard Worker 
46*bb4ee6a4SAndroid Build Coastguard Worker     let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();
47*bb4ee6a4SAndroid Build Coastguard Worker     let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();
48*bb4ee6a4SAndroid Build Coastguard Worker 
49*bb4ee6a4SAndroid Build Coastguard Worker     (quote! {
50*bb4ee6a4SAndroid Build Coastguard Worker         impl argh::FromArgs for #name {
51*bb4ee6a4SAndroid Build Coastguard Worker             fn from_args(command_name: &[&str], args: &[&str])
52*bb4ee6a4SAndroid Build Coastguard Worker                 -> std::result::Result<Self, argh::EarlyExit>
53*bb4ee6a4SAndroid Build Coastguard Worker             {
54*bb4ee6a4SAndroid Build Coastguard Worker                 let subcommand_name = if let Some(subcommand_name) = command_name.last() {
55*bb4ee6a4SAndroid Build Coastguard Worker                     *subcommand_name
56*bb4ee6a4SAndroid Build Coastguard Worker                 } else {
57*bb4ee6a4SAndroid Build Coastguard Worker                     return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
58*bb4ee6a4SAndroid Build Coastguard Worker                 };
59*bb4ee6a4SAndroid Build Coastguard Worker 
60*bb4ee6a4SAndroid Build Coastguard Worker                 #(
61*bb4ee6a4SAndroid Build Coastguard Worker                     if <#variant_ty as argh::SubCommands>::COMMANDS
62*bb4ee6a4SAndroid Build Coastguard Worker                     .iter()
63*bb4ee6a4SAndroid Build Coastguard Worker                     .find(|ci| ci.name.eq(subcommand_name))
64*bb4ee6a4SAndroid Build Coastguard Worker                     .is_some()
65*bb4ee6a4SAndroid Build Coastguard Worker                     {
66*bb4ee6a4SAndroid Build Coastguard Worker                         return <#variant_ty as argh::FromArgs>::from_args(command_name, args)
67*bb4ee6a4SAndroid Build Coastguard Worker                             .map(|v| Self::#variant_names(v));
68*bb4ee6a4SAndroid Build Coastguard Worker                     }
69*bb4ee6a4SAndroid Build Coastguard Worker                 )*
70*bb4ee6a4SAndroid Build Coastguard Worker 
71*bb4ee6a4SAndroid Build Coastguard Worker                 Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
72*bb4ee6a4SAndroid Build Coastguard Worker             }
73*bb4ee6a4SAndroid Build Coastguard Worker 
74*bb4ee6a4SAndroid Build Coastguard Worker             fn redact_arg_values(command_name: &[&str], args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
75*bb4ee6a4SAndroid Build Coastguard Worker                 let subcommand_name = if let Some(subcommand_name) = command_name.last() {
76*bb4ee6a4SAndroid Build Coastguard Worker                     *subcommand_name
77*bb4ee6a4SAndroid Build Coastguard Worker                 } else {
78*bb4ee6a4SAndroid Build Coastguard Worker                     return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
79*bb4ee6a4SAndroid Build Coastguard Worker                 };
80*bb4ee6a4SAndroid Build Coastguard Worker 
81*bb4ee6a4SAndroid Build Coastguard Worker                 #(
82*bb4ee6a4SAndroid Build Coastguard Worker                     if <#variant_ty as argh::SubCommands>::COMMANDS
83*bb4ee6a4SAndroid Build Coastguard Worker                     .iter()
84*bb4ee6a4SAndroid Build Coastguard Worker                     .find(|ci| ci.name.eq(subcommand_name))
85*bb4ee6a4SAndroid Build Coastguard Worker                     .is_some()
86*bb4ee6a4SAndroid Build Coastguard Worker                     {
87*bb4ee6a4SAndroid Build Coastguard Worker                         return <#variant_ty as argh::FromArgs>::redact_arg_values(
88*bb4ee6a4SAndroid Build Coastguard Worker                             command_name,
89*bb4ee6a4SAndroid Build Coastguard Worker                             args,
90*bb4ee6a4SAndroid Build Coastguard Worker                         );
91*bb4ee6a4SAndroid Build Coastguard Worker                     }
92*bb4ee6a4SAndroid Build Coastguard Worker 
93*bb4ee6a4SAndroid Build Coastguard Worker                 )*
94*bb4ee6a4SAndroid Build Coastguard Worker 
95*bb4ee6a4SAndroid Build Coastguard Worker                 Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
96*bb4ee6a4SAndroid Build Coastguard Worker             }
97*bb4ee6a4SAndroid Build Coastguard Worker         }
98*bb4ee6a4SAndroid Build Coastguard Worker 
99*bb4ee6a4SAndroid Build Coastguard Worker         impl argh::SubCommands for #name {
100*bb4ee6a4SAndroid Build Coastguard Worker             const COMMANDS: &'static [&'static argh::CommandInfo] = {
101*bb4ee6a4SAndroid Build Coastguard Worker                 const TOTAL_LEN: usize = #(<#variant_ty as argh::SubCommands>::COMMANDS.len())+*;
102*bb4ee6a4SAndroid Build Coastguard Worker                 const COMMANDS: [&'static argh::CommandInfo; TOTAL_LEN] = {
103*bb4ee6a4SAndroid Build Coastguard Worker                     let slices = &[#(<#variant_ty as argh::SubCommands>::COMMANDS,)*];
104*bb4ee6a4SAndroid Build Coastguard Worker                     // Its not possible for slices[0][0] to be invalid
105*bb4ee6a4SAndroid Build Coastguard Worker                     let mut output = [slices[0][0]; TOTAL_LEN];
106*bb4ee6a4SAndroid Build Coastguard Worker 
107*bb4ee6a4SAndroid Build Coastguard Worker                     let mut output_index = 0;
108*bb4ee6a4SAndroid Build Coastguard Worker                     let mut which_slice = 0;
109*bb4ee6a4SAndroid Build Coastguard Worker                     while which_slice < slices.len() {
110*bb4ee6a4SAndroid Build Coastguard Worker                         let slice = &slices[which_slice];
111*bb4ee6a4SAndroid Build Coastguard Worker                         let mut index_in_slice = 0;
112*bb4ee6a4SAndroid Build Coastguard Worker                         while index_in_slice < slice.len() {
113*bb4ee6a4SAndroid Build Coastguard Worker                             output[output_index] = slice[index_in_slice];
114*bb4ee6a4SAndroid Build Coastguard Worker                             output_index += 1;
115*bb4ee6a4SAndroid Build Coastguard Worker                             index_in_slice += 1;
116*bb4ee6a4SAndroid Build Coastguard Worker                         }
117*bb4ee6a4SAndroid Build Coastguard Worker                         which_slice += 1;
118*bb4ee6a4SAndroid Build Coastguard Worker                     }
119*bb4ee6a4SAndroid Build Coastguard Worker                     output
120*bb4ee6a4SAndroid Build Coastguard Worker                 };
121*bb4ee6a4SAndroid Build Coastguard Worker                 &COMMANDS
122*bb4ee6a4SAndroid Build Coastguard Worker             };
123*bb4ee6a4SAndroid Build Coastguard Worker         }
124*bb4ee6a4SAndroid Build Coastguard Worker     })
125*bb4ee6a4SAndroid Build Coastguard Worker     .into()
126*bb4ee6a4SAndroid Build Coastguard Worker }
127*bb4ee6a4SAndroid Build Coastguard Worker 
128*bb4ee6a4SAndroid Build Coastguard Worker /// A helper proc macro to pad strings so that argh would break them at intended points
129*bb4ee6a4SAndroid Build Coastguard Worker #[proc_macro_attribute]
pad_description_for_argh( _attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream130*bb4ee6a4SAndroid Build Coastguard Worker pub fn pad_description_for_argh(
131*bb4ee6a4SAndroid Build Coastguard Worker     _attr: proc_macro::TokenStream,
132*bb4ee6a4SAndroid Build Coastguard Worker     item: proc_macro::TokenStream,
133*bb4ee6a4SAndroid Build Coastguard Worker ) -> proc_macro::TokenStream {
134*bb4ee6a4SAndroid Build Coastguard Worker     let mut item = syn::parse_macro_input!(item as syn::Item);
135*bb4ee6a4SAndroid Build Coastguard Worker     if let syn::Item::Struct(s) = &mut item {
136*bb4ee6a4SAndroid Build Coastguard Worker         if let syn::Fields::Named(fields) = &mut s.fields {
137*bb4ee6a4SAndroid Build Coastguard Worker             for f in fields.named.iter_mut() {
138*bb4ee6a4SAndroid Build Coastguard Worker                 for a in f.attrs.iter_mut() {
139*bb4ee6a4SAndroid Build Coastguard Worker                     if a.path()
140*bb4ee6a4SAndroid Build Coastguard Worker                         .get_ident()
141*bb4ee6a4SAndroid Build Coastguard Worker                         .map(|i| i.to_string())
142*bb4ee6a4SAndroid Build Coastguard Worker                         .unwrap_or_default()
143*bb4ee6a4SAndroid Build Coastguard Worker                         == *"doc"
144*bb4ee6a4SAndroid Build Coastguard Worker                     {
145*bb4ee6a4SAndroid Build Coastguard Worker                         if let syn::Meta::NameValue(syn::MetaNameValue {
146*bb4ee6a4SAndroid Build Coastguard Worker                             value:
147*bb4ee6a4SAndroid Build Coastguard Worker                                 syn::Expr::Lit(syn::ExprLit {
148*bb4ee6a4SAndroid Build Coastguard Worker                                     lit: syn::Lit::Str(s),
149*bb4ee6a4SAndroid Build Coastguard Worker                                     ..
150*bb4ee6a4SAndroid Build Coastguard Worker                                 }),
151*bb4ee6a4SAndroid Build Coastguard Worker                             ..
152*bb4ee6a4SAndroid Build Coastguard Worker                         }) = &a.meta
153*bb4ee6a4SAndroid Build Coastguard Worker                         {
154*bb4ee6a4SAndroid Build Coastguard Worker                             let doc = s.value().lines().fold(String::new(), |mut output, s| {
155*bb4ee6a4SAndroid Build Coastguard Worker                                 let _ = write!(output, "{: <61}", s);
156*bb4ee6a4SAndroid Build Coastguard Worker                                 output
157*bb4ee6a4SAndroid Build Coastguard Worker                             });
158*bb4ee6a4SAndroid Build Coastguard Worker                             *a = syn::parse_quote! { #[doc= #doc] };
159*bb4ee6a4SAndroid Build Coastguard Worker                         }
160*bb4ee6a4SAndroid Build Coastguard Worker                     }
161*bb4ee6a4SAndroid Build Coastguard Worker                 }
162*bb4ee6a4SAndroid Build Coastguard Worker             }
163*bb4ee6a4SAndroid Build Coastguard Worker         } else {
164*bb4ee6a4SAndroid Build Coastguard Worker             unreachable!()
165*bb4ee6a4SAndroid Build Coastguard Worker         }
166*bb4ee6a4SAndroid Build Coastguard Worker     } else {
167*bb4ee6a4SAndroid Build Coastguard Worker         unreachable!()
168*bb4ee6a4SAndroid Build Coastguard Worker     }
169*bb4ee6a4SAndroid Build Coastguard Worker     quote! {
170*bb4ee6a4SAndroid Build Coastguard Worker         #item
171*bb4ee6a4SAndroid Build Coastguard Worker     }
172*bb4ee6a4SAndroid Build Coastguard Worker     .into()
173*bb4ee6a4SAndroid Build Coastguard Worker }
174