Skip to main content

gflags_derive/
lib.rs

1//! Derive command line arguments from `struct` fields using
2//! [`gflags`][gflags].
3//!
4//! This is an alternative to the "Defining flags" section of the
5//! [`gflags`][gflags] manual.
6//!
7//! [gflags]: https://docs.rs/gflags
8//!
9//! # Defining flags
10//!
11//! Create a struct to contain the configuration data for your library or
12//! binary.
13//!
14//! For example, this hypothetical logging library that defines two
15//! configuration options.
16//!
17//! ```ignore
18//! struct Config {
19//!     /// True if log messages should also be sent to STDERR
20//!     to_stderr: bool,
21//!
22//!     /// The directory to write log files to
23//!     dir: String,
24//! }
25//! ```
26//!
27//! Flags are added to the registry by deriving `gflags_derive::Gflags` on the
28//! struct.
29//!
30//! ```ignore
31//! use gflags_derive::GFlags;
32//!
33//! #[derive(GFlags)]
34//! struct Config {
35//!     /// True if log messages should also be sent to STDERR
36//!     to_stderr: bool,
37//!
38//!     /// The directory to write log files to
39//!     dir: String,
40//! }
41//! ```
42//!
43//! You now have two new flags, as if you had written:
44//!
45//! ```ignore
46//! gflags::define! {
47//!     /// True if log messages should also be sent to STDERR
48//!     --to_stderr: bool
49//! }
50//!
51//! gflags::define! {
52//!     /// The directory to write log files to
53//!     --dir: &str
54//! }
55//! ```
56//!
57//! Note that:
58//!
59//! - The comment on each struct field is also the documentation comment for
60//!   the flag, which becomes its help text.
61//! - The type for the `--dir` flag has been converted from `String` to `&str`.
62//!
63//! # Defining a flag prefix
64//!
65//! You might want all the flag names to have the same prefix, without needing
66//! to use that prefix on the field names. For example, a logging module might
67//! want all the flags to start `log-` or `log_`.
68//!
69//! To support this, use the `#[gflags(prefix = "...")]` attribute on the
70//! struct.
71//!
72//! ```ignore
73//! use gflags_derive::GFlags;
74//!
75//! #[derive(GFlags)]
76//! #[gflags(prefix = "log_")]
77//! struct Config {
78//!     /// True if log messages should also be sent to STDERR
79//!     to_stderr: bool,
80//!
81//!     /// The directory to write log files to
82//!     dir: String,
83//! }
84//! ```
85//!
86//! The flag definitions now include the prefix, as if you had written:
87//!
88//! ```ignore
89//! gflags::define! {
90//!     /// True if log messages should also be sent to STDERR
91//!     --log_to_stderr: bool
92//! }
93//!
94//! gflags::define! {
95//!     /// The directory to write log files to
96//!     --log_dir: &str
97//! }
98//! ```
99//!
100//! If the flag prefix ends with `-` then the macro converts the flag names to
101//! kebab-case instead of snake_case. So writing:
102//!
103//! ```ignore
104//! use gflags_derive::GFlags;
105//!
106//! #[derive(GFlags)]
107//! #[gflags(prefix = "log-")]
108//! struct Config {
109//!     /// True if log messages should also be sent to STDERR
110//!     to_stderr: bool,
111//!
112//!     /// The directory to write log files to
113//!     dir: String,
114//! }
115//! ```
116//!
117//! generates the following flags:
118//!
119//! ```ignore
120//! gflags::define! {
121//!     /// True if log messages should also be sent to STDERR
122//!     --log-to-stderr: bool
123//! }
124//!
125//! gflags::define! {
126//!     /// The directory to write log files to
127//!     --log-dir: &str
128//! }
129//! ```
130//!
131//! # Handling `Option<T>`
132//!
133//! Your configuration `struct` may have fields that have `Option<T>` types.
134//! For these fields `gflags_derive` creates a flag of the inner type `T`.
135//!
136//! # Customising the default value
137//!
138//! To specify a default value for the flag add a `#[gflags(default = ...)]`
139//! attribute to the field.
140//!
141//! The value for the attribute is the literal value, not a quoted value.
142//! Only quote the value if the type of the field is a string or can be
143//! created from a string.
144//!
145//! For example, to set the default value of the `--log-to-stderr` flag to
146//! `true`:
147//!
148//! ```ignore
149//! use gflags_derive::GFlags;
150//!
151//! #[derive(GFlags)]
152//! #[gflags(prefix = "log-")]
153//! struct Config {
154//!     /// True if log messages should also be sent to STDERR
155//!     #[gflags(default = true)]
156//!     to_stderr: bool,
157//!
158//!     /// The directory to write log files to
159//!     dir: String,
160//! }
161//! ```
162//!
163//! Specifying this with quotes, `#[gflags(default = "true")]` will give a
164//! compile time error:
165//!
166//! ```text
167//! expected `bool`, found `&str`
168//! ```
169//!
170//! > **Important**: This does *not* change the default value when an instance
171//! of the `Config` struct is created. It only changes the default value of
172//! the `LOG_TO_STDERR.flag` variable.
173//!
174//! # Customising the type
175//!
176//! To use a different type for the field and the command line flag add a
177//! `#[gflags(type = "...")]` attribute to the field.  For example, to store
178//! the log directory as a `PathBuf` but accept a string on the command line:
179//!
180//! ```ignore
181//! use gflags_derive::GFlags;
182//! use std::path::PathBuf;
183//!
184//! #[derive(GFlags)]
185//! #[gflags(prefix = "log-")]
186//! struct Config {
187//!     /// True if log messages should also be sent to STDERR
188//!     to_stderr: bool,
189//!
190//!     /// The directory to write log files to
191//!     #[gflags(type = "&str")]
192//!     dir: PathBuf,
193//! }
194//! ```
195//!
196//! # Customising the visibility
197//!
198//! To use a different visibility for the flags add a
199//! `#[gflags(visibility = "...")]` attribute to the field and give a Rust
200//! visibility specifier.
201//!
202//! In this example the `LOG_DIR` flag variable will be visible in the parent
203//! module.
204//!
205//! ```ignore
206//! use gflags_derive::GFlags;
207//! use std::path::PathBuf;
208//!
209//! #[derive(GFlags)]
210//! #[gflags(prefix = "log-")]
211//! struct Config {
212//!     /// True if log messages should also be sent to STDERR
213//!     to_stderr: bool,
214//!
215//!     /// The directory to write log files to
216//!     #[gflags(visibility = "pub(super)")]
217//!     #[gflags(type = "&str")]
218//!     dir: PathBuf,
219//! }
220//! ```
221//!
222//! # Specifying a placeholder
223//!
224//! To give a placeholder that will appear in the flag's `help` output add a
225//! `#[gflags(placeholder = "...")]` attribute to the field. This will be
226//! wrapped in `<...>` for display.
227//!
228//! ```ignore
229//! use gflags_derive::GFlags;
230//! use std::path::PathBuf;
231//!
232//! #[derive(GFlags)]
233//! #[gflags(prefix = "log-")]
234//! struct Config {
235//!     /// True if log messages should also be sent to STDERR
236//!     to_stderr: bool,
237//!
238//!     /// The directory to write log files to
239//!     #[gflags(placeholder = "DIR")]
240//!     #[gflags(type = "&str")]
241//!     dir: PathBuf,
242//! }
243//! ```
244//!
245//! In the help output the `--log-dir` flag will appear as:
246//!
247//! ```text
248//! --log-dir <DIR>
249//!         The directory to write log files to
250//! ```
251//!
252//! # Skipping flags
253//!
254//! To skip flag generation for a field add a `#[gflags(skip)]` attribute to
255//! the field.
256//!
257//! ```ignore
258//! use gflags_derive::GFlags;
259//! use std::path::PathBuf;
260//!
261//! #[derive(GFlags)]
262//! #[gflags(prefix = "log-")]
263//! struct Config {
264//!     /// True if log messages should also be sent to STDERR
265//!     to_stderr: bool,
266//!
267//!     /// The directory to write log files to
268//!     #[gflags(skip)]
269//!     dir: PathBuf,
270//! }
271//! ```
272//!
273//! No `--log-dir` flag will be generated.
274//!
275//! # Providing multiple attributes
276//!
277//! If you want to provide multiple attributes on a field then you can mix
278//! and match specifing multiple options in a single `#[gflags(...)]` attribute
279//! and specifying multiple `#[gflags(...)]` attributes. The following examples
280//! are identical.
281//!
282//! ```ignore
283//! ...
284//!     /// The directory to write log files to
285//!     #[gflags(type = "&str", visibility = "pub(super)")]
286//!     dir: PathBuf,
287//! ...
288//! ```
289//!
290//! ```ignore
291//! ...
292//!     /// The directory to write log files to
293//!     #[gflags(type = "&str")]
294//!     #[gflags(visibility = "pub(super)")]
295//!     dir: PathBuf,
296//! ...
297//! ```
298//!
299//! # Deserializing and merging flags
300//!
301//! This supports a powerful pattern for configuring an application that is
302//! composed of multiple crates, where each crate exports a configuration and
303//! supports multiple flags, and the application crate defines a configuration
304//! that imports the configuration structs from the component crates.
305//!
306//! This master configuration can be deserialized from e.g. a JSON file, and
307//! then each component crate can have the opportunity to override the loaded
308//! configuration with information from the command line flags that are specific
309//! to that crate.
310//!
311//! See the `examples/json` directory for a complete application that does
312//! this.
313//!
314//! # Use with `prost`
315//!
316//! This macro can be used to derive flags for `structs` generated from
317//! Protobuffer schemas using `prost` and `prost-build`.
318//!
319//! Given this `.proto` file
320//!
321//! ```proto
322//! syntax = "proto3"
323//!
324//! package log.config.v1;
325//!
326//! message Config {
327//!     // True if log messages should also be sent to STDERR
328//!     bool to_stderr = 1;
329//!
330//!     // The directory to write log files to
331//!     string dir = 2;
332//! }
333//! ```
334//!
335//! This `build.rs` file will add the relevant attributes to add the `log-`
336//! prefix and skip the `dir` field.
337//!
338//! ```ignore
339//! fn main() {
340//!     let mut config = prost_build::Config::new();
341//!
342//!     config.type_attribute(".log.config.v1.Config", "#[derive(gflags_derive::GFlags)]");
343//!     config.type_attribute(".log.config.v1.Config", "#[gflags(prefix=\"log-\")]");
344//!
345//!     config.field_attribute(".log.config.v1.Config.dir", "#[gflags(skip)]");
346//!
347//!     config
348//!         .compile_protos(&["proto/log/config/v1/config.proto"], &["proto"])
349//!         .unwrap();
350//! }
351//! ```
352//!
353//! See the `examples/protobuf` directory for a complete application that
354//! does this.
355
356#![doc(html_root_url = "https://docs.rs/gflags-derive/0.1.0")]
357
358extern crate proc_macro;
359
360use crate::FlagCase::{KebabCase, SnakeCase};
361use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree};
362use proc_macro_error::{abort, abort_call_site, proc_macro_error};
363use quote::{format_ident, quote};
364use std::collections::HashSet;
365use syn::{
366    punctuated::Punctuated, Attribute, Data, DataStruct, Field, Fields, FieldsNamed,
367    GenericArgument, Lit, Meta, NestedMeta, Path, PathArguments, PathSegment, Token, Type,
368};
369
370#[derive(Debug, PartialEq)]
371enum FlagCase {
372    SnakeCase,
373    KebabCase,
374}
375
376#[derive(Debug)]
377struct Config {
378    /// Prefix to apply to flag names
379    prefix: String,
380
381    flag_case: FlagCase,
382}
383
384impl Default for Config {
385    fn default() -> Self {
386        Config {
387            prefix: "".to_string(),
388            flag_case: KebabCase,
389        }
390    }
391}
392
393fn impl_gflags_macro(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
394    let fields: Vec<&Field> = match &ast.data {
395        Data::Struct(DataStruct {
396            fields: Fields::Named(FieldsNamed { named: fields, .. }),
397            ..
398        }) => fields.into_iter().collect(),
399        _ => abort_call_site!("expected a struct with named fields"),
400    };
401
402    let config = config_from_attributes(&ast.attrs);
403
404    let mut flags: Vec<TokenStream> = vec![];
405
406    for field in fields {
407        let flag = flag_from_field(&config, field);
408        flags.push(flag);
409    }
410
411    let gen = quote! {
412        #(#flags)*
413    };
414
415    gen.into()
416}
417
418/// Represents a `#[gflags(...)]` attribute on a struct or field.
419#[derive(Debug, Default)]
420struct GFlagsAttribute {
421    /// True if this field should be skipped (do not generate a flag for it)
422    skip: bool,
423
424    /// Prefix to apply to this flag (or global)
425    prefix: Option<String>,
426
427    /// Casing for this flag
428    flag_case: Option<FlagCase>,
429
430    /// Tokens that define the type to use for this flag
431    ty: Option<TokenStream>,
432
433    /// Visibility for the flag
434    visibility: Option<TokenStream>,
435
436    /// Placeholder to display in the help
437    placeholder: Option<TokenStream>,
438
439    /// Default value if the flag is not set
440    default: Option<TokenStream>,
441}
442
443impl From<Meta> for GFlagsAttribute {
444    fn from(meta: Meta) -> Self {
445        let meta = match meta {
446            Meta::List(meta) => meta,
447            _ => abort!(meta, "`#[gflags(...)]` expects a parameter list"),
448        };
449
450        if meta.nested.is_empty() {
451            abort!(meta, "`#[gflags(...)]` expects a non-empty parameter list");
452        }
453
454        let mut config = GFlagsAttribute::default();
455
456        let keywords: HashSet<&'static str> = [
457            "default",
458            "placeholder",
459            "prefix",
460            "skip",
461            "type",
462            "visibility",
463        ]
464        .iter()
465        .cloned()
466        .collect();
467
468        for kv in meta.nested {
469            let kv = match kv {
470                NestedMeta::Meta(Meta::Path(path)) => {
471                    let keyword = path.get_ident().expect("No ident found");
472                    if !keywords.contains(&keyword.to_string().as_ref()) {
473                        abort!(path, "Invalid keyword `{}`", keyword);
474                    }
475
476                    if path.is_ident("skip") {
477                        config.skip = true;
478                        break;
479                    }
480
481                    abort!(path, "Keyword `{}` requires a value", keyword);
482                }
483                NestedMeta::Meta(Meta::NameValue(kv)) => kv,
484                _ => abort!(kv, "`#[gflags(...)]` expects key=value pairs"),
485            };
486
487            if kv.path.is_ident("default") {
488                let lit = kv.lit;
489                config.default = Some(quote! { = #lit });
490                continue;
491            }
492
493            if kv.path.is_ident("placeholder") {
494                config.placeholder = match kv.lit {
495                    Lit::Str(lit) => {
496                        if lit.value().is_empty() {
497                            abort!(
498                                lit,
499                                "`#[gflags(placeholder=...)]` expects a non-empty quoted string"
500                            )
501                        }
502                        let tokens = lit.parse::<TokenStream>().unwrap();
503                        Some(quote! { < #tokens > })
504                    }
505                    _ => abort!(
506                        kv.lit,
507                        "`#[gflags(placeholder=...)]` expects a quoted string"
508                    ),
509                };
510                continue;
511            }
512
513            if kv.path.is_ident("prefix") {
514                let mut prefix = match kv.lit {
515                    Lit::Str(lit) => {
516                        if lit.value().is_empty() {
517                            abort!(
518                                lit,
519                                "`#[gflags(prefix=...)]` expects a non-empty quoted string"
520                            );
521                        }
522
523                        lit.value()
524                    }
525                    _ => abort!(kv.lit, "`#[gflags(prefix=...)]` expects a quoted string"),
526                };
527
528                if prefix.ends_with('_') {
529                    config.flag_case = Some(SnakeCase);
530                    prefix.pop();
531                }
532
533                if prefix.ends_with('-') {
534                    config.flag_case = Some(KebabCase);
535                    prefix.pop();
536                }
537
538                config.prefix = Some(prefix);
539                continue;
540            }
541
542            if kv.path.is_ident("skip") {
543                abort!(kv.lit, "`#[gflags(skip)]` does not take a value");
544            }
545
546            if kv.path.is_ident("type") {
547                config.ty = match kv.lit {
548                    Lit::Str(lit) => {
549                        if lit.value().is_empty() {
550                            abort!(
551                                lit,
552                                "`#[gflags(type=...)]` expects a non-empty quoted string"
553                            );
554                        }
555
556                        Some(lit.parse().unwrap())
557                    }
558                    _ => abort!(kv.lit, "`#[gflags(type=...)]` expects a quoted string"),
559                };
560
561                continue;
562            }
563
564            if kv.path.is_ident("visibility") {
565                config.visibility = match kv.lit {
566                    Lit::Str(lit) => {
567                        if lit.value().is_empty() {
568                            abort!(
569                                lit,
570                                "`#[gflags(visibility=...)]` expects a non-empty quoted string"
571                            )
572                        }
573                        Some(lit.parse().unwrap())
574                    }
575                    _ => abort!(
576                        kv.lit,
577                        "`#[gflags(visibility=...)]` expects a quoted string"
578                    ),
579                };
580                continue;
581            }
582
583            abort!(
584                kv.path,
585                "Invalid keyword `{}`",
586                kv.path.get_ident().unwrap()
587            );
588        }
589
590        config
591    }
592}
593
594impl From<&[Attribute]> for GFlagsAttribute {
595    fn from(attrs: &[Attribute]) -> Self {
596        let mut config: Self = Default::default();
597        for attr in attrs {
598            match attr.parse_meta() {
599                Ok(meta) => {
600                    if !meta.path().is_ident("gflags") {
601                        continue;
602                    }
603                    let parsed_config = GFlagsAttribute::from(meta);
604
605                    // Any results in the parsed config overwrite any existing values.
606                    // This allows multiple #[gflags(...)] attributes to exist on
607                    // a single field
608                    if parsed_config.skip {
609                        config.skip = true
610                    };
611
612                    if parsed_config.default.is_some() {
613                        config.default = parsed_config.default;
614                    }
615
616                    if parsed_config.placeholder.is_some() {
617                        config.placeholder = parsed_config.placeholder;
618                    }
619
620                    if parsed_config.prefix.is_some() {
621                        config.prefix = parsed_config.prefix;
622                    }
623
624                    if parsed_config.flag_case.is_some() {
625                        config.flag_case = parsed_config.flag_case;
626                    }
627
628                    if parsed_config.ty.is_some() {
629                        config.ty = parsed_config.ty;
630                    }
631
632                    if parsed_config.visibility.is_some() {
633                        config.visibility = parsed_config.visibility;
634                    }
635                }
636                Err(e) => abort!(attr, e),
637            }
638        }
639
640        config
641    }
642}
643
644/// Generate a configuration based on `#[gflags(...)` attribute values
645fn config_from_attributes(attrs: &[Attribute]) -> Config {
646    let mut config: Config = Default::default();
647
648    let gfa = GFlagsAttribute::from(attrs);
649
650    if gfa.prefix.is_some() {
651        config.prefix = gfa.prefix.unwrap();
652    }
653
654    if gfa.flag_case.is_some() {
655        config.flag_case = gfa.flag_case.unwrap();
656    }
657
658    config
659}
660
661fn flag_from_field(config: &Config, field: &Field) -> TokenStream {
662    let gfa = GFlagsAttribute::from(field.attrs.as_ref());
663    if gfa.skip {
664        return TokenStream::new();
665    }
666
667    // Figure out the flag name
668    let flag_name = if config.flag_case == SnakeCase {
669        let ident = if !config.prefix.is_empty() {
670            format_ident!(
671                "{}_{}",
672                config.prefix,
673                field
674                    .ident
675                    .as_ref()
676                    .expect("Unwrapping field.ident (prefix) failed")
677            )
678        } else {
679            field
680                .ident
681                .as_ref()
682                .expect("Unwrapping field.ident (no-prefix) failed")
683                .clone()
684        };
685        quote! {--#ident}
686    } else {
687        let span = Span::call_site();
688        let mut segments: Punctuated<Ident, Token![-]> = Punctuated::new();
689        if !config.prefix.is_empty() {
690            segments.push(Ident::new(&config.prefix, span));
691        }
692
693        let field = field.ident.as_ref().unwrap().to_string();
694        for part in field.split('_') {
695            segments.push(Ident::new(part, span));
696        }
697        quote! {--#segments}
698    };
699
700    // Figure out the default value
701    let default = match gfa.default {
702        Some(default) => default,
703        _ => TokenStream::new(),
704    };
705
706    // Figure out the placeholder
707    let placeholder = match gfa.placeholder {
708        Some(placeholder) => placeholder,
709        _ => TokenStream::new(),
710    };
711
712    // Figure out the visibility
713    let visibility = match gfa.visibility {
714        Some(visibility) => visibility,
715        _ => TokenStream::new(),
716    };
717
718    // Figure out the type
719    let ty = match gfa.ty {
720        Some(ty) => ty,
721        _ => match &field.ty {
722            Type::Path(ty) => {
723                let mut last = ty.path.segments.last().unwrap();
724                let mut ident = &last.ident;
725
726                let mut final_type = ty.clone();
727
728                // Replace `Option<T>` with `T` before proceeding
729                if *ident == "Option" {
730                    let option_type = syn::Type::from(final_type);
731
732                    let new_ty = extract_type_from_option(&option_type);
733                    match new_ty {
734                        Some(Type::Path(new_ty)) => {
735                            final_type = new_ty.clone();
736                            last = final_type.path.segments.last().unwrap();
737                            ident = &last.ident;
738                        }
739                        _ => abort!(&field.ty, "Unexpected type"),
740                    }
741                }
742
743                if *ident == "String" {
744                    quote! { &str }
745                } else {
746                    quote! { #final_type }
747                }
748            }
749            _ => abort!(&field.ty, "Unexpected type"),
750        },
751    };
752
753    // Figure out the doc string, if there is one
754    let mut docs: Vec<Literal> = vec![];
755
756    for attr in &field.attrs {
757        if !attr.path.is_ident("doc") {
758            continue;
759        }
760        let tokens = attr.tokens.clone();
761        for token in tokens {
762            if let TokenTree::Literal(l) = token {
763                docs.push(l);
764            }
765        }
766    }
767
768    // Construct the macro call
769    let gen = quote! {
770        gflags::define! {
771            #( #[doc = #docs])*
772            #visibility #flag_name #placeholder: #ty #default
773        }
774    };
775
776    gen
777}
778
779/// Given a `syn::Type` that is an `Option<T>`, return the `syn::Type` for the
780/// `T`, or `None` if it's not a `syn::Type::Path`.
781///
782/// https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn
783fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
784    fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
785        match *ty {
786            syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
787            _ => None,
788        }
789    }
790
791    fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
792        let idents_of_path = path.segments.iter().fold(String::new(), |mut acc, v| {
793            acc.push_str(&v.ident.to_string());
794            acc.push('|');
795            acc
796        });
797        vec!["Option|", "std|option|Option|", "core|option|Option|"]
798            .into_iter()
799            .find(|s| idents_of_path == *s)
800            .and_then(|_| path.segments.last())
801    }
802
803    extract_type_path(ty)
804        .and_then(|path| extract_option_segment(path))
805        .and_then(|pair_path_segment| {
806            let type_params = &pair_path_segment.arguments;
807            // It should have only one angle-bracketed param ("<String>"):
808            match *type_params {
809                PathArguments::AngleBracketed(ref params) => params.args.first(),
810                _ => None,
811            }
812        })
813        .and_then(|generic_arg| match *generic_arg {
814            GenericArgument::Type(ref ty) => Some(ty),
815            _ => None,
816        })
817}
818
819/// # Struct level attributes
820///
821/// `#[gflags(prefix = "...")]` -- apply this prefix to flag names
822///
823/// # Field level attributes
824///
825/// `#[gflags(default = ...)]` -- default value for this flag
826///
827/// `#[gflags(placeholder= "...")]` -- placeholder to display in help
828///
829/// `#[gflags(skip)]` -- do not generate a flag for this field
830///
831/// `#[gflags(type = "...")]` -- generate a flag with this type
832///
833/// `#[gflags(visibility = "...")]` -- generate a flag with this visibility
834///
835/// Refer to the [crate level documentation](index.html) for a complete example.
836#[proc_macro_derive(GFlags, attributes(gflags))]
837#[proc_macro_error]
838pub fn gflags_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
839    let ast = syn::parse(input).unwrap();
840    impl_gflags_macro(&ast)
841}