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