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