1use proc_macro2::{Span, TokenStream};
2use quote::{quote, ToTokens};
3use syn::{
4 braced, parenthesized,
5 parse::{Parse, ParseStream},
6 parse_quote,
7 punctuated::Punctuated,
8 spanned::Spanned,
9 token,
10 visit_mut::VisitMut,
11 Attribute, Error, Expr, Ident, ItemFn, LitChar, LitStr, Result, Visibility,
12};
13
14use crate::{
15 attrs::{parse_bpaf_doc_attrs, EnumPrefix, PostDecor, StrictName},
16 custom_path::CratePathReplacer,
17 field::StructField,
18 help::Help,
19 td::{CommandCfg, EAttr, Ed, Mode, OptionsCfg, ParserCfg, TopInfo},
20 utils::{to_kebab_case, to_snake_case, LineIter},
21};
22
23#[derive(Debug)]
24pub(crate) struct Top {
26 ty: Ident,
29
30 vis: Visibility,
32
33 generate: Ident,
36
37 body: Body,
39 mode: Mode,
40 boxed: bool,
41 adjacent: bool,
42 attrs: Vec<PostDecor>,
43 bpaf_path: Option<syn::Path>,
44}
45
46fn ident_to_long(ident: &Ident) -> LitStr {
47 LitStr::new(&to_kebab_case(&ident.to_string()), ident.span())
48}
49
50fn ident_to_short(ident: &Ident) -> LitChar {
51 LitChar::new(
52 to_kebab_case(&ident.to_string()).chars().next().unwrap(),
53 ident.span(),
54 )
55}
56
57impl Parse for Top {
58 fn parse(input: ParseStream) -> Result<Self> {
59 let attrs = input.call(Attribute::parse_outer)?;
60 let (top_decor, mut help) = parse_bpaf_doc_attrs::<TopInfo>(&attrs)?;
61 let TopInfo {
62 private,
63 custom_name,
64 boxed,
65 mut mode,
66 attrs,
67 ignore_rustdoc,
68 adjacent,
69 bpaf_path,
70 } = top_decor.unwrap_or_default();
71
72 if ignore_rustdoc {
73 help = None;
74 }
75 let vis = input.parse::<Visibility>()?;
76
77 let mut body = Body::parse(input)?;
78 let ty = body.ty();
79
80 if let Mode::Command { command, .. } = &mut mode {
81 if let Some(name) = &command.name {
82 body.set_named_command(name.span())?;
83 } else {
84 body.set_unnamed_command()?;
85 command.name = Some(ident_to_long(&ty));
86 }
87 }
88
89 if let Some(help) = help.take() {
90 match &mut mode {
91 Mode::Command {
92 command: _,
93 options,
94 } => {
95 split_options_help(help, options);
96 }
97 Mode::Options { options } => {
98 split_options_help(help, options);
99 }
100 Mode::Parser { parser } => {
101 if parser.group_help.is_none() {
102 parser.group_help = Some(help);
103 }
104 }
105 }
106 }
107
108 Ok(Top {
109 vis: if private { Visibility::Inherited } else { vis },
110 mode,
111
112 generate: custom_name
113 .unwrap_or_else(|| Ident::new(&to_snake_case(&ty.to_string()), ty.span())),
114 ty,
115 attrs,
116 body,
117 boxed,
118 adjacent,
119 bpaf_path,
120 })
121 }
122}
123
124fn split_options_help(h: Help, opts: &mut OptionsCfg) {
125 match &h {
126 Help::Custom(_) => {
127 if opts.descr.is_none() {
128 opts.descr = Some(h);
129 }
130 }
131 Help::Doc(c) => {
132 let mut chunks = LineIter::from(c.as_str());
133 if let Some(s) = chunks.next() {
134 if opts.descr.is_none() {
135 opts.descr = Some(Help::Doc(s));
136 }
137 }
138 if let Some(s) = chunks.next() {
139 if !s.is_empty() && opts.header.is_none() {
140 opts.header = Some(Help::Doc(s));
141 }
142 }
143 if let Some(s) = chunks.rest() {
144 if opts.footer.is_none() {
145 opts.footer = Some(Help::Doc(s));
146 }
147 }
148 }
149 }
150}
151
152fn split_ehelp_into(h: Help, opts_at: usize, attrs: &mut Vec<EAttr>) {
153 match &h {
154 Help::Custom(_) => attrs.push(EAttr::Descr(h)),
155 Help::Doc(c) => {
156 let mut chunks = LineIter::from(c.as_str());
157 if let Some(s) = chunks.next() {
158 attrs.insert(opts_at, EAttr::Descr(Help::Doc(s)));
159 }
160 if let Some(s) = chunks.next() {
161 if !s.is_empty() {
162 attrs.insert(opts_at, EAttr::Header(Help::Doc(s)));
163 }
164 }
165 if let Some(s) = chunks.rest() {
166 attrs.insert(opts_at, EAttr::Footer(Help::Doc(s)));
167 }
168 }
169 }
170}
171
172impl ToTokens for Top {
173 fn to_tokens(&self, tokens: &mut TokenStream) {
174 let Top {
175 ty,
176 vis,
177 generate,
178 body,
179 mode,
180 attrs,
181 boxed,
182 adjacent,
183 bpaf_path,
184 } = self;
185 let boxed = if *boxed { quote!(.boxed()) } else { quote!() };
186 let adjacent = if *adjacent {
187 quote!(.adjacent())
188 } else {
189 quote!()
190 };
191
192 let original = match mode {
193 Mode::Command { command, options } => {
194 let OptionsCfg {
195 cargo_helper: _,
196 usage,
197 version,
198 descr,
199 footer,
200 header,
201 max_width,
202 fallback_usage,
203 } = options;
204
205 let version = version.as_ref().map(|v| quote!(.version(#v)));
206 let usage = usage.as_ref().map(|v| quote!(.usage(#v)));
207 let descr = descr.as_ref().map(|v| quote!(.descr(#v)));
208 let footer = footer.as_ref().map(|v| quote!(.footer(#v)));
209 let header = header.as_ref().map(|v| quote!(.header(#v)));
210 let max_width = max_width.as_ref().map(|v| quote!(.max_width(#v)));
211 let fallback_usage = if *fallback_usage {
212 Some(quote!(.fallback_to_usage()))
213 } else {
214 None
215 };
216 let CommandCfg {
217 name,
218 long,
219 short,
220 help,
221 } = command;
222 if let Some(name) = name.as_ref() {
223 let long = long.iter().map(|v| quote!(.long(#v)));
224 let short = short.iter().map(|v| quote!(.short(#v)));
225 let help = help.as_ref().map(|v| quote!(.help(#v)));
226 quote! {
227 #[doc(hidden)]
228 #vis fn #generate() -> impl ::bpaf::Parser<#ty> {
229
230 #[allow(unused_imports)]
231 use ::bpaf::Parser;
232 #body
233 #(.#attrs)*
234 .to_options()
235 #fallback_usage
236 #version
237 #descr
238 #header
239 #footer
240 #usage
241 #max_width
242 .command(#name)
243 #(#short)*
244 #(#long)*
245 #help
246 #adjacent
247 #boxed
248 }
249 }
250 } else {
251 Error::new(self.span(), "Internal bpaf_derive error: Command name was not set! This is a bug, please report it.").to_compile_error()
252 }
253 }
254 Mode::Options { options } => {
255 let OptionsCfg {
256 cargo_helper,
257 usage,
258 version,
259 descr,
260 footer,
261 header,
262 max_width,
263 fallback_usage,
264 } = options;
265 let body = match cargo_helper {
266 Some(cargo) => quote!(::bpaf::cargo_helper(#cargo, #body)),
267 None => quote!(#body),
268 };
269
270 let fallback_usage = if *fallback_usage {
271 Some(quote!(.fallback_to_usage()))
272 } else {
273 None
274 };
275 let version = version.as_ref().map(|v| quote!(.version(#v)));
276 let usage = usage.as_ref().map(|v| quote!(.usage(#v)));
277 let descr = descr.as_ref().map(|v| quote!(.descr(#v)));
278 let footer = footer.as_ref().map(|v| quote!(.footer(#v)));
279 let header = header.as_ref().map(|v| quote!(.header(#v)));
280 let max_width = max_width.as_ref().map(|v| quote!(.max_width(#v)));
281
282 quote! {
283 #[doc(hidden)]
284 #vis fn #generate() -> ::bpaf::OptionParser<#ty> {
285 #[allow(unused_imports)]
286 use ::bpaf::Parser;
287 #body
288 #(.#attrs)*
289 .to_options()
290 #fallback_usage
291 #version
292 #descr
293 #header
294 #footer
295 #usage
296 #max_width
297 }
298 }
299 }
300 Mode::Parser { parser } => {
301 let ParserCfg { group_help } = &parser;
302 let group_help = group_help.as_ref().map(|v| quote!(.group_help(#v)));
303 quote! {
304 #[doc(hidden)]
305 #vis fn #generate() -> impl ::bpaf::Parser<#ty> {
306 #[allow(unused_imports)]
307 use ::bpaf::Parser;
308 #body
309 #adjacent
310 #group_help
311 #(.#attrs)*
312 #boxed
313 }
314 }
315 }
316 };
317
318 if let Some(custom_path) = bpaf_path {
319 let mut replaced: ItemFn = parse_quote!(#original);
320 CratePathReplacer::new(parse_quote!(::bpaf), custom_path.clone())
321 .visit_item_fn_mut(&mut replaced);
322 replaced.to_token_stream()
323 } else {
324 original
325 }
326 .to_tokens(tokens)
327 }
328}
329
330#[derive(Debug, Clone)]
335pub(crate) enum Body {
336 Single(Branch),
338 Alternatives(Ident, Vec<EnumBranch>),
339}
340
341impl Parse for Body {
342 fn parse(input: ParseStream) -> Result<Self> {
343 if input.peek(token::Struct) {
344 input.parse::<token::Struct>()?;
345 let branch = Self::Single(input.parse::<Branch>()?);
346 if input.peek(token::Semi) {
347 input.parse::<token::Semi>()?;
348 }
349 Ok(branch)
350 } else if input.peek(token::Enum) {
351 input.parse::<token::Enum>()?;
352 let name = input.parse::<Ident>()?;
353 let content;
354 braced!(content in input);
355
356 let branches = content
357 .parse_terminated(ParsedEnumBranch::parse, token::Comma)?
358 .into_iter()
359 .filter_map(|p| p.resolve(&name).transpose())
360 .collect::<Result<Vec<_>>>()?;
361 Ok(Self::Alternatives(name, branches))
362 } else {
363 Err(input.error("Only structs and enums are supported"))
364 }
365 }
366}
367
368impl Body {
369 fn ty(&self) -> Ident {
370 match self {
371 Body::Single(b) => &b.ident,
372 Body::Alternatives(n, _) => n,
373 }
374 .clone()
375 }
376}
377
378impl Body {
379 fn set_named_command(&mut self, span: Span) -> Result<()> {
380 match self {
381 Body::Single(branch) => {
382 branch.set_command();
383 Ok(())
384 }
385 Body::Alternatives(_, _) => Err(Error::new(
386 span,
387 "You can't annotate `enum` with a named command.",
388 )),
389 }
390 }
391
392 fn set_unnamed_command(&mut self) -> Result<()> {
393 match self {
394 Body::Single(b) => {
395 b.set_unnamed_command();
396 Ok(())
397 }
398 Body::Alternatives(_name, _branches) => {
399 Ok(())
413 }
414 }
415 }
416}
417
418impl ToTokens for Body {
419 fn to_tokens(&self, tokens: &mut TokenStream) {
420 match self {
421 Body::Single(branch) => quote!(#branch),
422 Body::Alternatives(_name, b) if b.len() == 1 => {
423 let branch = &b[0];
424 quote!(#branch)
425 }
426 Body::Alternatives(_name, b) => {
427 let branches = b.iter();
428 let mk = |i| Ident::new(&format!("alt{}", i), Span::call_site());
429 let name_f = b.iter().enumerate().map(|(n, _)| mk(n));
430 let name_t = name_f.clone();
431 quote! {{
432 #( let #name_f = #branches; )*
433 ::bpaf::construct!([ #( #name_t, )* ])
434 }}
435 }
436 }
437 .to_tokens(tokens);
438 }
439}
440
441pub(crate) struct ParsedEnumBranch {
448 branch: Branch,
450 attrs: Vec<Attribute>,
451}
452
453impl ParsedEnumBranch {
454 fn resolve(self, enum_name: &Ident) -> Result<Option<EnumBranch>> {
455 let ParsedEnumBranch { mut branch, attrs } = self;
456
457 branch.enum_name = Some(EnumPrefix(enum_name.clone()));
458
459 let (enum_decor, mut help) = parse_bpaf_doc_attrs::<Ed>(&attrs)?;
460 let Ed { attrs: ea, skip } = enum_decor.unwrap_or_default();
461 if skip {
462 return Ok(None);
463 }
464
465 let mut attrs = Vec::with_capacity(ea.len());
466 let mut has_options = None;
467 let mut fallback_usage = false;
468 for attr in ea {
469 match attr {
470 EAttr::NamedCommand(_) => {
471 branch.set_command();
472 attrs.push(EAttr::ToOptions);
473 has_options = Some(attrs.len());
474 attrs.push(attr);
475 }
476 EAttr::UnnamedCommand => {
477 branch.set_command();
478 attrs.push(EAttr::ToOptions);
479 has_options = Some(attrs.len());
480 attrs.push(EAttr::NamedCommand(ident_to_long(&branch.ident)));
481 }
482
483 EAttr::CommandShort(_) | EAttr::CommandLong(_) => {
484 attrs.push(attr);
487 }
488
489 EAttr::UnitShort(n) => branch.set_unit_name(StrictName::Short {
490 name: n.unwrap_or_else(|| ident_to_short(&branch.ident)),
491 }),
492 EAttr::UnitLong(n) => branch.set_unit_name(StrictName::Long {
493 name: n.unwrap_or_else(|| ident_to_long(&branch.ident)),
494 }),
495 EAttr::Env(name) => branch.set_unit_name(StrictName::Env { name }),
496
497 EAttr::Usage(_) => {
498 if let Some(o) = attrs.iter().position(|i| matches!(i, EAttr::ToOptions)) {
499 attrs.insert(o + 1, attr);
500 } else {
501 unreachable!();
502 }
503 }
504 EAttr::Adjacent | EAttr::Hide => attrs.push(attr),
505 EAttr::Header(_) | EAttr::Footer(_) | EAttr::Descr(_) => {
506 if let Some(o) = attrs.iter().position(|i| matches!(i, EAttr::ToOptions)) {
507 attrs.insert(o + 1, attr);
508 }
509 }
510 EAttr::FallbackUsage => fallback_usage = true,
511 EAttr::ToOptions => unreachable!(),
512 }
513 }
514
515 if let Some(opts_at) = has_options {
516 if fallback_usage {
517 attrs.insert(opts_at, EAttr::FallbackUsage);
518 }
519
520 if let Some(h) = std::mem::take(&mut help) {
521 split_ehelp_into(h, opts_at, &mut attrs);
522 }
523 }
524 branch.set_inplicit_name();
525 if let Some(help) = help {
526 branch.push_help(help);
527 }
528
529 Ok(Some(EnumBranch { branch, attrs }))
530 }
531}
532
533impl Parse for ParsedEnumBranch {
534 fn parse(input: ParseStream) -> Result<Self> {
535 Ok(ParsedEnumBranch {
536 attrs: input.call(Attribute::parse_outer)?,
537 branch: input.parse::<Branch>()?,
538 })
539 }
540}
541
542#[derive(Debug, Clone)]
545pub(crate) struct EnumBranch {
546 branch: Branch,
548 attrs: Vec<EAttr>,
549}
550
551impl ToTokens for EnumBranch {
552 fn to_tokens(&self, tokens: &mut TokenStream) {
553 let EnumBranch { branch, attrs } = self;
554 quote!(#branch #(.#attrs)*).to_tokens(tokens);
555 }
556}
557
558#[derive(Debug, Clone)]
561pub struct Branch {
562 pub(crate) enum_name: Option<EnumPrefix>,
564 pub(crate) ident: Ident,
565 pub(crate) fields: FieldSet,
566}
567
568impl Branch {
569 fn set_command(&mut self) {
570 if let FieldSet::Unit(_, _, _) = self.fields {
571 let ident = &self.ident;
572 let enum_name = &self.enum_name;
573 self.fields = FieldSet::Pure(parse_quote!(::bpaf::pure(#enum_name #ident)));
574 }
575 }
576
577 fn set_unnamed_command(&mut self) {
578 if let FieldSet::Unit(_, _, _) = self.fields {
579 self.set_command();
580 }
581 }
582
583 fn set_unit_name(&mut self, name: StrictName) {
584 if let FieldSet::Unit(_, names, _) = &mut self.fields {
585 names.push(name);
586 }
587 }
588
589 fn set_inplicit_name(&mut self) {
590 if let FieldSet::Unit(_, names, _) = &mut self.fields {
591 if !names
592 .iter()
593 .any(|n| matches!(n, StrictName::Long { .. } | StrictName::Short { .. }))
594 {
595 names.push(StrictName::Long {
596 name: ident_to_long(&self.ident),
597 });
598 }
599 }
600 }
601 fn push_help(&mut self, help: Help) {
602 if let FieldSet::Unit(_, _, h) = &mut self.fields {
603 *h = Some(help);
604 }
608 }
609}
610
611impl Parse for Branch {
612 fn parse(input: ParseStream) -> Result<Self> {
613 let ident = input.parse::<Ident>()?;
614 let content;
615 let fields = if input.peek(token::Paren) {
616 parenthesized!(content in input);
617 FieldSet::Unnamed(content.parse_terminated(StructField::parse_unnamed, token::Comma)?)
618 } else if input.peek(token::Brace) {
619 braced!(content in input);
620 FieldSet::Named(content.parse_terminated(StructField::parse_named, token::Comma)?)
621 } else {
622 if input.peek(token::Semi) {
623 input.parse::<token::Semi>()?;
624 }
625 FieldSet::Unit(ident.clone(), Vec::new(), None)
626 };
627
628 Ok(Branch {
629 enum_name: None,
630 ident,
631 fields,
633 })
634 }
635}
636
637impl ToTokens for Branch {
638 fn to_tokens(&self, tokens: &mut TokenStream) {
639 let Branch {
640 enum_name,
641 ident,
642 fields,
643 } = self;
644 match fields {
645 FieldSet::Named(fields) if fields.is_empty() => {
646 quote! {
647 ::bpaf::pure( #enum_name #ident {})
648 }
649 }
650 FieldSet::Named(fields) => {
651 let name = fields
652 .iter()
653 .enumerate()
654 .map(|(ix, field)| field.var_name(ix));
655 let result = name.clone();
656 let value = fields.iter();
657 quote! {{
658 #( let #name = #value; )*
659 ::bpaf::construct!( #enum_name #ident { #( #result , )* })
660 }}
661 }
662
663 FieldSet::Unnamed(fields) if fields.is_empty() => {
664 quote! {
665 ::bpaf::pure( #enum_name #ident ())
666 }
667 }
668 FieldSet::Unnamed(fields) => {
669 let name = fields
670 .iter()
671 .enumerate()
672 .map(|(ix, field)| field.var_name(ix));
673 let result = name.clone();
674 let value = fields.iter();
675 quote! {{
676 #( let #name = #value; )*
677 ::bpaf::construct!( #enum_name #ident ( #( #result , )* ))
678 }}
679 }
680 FieldSet::Unit(ident, names, help) => {
681 let help = help.iter();
682 if names.is_empty() {
683 let name = StrictName::Long {
684 name: ident_to_long(ident),
685 };
686 quote!(::bpaf:: #name.#(help(#help).)* req_flag(#enum_name #ident))
687 } else {
688 quote!(::bpaf:: #( #names .)* #(help(#help).)* req_flag(#enum_name #ident))
689 }
690 }
691 FieldSet::Pure(x) => quote!(#x),
692 }
693 .to_tokens(tokens);
694 }
695}
696#[derive(Debug, Clone)]
699pub(crate) enum FieldSet {
700 Named(Punctuated<StructField, token::Comma>),
702 Unnamed(Punctuated<StructField, token::Comma>),
703 Unit(Ident, Vec<StrictName>, Option<Help>),
704 Pure(Box<Expr>),
705}